Skip to content

Instantly share code, notes, and snippets.

@Varriount
Last active April 8, 2020 21:24
Show Gist options
  • Save Varriount/65a91d2d74baba4d9ec25a173d31e945 to your computer and use it in GitHub Desktop.
Save Varriount/65a91d2d74baba4d9ec25a173d31e945 to your computer and use it in GitHub Desktop.
from strutils import nil
template copy(target: string, tRegion: Slice|HSlice, source: string, sRegion: Slice|HSlice): untyped =
template resolve(c, i): untyped =
when i is BackwardsIndex:
len(c) - int(i)
else:
i
# Figure out the length of each slice
var tSpan = tRegion.a - resolve(tRegion, tRegion.b)
var sSpan = sRegion.a - resolve(sRegion, sRegion.b)
if tSpan > sSpan:
target[tRegion.a .. tRegion.a + sSpan] = source[sRegion]
elif tSpan < sSpan:
target[tRegion] = source[sRegion.a .. sRegion.a + tSpan]
else:
target[tRegion] = source[sRegion]
const OS = ""
type error = string
type rune = distinct int16
proc `<` * (x, y: rune): bool {.borrow.}
proc `<=` * (x, y: rune): bool {.borrow.}
proc `==` * (x, y: rune): bool {.borrow.}
# OS
const os_PathSeparator = '/'
const os_PathListSeparator = '/'
const os_ModeSymlink = 0'i32
type os_FileInfo = pointer
type os_File = object of RootObj
type os_FileMode = int32
proc IsDir(fi: os_FileMode): bool = discard
proc IsDir(fi: os_FileInfo): bool = discard
proc Mode(fi: os_FileInfo): os_FileMode = discard
proc Close(f: os_File) = discard
proc Readdirnames(f: os_File, i: int): seq[string] = discard
proc os_Getwd(): string = discard
proc os_Lstat(root: string): os_FileInfo = discard
proc os_Readlink(dest: string): string = discard
proc os_IsPathSeparator(sep: char): bool = discard
proc os_Stat(dir: string): os_FileInfo = discard
proc os_Open(dir: string): os_File = discard
# Syscall
const syscall_ENOTDIR = "ENOTDIR"
type syscall_Win32finddata = object of RootObj
FileName: string
type syscall_Handle = RootObj
proc syscall_UTF16PtrFromString(s: string): pointer = discard
proc syscall_FindFirstFile(a: pointer, b: ptr syscall_Win32finddata): syscall_Handle = discard
proc syscall_FindClose(h: RootObj) = discard
proc syscall_UTF16ToString(a: string): string = discard
proc syscall_FullPath(path: string): string = discard
# Strings
# proc strutils.toUpperAscii(volume: string): string
proc strings_LastIndexByte(path: string, sep: char): int = discard
proc strings_Count(base: string, sep: string): int = discard
proc strings_Contains(name: string, sep: string): bool = discard
proc strings_ContainsAny(path: string, magicChars: string): bool = discard
proc strings_Split(path: string, sep: string): seq[string] = discard
proc strings_HasPrefix(a: string, b: string): bool = discard
proc strings_ReplaceAll(a: string, b: string, c: string): string = discard
proc strings_Join(elem: openarray[string], sep: string): string = discard
proc strings_EqualFold(a: string, b: string): bool = discard
proc strings_ToLower(s: string): string = discard
# UTF8
const utf8_RuneError = rune(0)
proc utf8_DecodeRuneInString(chunk: string): (rune, int) = discard
# Sort
proc sort_Strings(s: var seq[string]) = discard
const
Separator = os_PathSeparator
ListSeparator = os_PathListSeparator
## A lazybuf is a lazily constructed path buffer.
## It supports append, reading previously appended bytes,
## and retrieving the final string. It does not allocate a buffer
## to hold the output until that output diverges from s.
type lazybuf = object of RootObj
path: string
buf: string
w: int
volAndPath: string
volLen: int
type WalkFunc = proc (path: string, info: os_FileInfo, err: bool): bool
proc `$`(b: var lazybuf): string
proc abs_win(path_p: string): string
proc abs_plan9(path_p: string): string
proc Abs*(path: string): string
proc append(b: var lazybuf, c: char)
proc Base*(path_p: string): string
proc baseIsDotDot(path: string): bool
proc Clean*(path_p: string): string
proc cleanGlobPath(path: string): string
proc cleanGlobPathWindows(path: string): tuple[prefixLen: int, cleaned: string]
proc Dir*(path: string): string
proc evalSymlinks_win(path: string): string
proc evalSymlinks_posix(path: string): string
proc EvalSymlinks*(path: string): string
proc Ext*(path: string): string
proc FromSlash*(path: string): string
proc getEsc(chunk_p: string): tuple[r: rune, nchunk: string]
proc glob_helper(dir, pattern: string, matches: seq[string]): seq[string]
proc Glob*(pattern: string): seq[string]
proc hasMeta(path: string): bool
proc HasPrefix_win*(p, prefix: string): bool
proc HasPrefix_posix*(p, prefix: string): bool
proc HasPrefix_plan9*(p, prefix: string): bool
proc HasPrefix*(p, prefix: string): bool
proc index(b: var lazybuf, i: int): char
proc isAbs_win*(path_p: string): bool
proc isAbs_posix*(path_p: string): bool
proc isAbs_plan9*(path_p: string): bool
proc IsAbs*(path: string): bool
proc isReservedName(path: string): bool
proc isSlash(c: char): bool
proc isUNC(path: string): bool
proc join_win(elem: openarray[string]): string
proc join_posix(elem: openarray[string]): string
proc join_plan9(elem: openarray[string]): string
proc Join*(elem: varargs[string]): string
proc joinNonEmpty(elem: openarray[string]): string
proc Match*(pattern_p, name_p: string): bool
proc matchChunk(chunk_p, s_p: string): tuple[rest: string, ok: bool]
proc normBase(path: string): string
proc normVolumeName(path: string): string
proc readDirNames(dirname: string): seq[string]
proc Rel*(basepath, targpath: string): string
proc sameWord_win(a, b: string): bool
proc sameWord_posix(a, b: string): bool
proc sameWord_plan9(a, b: string): bool
proc sameWord(a, b: string): bool
proc scanChunk(pattern_p: string): tuple[star: bool, chunk, rest: string]
proc Split*(path: string): tuple[dir, file: string]
proc splitList_win(path: string): seq[string]
proc splitList_posix(path: string): seq[string]
proc splitList_plan9(path: string): seq[string]
proc SplitList*(path: string): seq[string]
proc toNorm(path_p: string, normBase: proc (s: string): string): string
proc ToSlash*(path: string): string
proc unixAbs(path: string): string
proc VolumeName*(path: string): string
proc volumeNameLen_win(path: string): int
proc volumeNameLen_posix(path: string): int
proc volumeNameLen_plan9(path: string): int
proc volumeNameLen(path: string): int
proc walk(path: string, info: os_FileInfo, walkFn: WalkFunc): bool
proc Walk*(root: string, walkFn: WalkFunc): bool
proc walkSymlinks(path_p: string): string
## normVolumeName is like VolumeName, but makes drive letter upper case.
## result of EvalSymlinks must be unique, so we have
## EvalSymlinks('''c:\a''') == EvalSymlinks('''C:\a''').
proc normVolumeName(path: string): string =
var volume = VolumeName(path)
if len(volume) > 2: ## isUNC
return volume
return strutils.toUpperAscii(volume)
## normBase returns the last element of path with correct case.
## Note that this uses the filesystem to normalize the path, so it must exist.
proc normBaseFromFS(path: string): string =
var utf16Path = syscall_UTF16PtrFromString(path)
var
data: syscall_Win32finddata
handle = syscall_FindFirstFile(utf16Path, addr data)
syscall_FindClose(handle)
return syscall_UTF16ToString(data.FileName)
## baseIsDotDot reports whether the last element of path is "..".
## The given path should be 'Clean'-ed in advance.
proc baseIsDotDot(path: string): bool =
return (
len(path) >= 3 and # Can this be optimized away?
path[^1] == '.' and
path[^1] == '.' and
path[^1] == Separator
)
## toNorm returns the normalized path that is guaranteed to be unique.
## It should accept the following formats:
## * UNC paths (e.g \\server\share\foo\bar)
## * absolute paths (e.g C:\foo\bar)
## * relative paths begin with drive letter (e.g C:foo\bar, C:..\foo\bar, C:.., C:.)
## * relative paths begin with '\' (e.g \foo\bar)
## * relative paths begin without '\' (e.g foo\bar, ..\foo\bar, .., .)
## The returned normalized path will be in the same form (of 5 listed above) as the input path.
## If two paths A and B are indicating the same file with the same format, toNorm(A) should be equal to toNorm(B).
## The normBase parameter should be equal to the normBase func, except for in tests. See docs on the normBase func.
proc toNorm(path_p: string, normBase: proc(s: string): string): string =
if path_p == "":
return path_p
var
path = Clean(path_p)
volume = normVolumeName(path_p)
path = path[len(volume)..^1]
## skip special cases
if path == "." or path == "\\":
return volume & path
var normPath: string
while true:
if baseIsDotDot(path):
normPath = path & "\\" & normPath
break
var name = normBase(volume & path)
normPath = name & "\\" & normPath
var i = strings_LastIndexByte(path, Separator)
if i == -1:
break
if i == 0: ## '''\Go''' or '''C:\Go'''
normPath = "\\" & normPath
break
path = path[0..(i - 1)]
normPath = normPath[0..(len(normPath) - 2)] ## remove trailing '\'
return volume & normPath
proc evalSymlinks_win(path: string): string =
var newpath = walkSymlinks(path)
newpath = toNorm(newpath, normBase)
return newpath
## Package filepath implements utility routines for manipulating filename paths
## in a way compatible with the target operating system-defined file paths.
##
## The filepath package uses either forward slashes or backslashes,
## depending on the operating system. To process paths such as URLs
## that always use forward slashes regardless of the operating
## system, see the path package.
# package filepath
proc index(b: var lazybuf, i: int): char =
if b.buf != "":
return b.buf[i]
return b.path[i]
proc append(b: var lazybuf, c: char) =
if b.buf == "":
if b.w < len(b.path) and b.path[b.w] == c:
b.w += 1
return
b.buf = newString(len(b.path))
for i in 0..(b.w - 1):
b.buf[i] = b.path[i]
b.buf[b.w] = c
b.w += 1
proc `$`(b: var lazybuf): string =
if b.buf == "":
return b.volAndPath[0..(b.volLen + b.w - 1)]
return b.volAndPath[0..(b.volLen - 1)] & b.buf[0..(b.w - 1)]
## Clean returns the shortest path name equivalent to path
## by purely lexical processing. It applies the following rules
## iteratively until no further processing can be done:
##
## 1. Replace multiple Separator elements with a single one.
## 2. Eliminate each . path name element (the current directory).
## 3. Eliminate each inner .. path name element (the parent directory)
## along with the non-.. element that precedes it.
## 4. Eliminate .. elements that begin a rooted path:
## that is, replace "/.." by "/" at the beginning of a path,
## assuming Separator is '/'.
##
## The returned path ends in a slash only if it represents a root directory,
## such as "/" on Unix or '''C:\''' on Windows.
##
## Finally, any occurrences of slash are replaced by Separator.
##
## If the result of this process is an empty string, Clean
## returns the string ".".
##
## See also Rob Pike, ''''''Lexical File Names in Plan 9 or
## Getting Dot-Dot Right,''
## https:##9p.io/sys/doc/lexnames.html
proc Clean*(path_p: string): string =
var path = path_p
var originalPath = path
var volLen = 0
if OS == "win":
volLen = volumeNameLen_win(path)
when defined(posix):
volLen = volumeNameLen_posix(path)
when defined(plan9):
volLen = volumeNameLen_plan9(path)
path = path[volLen..^1]
if path == "":
if volLen > 1 and originalPath[1] != ':':
## should be UNC
return FromSlash(originalPath)
return originalPath & "."
var rooted = os_IsPathSeparator(path[0])
## Invariants:
## reading from path; r is index of next char to process.
## writing to buf; w is index of next char to write.
## dotdot is index in buf where .. must stop, either because
## it is the leading slash or it is a leading ../../.. prefix.
var n = len(path)
var outs = lazybuf(
path: path,
volAndPath: originalPath,
volLen: volLen
)
var (r, dotdot) = (0, 0)
if rooted:
outs.append(Separator)
(r, dotdot) = (1, 1)
while r < n:
if os_IsPathSeparator(path[r]):
## empty path element
r += 1
elif path[r] == '.' and (r + 1 == n or os_IsPathSeparator(path[r + 1])):
## . element
r += 1
elif path[r] == '.' and path[r + 1] == '.' and (r + 2 == n or os_IsPathSeparator(path[r + 2])):
## .. element: remove to last separator
r += 2
if outs.w > dotdot:
## can backtrack
outs.w -= 1
while outs.w > dotdot and not os_IsPathSeparator(outs.index(outs.w)):
outs.w -= 1
elif not rooted:
## cannot backtrack, but not rooted, so append .. element.
if outs.w > 0:
outs.append(Separator)
outs.append('.')
outs.append('.')
dotdot = outs.w
else:
## real path element.
## add slash if needed
if rooted and outs.w != 1 or not rooted and outs.w != 0:
outs.append(Separator)
## copy element
while r < n and not os_IsPathSeparator(path[r]):
outs.append(path[r])
r += 1
## Turn empty string into "."
if outs.w == 0:
outs.append('.')
return FromSlash($outs)
## ToSlash returns the result of replacing each separator character
## in path with a slash ('/') character. Multiple separators are
## replaced by multiple slashes.
proc ToSlash*(path: string): string =
if Separator == '/':
return path
return strings_ReplaceAll(path, $Separator, "/")
## FromSlash returns the result of replacing each slash ('/') character
## in path with a separator character. Multiple slashes are replaced
## by multiple separators.
proc FromSlash*(path: string): string =
if Separator == '/':
return path
return strings_ReplaceAll(path, "/", $Separator)
## SplitList splits a list of paths joined by the OS-specific ListSeparator,
## usually found in PATH or GOPATH environment variables.
## Unlike strings_Split, SplitList returns an empty slice when passed an empty
## string.
proc SplitList*(path: string): seq[string] =
if OS == "win":
return splitList_win(path)
elif OS == "posix":
return splitList_posix(path)
elif defined(plan9):
return splitList_plan9(path)
proc volumeNameLen(path: string): int =
if OS == "win":
return volumeNameLen_win(path)
elif OS == "posix":
return volumeNameLen_posix(path)
elif defined(plan9):
return volumeNameLen_plan9(path)
## Split splits path immediately following the final Separator,
## separating it into a directory and file name component.
## If there is no Separator in path, Split returns an empty dir
## and file set to path.
## The returned values have the property that path = dir+file.
proc Split*(path: string): tuple[dir, file: string] =
var vol = VolumeName(path)
var i = len(path) - 1
while i >= len(vol) and not os_IsPathSeparator(path[i]):
i -= 1
return (path[0..i], path[(i + 1)..^1])
## Join joins any number of path elements into a single path,
## separating them with an OS specific Separator. Empty elements
## are ignored. The result is Cleaned. However, if the argument
## list is empty or all its elements are empty, Join returns
## an empty string.
## On Windows, the result will only be a UNC path if the first
## non-empty element is a UNC path.
proc Join*(elem: varargs[string]): string =
if OS == "win":
return join_win(elem)
elif OS == "posix":
return join_posix(elem)
elif defined(plan9):
return join_plan9(elem)
## Ext returns the file name extension used by path.
## The extension is the suffix beginning at the final dot
## in the final element of path; it is empty if there is
## no dot.
proc Ext*(path: string): string =
var i = len(path) - 1
while i >= 0 and not os_IsPathSeparator(path[i]):
if path[i] == '.':
return path[i..^1]
i -= 1
return ""
## EvalSymlinks returns the path name after the evaluation of any symbolic
## links.
## If path is relative the result will be relative to the current directory,
## unless one of the components is an absolute symbolic link.
## EvalSymlinks calls Clean on the result.
proc EvalSymlinks*(path: string): string =
if OS == "win":
return evalSymLinks_win(path)
elif OS == "posix" or OS == "plan9":
return evalSymLinks_posix(path)
## Abs returns an absolute representation of path.
## If the path is not absolute it will be joined with the current
## working directory to turn it into an absolute path. The absolute
## path name for a given file is not guaranteed to be unique.
## Abs calls Clean on the result.
proc Abs*(path: string): string =
if OS == "win":
return abs_win(path)
elif defined(posix) or defined(plan9):
return abs_plan9(path)
proc IsAbs*(path: string): bool =
if OS == "win":
return isAbs_win(path)
elif OS == "posix":
return isAbs_posix(path)
elif defined(plan9):
return isAbs_plan9(path)
proc sameWord(a, b: string): bool =
if OS == "win":
return sameWord_win(a, b)
elif OS == "posix":
return sameWord_posix(a, b)
elif defined(plan9):
return sameWord_plan9(a, b)
proc unixAbs(path: string): string =
if IsAbs(path):
return Clean(path)
var wd = os_Getwd()
return Join(wd, path)
## Rel returns a relative path that is lexically equivalent to targpath when
## joined to basepath with an intervening separator. That is,
## Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself.
## On success, the returned path will always be relative to basepath,
## even if basepath and targpath share no elements.
## An error is returned if targpath can't be made relative to basepath or if
## knowing the current working directory would be necessary to compute it.
## Rel calls Clean on the result.
proc Rel*(basepath, targpath: string): string =
var baseVol = VolumeName(basepath)
var targVol = VolumeName(targpath)
var base = Clean(basepath)
var targ = Clean(targpath)
if sameWord(targ, base):
return "."
base = base[len(baseVol)..^1]
targ = targ[len(targVol)..^1]
if base == ".":
base = ""
## Can't use IsAbs - '''\a''' and '''a''' are both relative in Windows.
var baseSlashed = len(base) > 0 and base[0] == Separator
var targSlashed = len(targ) > 0 and targ[0] == Separator
if baseSlashed != targSlashed or not sameWord(baseVol, targVol):
raise newException(Exception, "Rel: can't make " & targpath & " relative to " & basepath)
## Position base[(b0)..(bi - 1)] and targ[(t0)..(ti - 1)] at the first differing elements.
var bl = len(base)
var tl = len(targ)
var b0, bi, t0, ti: int
while true:
while bi < bl and base[bi] != Separator:
bi += 1
while ti < tl and targ[ti] != Separator:
ti += 1
if not sameWord(targ[t0..(ti - 1)], base[b0..(bi - 1)]):
break
if bi < bl:
bi += 1
if ti < tl:
ti += 1
b0 = bi
t0 = ti
if base[b0..(bi - 1)] == "..":
raise newException(Exception, "Rel: can't make " & targpath & " relative to " & basepath)
if b0 != bl:
## Base elements left. Must go up before going down.
var seps = strings_Count(base[b0..(bl - 1)], $Separator)
var size = 2 + seps * 3
if tl != t0:
size += 1 + tl - t0
var buf = newString(size)
var n = 2
for i in 0..(seps - 1):
buf[n] = Separator
copy(buf, (n + 1)..^1, "..", 0..2)
n += 3
if t0 != tl:
buf[n] = Separator
copy(buf, (n + 1)..^1, targ, (t0)..^1)
return buf
return targ[t0..^1]
## SkipDir is used as a return value from WalkFuncs to indicate that
## the directory named in the call is to be skipped. It is not returned
## as an error by any function.
var SkipDir = "skip this directory"
## WalkFunc is the type of the function called for each file or directory
## visited by Walk. The path argument contains the argument to Walk as a
## prefix; that is, if Walk is called with "dir", which is a directory
## containing the file "a", the walk function will be called with argument
## "dir/a". The info argument is the os_FileInfo for the named path.
##
## If there was a problem walking to the file or directory named by path, the
## incoming error will describe the problem and the function can decide how
## to handle that error (and Walk will not descend into that directory). In the
## case of an error, the info argument will be nil. If an error is returned,
## processing stops. The sole exception is when the function returns the special
## value SkipDir. If the function returns SkipDir when invoked on a directory,
## Walk skips the directory's contents entirely. If the function returns SkipDir
## when invoked on a non-directory file, Walk skips the remaining files in the
## containing directory.
## walk recursively descends path, calling walkFn.
proc walk(path: string, info: os_FileInfo, walkFn: WalkFunc): bool =
if not info.IsDir():
return walkFn(path, info, false)
var
names: seq[string]
err: bool
try:
names = readDirNames(path)
except:
err = true
var err1 = walkFn(path, info, err)
## If err != nil, walk can't walk into this directory.
## err1 != nil means walkFn want walk to skip this directory or stop walking.
## Therefore, if one of err and err1 isn't nil, walk will return.
if err or err1:
## The caller's behavior is controlled by the return value, which is decided
## by walkFn. walkFn may ignore err and return nil.
## If walkFn returns SkipDir, it will be handled by the caller.
## So walk should return whatever walkFn returns.
return err1
for name in names:
var filename = Join(path, name)
var fileInfo: os_FileInfo
try:
fileInfo = os_Lstat(filename)
except:
err = true
if err:
err = walkFn(filename, fileInfo, err)
if err: # and err != SkipDir:
return err
else:
err = walk(filename, fileInfo, walkFn)
if err:
if not fileInfo.IsDir(): # or err != SkipDir:
return err
return false
## Walk walks the file tree rooted at root, calling walkFn for each file or
## directory in the tree, including root. All errors that arise visiting files
## and directories are filtered by walkFn. The files are walked in lexical
## order, which makes the output deterministic but means that for very
## large directories Walk can be inefficient.
## Walk does not follow symbolic links.
proc Walk*(root: string, walkFn: WalkFunc): bool =
var err: bool
var info: os_FileInfo
try:
info = os_Lstat(root)
except:
err = true
if err:
err = walkFn(root, nil, err)
else:
err = walk(root, info, walkFn)
if err: # == SkipDir:
return false
return err
## readDirNames reads the directory named by dirname and returns
## a sorted list of directory entries.
proc readDirNames(dirname: string): seq[string] =
var f = os_Open(dirname)
var names = f.Readdirnames(-1)
f.Close()
sort_Strings(names)
return names
## Base returns the last element of path.
## Trailing path separators are removed before extracting the last element.
## If the path is empty, Base returns ".".
## If the path consists entirely of separators, Base returns a single separator.
proc Base*(path_p: string): string =
var path = path_p
if path == "":
return "."
## Strip trailing slashes.
while len(path) > 0 and os_IsPathSeparator(path[len(path) - 1]):
path = path[0..(len(path) - 2)]
## Throw away volume name
path = path[len(VolumeName(path))..^1]
## Find the last element
var i = len(path) - 1
while i >= 0 and not os_IsPathSeparator(path[i]):
i -= 1
if i >= 0:
path = path[(i + 1)..^1]
## If empty now, it had only slashes.
if path == "":
return $Separator
return path
## Dir returns all but the last element of path, typically the path's directory.
## After dropping the final element, Dir calls Clean on the path and trailing
## slashes are removed.
## If the path is empty, Dir returns ".".
## If the path consists entirely of separators, Dir returns a single separator.
## The returned path does not end in a separator unless it is the root directory.
proc Dir*(path: string): string =
var vol = VolumeName(path)
var i = len(path) - 1
while i >= len(vol) and not os_IsPathSeparator(path[i]):
i -= 1
var dir = Clean(path[len(vol)..i])
if dir == "." and len(vol) > 2:
## must be UNC
return vol
return vol & dir
## VolumeName returns leading volume name.
## Given "C:\foo\bar" it returns "C:" on Windows.
## Given "\\host\share\foo" it returns "\\host\share".
## On other platforms it returns "".
proc VolumeName*(path: string): string =
if OS == "win":
return path[0..(volumeNameLen_win(path) - 1)]
elif OS == "posix":
return path[0..(volumeNameLen_posix(path) - 1)]
elif OS == "plan9":
return path[0..(volumeNameLen_plan9(path) - 1)]
## +build aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solaris
# package filepath
## IsAbs reports whether the path is absolute.
proc isAbs_posix*(path_p: string): bool =
return strings_HasPrefix(path_p, "/")
proc HasPrefix*(p, prefix: string): bool =
if OS == "win":
return HasPrefix_win(p, prefix)
else:
return HasPrefix_posix(p, prefix)
## volumeNameLen returns length of the leading volume name on Windows.
## It returns 0 elsewhere.
proc volumeNameLen_posix(path: string): int =
return 0
## HasPrefix exists for historical compatibility and should not be used.
##
## Deprecated: HasPrefix does not respect path boundaries and
## does not ignore case when required.
proc HasPrefix_posix*(p, prefix: string): bool =
return strings_HasPrefix(p, prefix)
proc splitList_posix(path: string): seq[string] =
if path == "":
return @[]
return strings_Split(path, $ListSeparator)
proc join_posix(elem: openarray[string]): string =
## If there's a bug here, fix the logic in ./path_plan9.go too.
for i, e in elem:
if e != "":
return Clean(strings_Join(elem[i..^1], $Separator))
return ""
proc sameWord_posix(a, b: string): bool =
return a == b
# package filepath
proc walkSymlinks(path_p: string): string =
var path = path_p
var volLen = volumeNameLen(path)
var pathSeparator = $os_PathSeparator
if volLen < len(path) and os_IsPathSeparator(path[volLen]):
volLen += 1
var vol = path[0..(volLen - 1)]
var dest = vol
var linksWalked = 0
var (start, ends) = (volLen, volLen)
while start < len(path):
while start < len(path) and os_IsPathSeparator(path[start]):
start += 1
ends = start
while ends < len(path) and not os_IsPathSeparator(path[ends]):
ends += 1
start = ends
## On Windows, "." can be a symlink.
## We look it up, and use the value if it is absolute.
## If not, we just return ".".
var isWindowsDot = OS == "windows" and path[volumeNameLen(path)..^1] == "."
## The next path component is in path[start..(end - 1)].
if ends == start:
## No more path components.
break
elif path[start..(ends - 1)] == "." and not isWindowsDot:
## Ignore path component ".".
continue
elif path[start..(ends - 1)] == "..":
## Back up to previous component if possible.
## Note that volLen includes any leading slash.
## Set r to the index of the last slash in dest,
## after the volume.
var r = len(dest) - 1
while r >= volLen:
if os_IsPathSeparator(dest[r]):
break
r -= 1
if r < volLen or dest[(r + 1)..^1] == "..":
## Either path has no slashes
## (it's empty or just "C:")
## or it ends in a ".." we had to keep.
## Either way, keep this "..".
if len(dest) > volLen:
dest &= pathSeparator
dest &= ".."
else:
## Discard everything since the last slash.
dest = dest[0..(r - 1)]
continue
## Ordinary path component. Add it to result.
if len(dest) > volumeNameLen(dest) and not os_IsPathSeparator(dest[len(dest) - 1]):
dest &= pathSeparator
dest &= path[start..(ends - 1)]
## Resolve symlink.
var fi = os_Lstat(dest)
if true: # fi.Mode() & os_ModeSymlink == 0'i32:
if not fi.Mode().IsDir() and ends < len(path):
raise newException(Exception, syscall_ENOTDIR)
continue
## Found symlink.
linksWalked += 1
if linksWalked > 255:
raise newException(Exception, "EvalSymlinks: too many links")
var link: string
link = os_Readlink(dest)
if isWindowsDot and not IsAbs(link):
## On Windows, if "." is a relative symlink,
## just return ".".
break
path = link & path[ends..^1]
var v = volumeNameLen(link)
if v > 0:
## Symlink to drive name is an absolute path.
if v < len(link) and os_IsPathSeparator(link[v]):
v += 1
vol = link[0..(v - 1)]
dest = vol
ends = len(vol)
elif len(link) > 0 and os_IsPathSeparator(link[0]):
## Symlink to absolute path.
dest = link[0..(1 - 1)]
ends = 1
else:
## Symlink to relative path; replace last
## path component in dest.
var r = len(dest) - 1
while r >= volLen:
if os_IsPathSeparator(dest[r]):
break
r -= 1
if r < volLen:
dest = vol
else:
dest = dest[0..(r - 1)]
ends = 0
return Clean(dest)
## ErrBadPattern indicates a pattern was malformed.
var ErrBadPattern = "syntax error in pattern"
## Match reports whether name matches the shell file name pattern.
## The pattern syntax is:
##
## pattern:
## { term }
## term:
## '*' matches any sequence of non-Separator characters
## '?' matches any single non-Separator character
## '[' [ '^' ] { character-range } ']'
## character class (must be non-empty)
## c matches character c (c != '*', '?', '\\', '[')
## '\\' c matches character c
##
## character-range:
## c matches character c (c != '\\', '-', ']')
## '\\' c matches character c
## lo '-' hi matches character c for lo <= c <= hi
##
## Match requires pattern to match all of name, not just a substring.
## The only possible returned error is ErrBadPattern, when pattern
## is malformed.
##
## On Windows, escaping is disabled. Instead, '\\' is treated as
## path separator.
##
proc Match*(pattern_p, name_p: string): bool =
var pattern = pattern_p
var name = name_p
while len(pattern) > 0:
block Pattern:
var star: bool
var chunk: string
(star, chunk, pattern) = scanChunk(pattern)
if star and chunk == "":
## Trailing * matches rest of string unless it has a /.
return not strings_Contains(name, $Separator)
## Look for match at current position.
var (t, ok) = matchChunk(chunk, name)
## if we're the last chunk, make sure we've exhausted the name
## otherwise we'll give a false result even if we could still match
## using the star
if ok and (len(t) == 0 or len(pattern) > 0):
name = t
continue
# if err != "":
# return (false, err)
if star:
## Look for match skipping i + 1 chars.
## Cannot skip /.
var i = 0
while i < len(name) and name[i] != Separator:
var (t, ok) = matchChunk(chunk, name[i + 1..^1])
if ok:
## if we're the last chunk, make sure we exhausted the name
if len(pattern) == 0 and len(t) > 0:
i += 1
continue
name = t
break Pattern
# if err != "":
# return (false, err)
i += 1
return false
return len(name) == 0
## scanChunk gets the next segment of pattern, which is a non-star string
## possibly preceded by a star.
proc scanChunk(pattern_p: string): tuple[star: bool, chunk, rest: string] =
var pattern = pattern_p
while len(pattern) > 0 and pattern[0] == '*':
pattern = pattern[1..^1]
result.star = true
var inrange = false
var i = 0
while i < len(pattern):
case pattern[i]
of '\\':
if OS != "windows":
## error check handled in matchChunk: bad pattern.
if i + 1 < len(pattern):
i += 1
of '[':
inrange = true
of ']':
inrange = false
of '*':
if not inrange:
break
else:
discard
i += 1
return (result.star, pattern[0..(i - 1)], pattern[i..^1])
## matchChunk checks whether chunk matches the beginning of s.
## If so, it returns the remainder of s (after the match).
## Chunk is all single-character operators: literals, char classes, and ?.
proc matchChunk(chunk_p, s_p: string): tuple[rest: string, ok: bool] =
var chunk = chunk_p
var s = s_p
while len(chunk) > 0:
if len(s) == 0:
return
case chunk[0]
of '[':
## character class
var (r, n) = utf8_DecodeRuneInString(s)
s = s[n..^1]
chunk = chunk[1..^1]
## We can't end right after '[', we're expecting at least
## a closing bracket and possibly a caret.
if len(chunk) == 0:
raise newException(Exception, ErrBadPattern)
## possibly negated
var negated = chunk[0] == '^'
if negated:
chunk = chunk[1..^1]
## parse all ranges
var match = false
var nrange = 0
while true:
if len(chunk) > 0 and chunk[0] == ']' and nrange > 0:
chunk = chunk[1..^1]
break
var lo, hi: rune
(lo, chunk) = getEsc(chunk)
hi = lo
if chunk[0] == '-':
(hi, chunk) = getEsc(chunk[1..^1])
if lo <= r and r <= hi:
match = true
nrange += 1
if match == negated:
return
of '?':
if s[0] == Separator:
return
var (_, n) = utf8_DecodeRuneInString(s)
s = s[n..^1]
chunk = chunk[1..^1]
of '\\':
if OS != "windows":
chunk = chunk[1..^1]
if len(chunk) == 0:
raise newException(Exception, ErrBadPattern)
if chunk[0] != s[0]:
return
s = s[1..^1]
chunk = chunk[1..^1]
else:
if chunk[0] != s[0]:
return
s = s[1..^1]
chunk = chunk[1..^1]
return (s, true)
## getEsc gets a possibly-escaped character from chunk, for a character class.
proc getEsc(chunk_p: string): tuple[r: rune, nchunk: string] =
var chunk = chunk_p
if len(chunk) == 0 or chunk[0] == '-' or chunk[0] == ']':
raise newException(Exception, ErrBadPattern)
if chunk[0] == '\\' and OS != "windows":
chunk = chunk[1..^1]
if len(chunk) == 0:
raise newException(Exception, ErrBadPattern)
var (r, n) = utf8_DecodeRuneInString(chunk)
if r == utf8_RuneError and n == 1:
raise newException(Exception, ErrBadPattern)
result.nchunk = chunk[n..^1]
if len(result.nchunk) == 0:
raise newException(Exception, ErrBadPattern)
return
## Glob returns the names of all files matching pattern or nil
## if there is no matching file. The syntax of patterns is the same
## as in Match. The pattern may describe hierarchical names such as
## /usr/*/bin/ed (assuming the Separator is '/').
##
## Glob ignores file system errors such as I/O errors reading directories.
## The only possible returned error is ErrBadPattern, when pattern
## is malformed.
proc Glob*(pattern: string): seq[string] =
if not hasMeta(pattern):
discard os_Lstat(pattern)
# if result.err != "":
# return (@[], "")
return @[pattern]
var (dir, file) = Split(pattern)
var volumeLen = 0
if OS == "windows":
(volumeLen, dir) = cleanGlobPathWindows(dir)
else:
dir = cleanGlobPath(dir)
if not hasMeta(dir[volumeLen..^1]):
if OS == "win":
return glob_helper(dir, file, @[])
## Prevent infinite recursion. See issue 15879.
if dir == pattern:
raise newException(Exception, ErrBadPattern)
var m = Glob(dir)
for _, d in m:
result = glob_helper(d, file, result)
return
## cleanGlobPath prepares path for glob matching.
proc cleanGlobPath(path: string): string =
case path
of "":
return "."
of $Separator:
## do nothing to the path
return path
else:
return path[0..(len(path) - 2)] ## chop off trailing separator
## cleanGlobPathWindows is windows version of cleanGlobPath.
proc cleanGlobPathWindows(path: string): tuple[prefixLen: int, cleaned: string] =
var vollen = volumeNameLen(path)
if path == "":
return (0, ".")
elif vollen + 1 == len(path) and os_IsPathSeparator(path[len(path) - 1]): ## /, \, C:\ and C:/
## do nothing to the path
return (vollen + 1, path)
elif vollen == len(path) and len(path) == 2: ## C:
return (vollen, path & ".") ## convert C: into C:.
else:
if vollen >= len(path):
vollen = len(path) - 1
return (vollen, path[0..(len(path) - 2)]) ## chop off trailing separator
## glob searches for files matching pattern in the directory dir
## and appends them to matches. If the directory cannot be
## opened, it returns the existing matches. New matches are
## added in lexicographical order.
proc glob_helper(dir, pattern: string, matches: seq[string]): seq[string] =
var m = matches
var fi = os_Stat(dir)
if not fi.IsDir():
return
var d: os_File
try:
d = os_Open(dir)
except:
return
defer: d.Close()
var names: seq[string]
try:
names = d.Readdirnames(-1)
except:
discard
sort_Strings(names)
for n in names:
var matched = Match(pattern, n)
if matched:
m.add(Join(dir, n))
return
## hasMeta reports whether path contains any of the magic characters
## recognized by Match.
proc hasMeta(path: string): bool =
var magicChars = r"*?["
if OS != "windows":
magicChars = r"*?[\"
return strings_ContainsAny(path, magicChars)
# package filepath
## IsAbs reports whether the path is absolute.
proc isAbs_plan9*(path_p: string): bool =
return strings_HasPrefix(path_p, "/") or strings_HasPrefix(path_p, "#")
## volumeNameLen returns length of the leading volume name on Windows.
## It returns 0 elsewhere.
proc volumeNameLen_plan9(path: string): int =
return 0
## HasPrefix exists for historical compatibility and should not be used.
##
## Deprecated: HasPrefix does not respect path boundaries and
## does not ignore case when required.
proc HasPrefix_plan9*(p, prefix: string): bool =
return strings_HasPrefix(p, prefix)
proc splitList_plan9(path: string): seq[string] =
if path == "":
return @[]
return strings_Split(path, $ListSeparator)
proc abs_plan9(path_p: string): string =
return unixAbs(path_p)
proc join_plan9(elem: openarray[string]): string =
## If there's a bug here, fix the logic in ./path_unix.go too.
for i, e in elem:
if e != "":
return Clean(strings_Join(elem[i..^1], $Separator))
return ""
proc sameWord_plan9(a, b: string): bool =
return a == b
# package filepath
proc isSlash(c: char): bool =
return c == '\\' or c == '/'
## reservedNames lists reserved Windows names. Search for PRN in
## https:##docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
## for details.
var reservedNames = @[
"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
]
## isReservedName returns true, if path is Windows reserved name.
## See reservedNames for the full list.
proc isReservedName(path: string): bool =
if len(path) == 0:
return false
for reserved in reservedNames:
if strings_EqualFold(path, reserved):
return true
return false
## IsAbs reports whether the path is absolute.
proc isAbs_win*(path_p: string): bool =
var path = path_p
if isReservedName(path):
return true
var l = volumeNameLen(path)
if l == 0:
return false
path = path[l..^1]
if path == "":
return false
return isSlash(path[0])
## volumeNameLen returns length of the leading volume name on Windows.
## It returns 0 elsewhere.
proc volumeNameLen_win(path: string): int =
if len(path) < 2:
return 0
# Classic Drive
let isDrive = (
path[1] == ':' and
(c in 'a'..'z' or c in'A'..'Z')
)
if isDrive:
return 2
## See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/62e862f4-2a51-452e-8eeb-dc4ff5ee33cc
let
isUNC = (
len(path) >= 5 and # "\\" host-name "\" share-name
isSlash(path[0]) and # "\"
isSlash(path[1]) and # " \"
not isSlash(path[2]) and # A NetBIOS name cannot start with a dot,
path[2] != '.' # nor can a FQDN or IP address.
)
if isUNC:
## TODO This doesn't ensure that the UNC is 100% valid
## First, find the slash separating the "host-name" and "share-name"
var n = -1
for i in 3..(high(path) - 1)
if isSlash(path[n]):
# If we've found a slash, move to the next character.
# We avoid going past the end of string by stopping at `high(path) - 1`.
n = i + 1
break
if n == -1:
return 0
# The share must have at least one character that doesn't start with a
# slash or a dot
if isSlash(path[n]) or path[n] == '.':
return 0
## Second, find the next slash or the end of the string.
if not isSlash(path[n]):
if path[n] == '.':
break
for i in n..high(path):
if isSlash(path[n]):
break
n += 1
return n
break
n += 1
return 0
## HasPrefix exists for historical compatibility and should not be used.
##
## Deprecated: HasPrefix does not respect path boundaries and
## does not ignore case when required.
proc HasPrefix_win*(p, prefix: string): bool =
if strings_HasPrefix(p, prefix):
return true
return strings_HasPrefix(strings_ToLower(p), strings_ToLower(prefix))
proc splitList_win(path: string): seq[string] =
## The same implementation is used in LookPath in os/exec;
## consider changing os/exec when changing this.
if path == "":
return @[]
## Split path, respecting but preserving quotes.
var list: seq[string] = @[]
var start = 0
var quo = false
var i = 0
while i < len(path):
case path[i]
of '"':
quo = not quo
of ListSeparator:
if not quo:
list.add(path[start..(i - 1)])
start = i + 1
else:
discard
i += 1
list.add(path[start..^1])
## Remove quotes.
for i, s in list:
list[i] = strings_ReplaceAll(s, "\"", "'")
return list
proc abs_win(path_p: string): string =
var path = path_p
if path == "":
## syscall_FullPath returns an error on empty path, because it's not a valid path.
## To implement Abs behavior of returning working directory on empty string input,
## special-case empty path by changing it to "." path. See golang.org/issue/24441.
path = "."
var fullPath = syscall_FullPath(path)
return Clean(fullPath)
proc join_win(elem: openarray[string]): string =
for i, e in elem:
if e != "":
return joinNonEmpty(elem[i..^1])
return ""
## joinNonEmpty is like join, but it assumes that the first element is non-empty.
proc joinNonEmpty(elem: openarray[string]): string =
if len(elem[0]) == 2 and elem[0][1] == ':':
## First element is drive letter without terminating slash.
## Keep path relative to current directory on that drive.
## Skip empty elements.
var i = 1
while i < len(elem):
if elem[i] != "":
break
i += 1
return Clean(elem[0] & strings_Join(elem[i..^1], $Separator))
## The following logic prevents Join from inadvertently creating a
## UNC path on Windows. Unless the first element is a UNC path, Join
## shouldn't create a UNC path. See golang.org/issue/9167.
var p = Clean(strings_Join(elem, $Separator))
if not isUNC(p):
return p
## p == UNC only allowed when the first element is a UNC path.
var head = Clean(elem[0])
if isUNC(head):
return p
## head + tail == UNC, but joining two non-UNC paths should not result
## in a UNC path. Undo creation of UNC path.
var tail = Clean(strings_Join(elem[1..^1], $Separator))
if head[len(head) - 1] == Separator:
return head & tail
return head & $Separator & tail
## isUNC reports whether path is a UNC path.
proc isUNC(path: string): bool =
return volumeNameLen(path) > 2
proc sameWord_win(a, b: string): bool =
return strings_EqualFold(a, b)
## +build not windows
# package filepath
proc evalSymlinks_posix(path: string): string =
return walkSymlinks(path)
import sys
try:
import regex
except:
print("Requires regex and possibly Python 3")
sys.exit(1)
LINE_START=r'^\t*'
WORD=r'\w+'
WORD_LIST=rf'{WORD}(?: *, *{WORD})+'
LINE_END=r' *$'
L_PAREN=r'\('
R_PAREN=r'\)'
L_CBRACE=r'\{'
R_CBRACE=r'\}'
L_SQBRACE=r'\['
R_SQBRACE=r'\]'
PAREN_PAIR_2=rf'(?P<PAREN_PAIR>{L_PAREN}((?>[^{L_PAREN}{R_PAREN}]+|(?&PAREN_PAIR))*){R_PAREN})'
CBRACE_PAIR_2=rf'(?P<CBRACE_PAIR>{L_CBRACE}((?>[^{L_CBRACE}{R_CBRACE}]+|(?&CBRACE_PAIR))*){R_CBRACE})'
BREAK=rf'\b'
STAR='\*'
TAB='\t'
subs = [
## Variable Transforms ##
# Single short variable declaration
fr'({LINE_START})({WORD}) *:=',
'{1}var {2} =',
# Multiple short variable declarations
rf'({LINE_START})({WORD_LIST}) *:=',
'{1}var ({2}) =',
# Single long variable declaration
rf'({LINE_START})var +({WORD}) +({WORD})',
'{1}var {2}: {3}',
# Multiple long variable declarations
rf'({LINE_START})var +({WORD_LIST}) +({WORD})',
'{1}var {2}: {3}',
# Multiple variable assignments
rf'({LINE_START})({WORD_LIST}) *=',
'{1}({2}) =',
## Function Transforms ##
# Method to Go func
rf'({LINE_START})func *{PAREN_PAIR_2} *({WORD}) *{L_PAREN}',
'{1}func {4} ({3}, ',
# Function argument to Nim argument
rf'({LINE_START}func +{WORD} *{L_PAREN}[^{R_PAREN}]*?){BREAK}({WORD}) +([^, \)]+)',
'{1}{2}: {3}',
# Function argument with reference to Nim argument
rf'({LINE_START}func +({WORD}) *{L_PAREN}[^{R_PAREN}]*?){BREAK}({WORD}) +{STAR} *({WORD})',
'{1}{2}: ref {4}',
# Function with no arguments and no return value
rf'({LINE_START})func +({WORD}) *{L_PAREN}{R_PAREN} *{L_CBRACE}{LINE_END}',
'{1}proc {2} =',
# Function with arguments and no return value
rf'({LINE_START})func +({WORD}) *{PAREN_PAIR_2} *{L_CBRACE}{LINE_END}',
'{1}proc {2}({4}) =',
# Function with no arguments and a return value
rf'({LINE_START})func +({WORD}) *{L_PAREN}{R_PAREN} *(.+?) *{L_CBRACE}{LINE_END}',
'{1}proc {2}(): {3} =',
# Function with arguments and a return value
rf'({LINE_START})func +({WORD}) *{PAREN_PAIR_2} *(.+?) *{L_CBRACE}{LINE_END}',
'{1}proc {2}({4}): {5} =',
# Add export marker to proc
rf'({LINE_START})proc +([A-Z]({WORD})?) *\(',
'{1}proc {2}*(',
## Operators & Symbols
# Comments
rf'//',
'##',
# Boolean And
rf'&&',
'and',
# Boolean Or
rf'\|\|',
'or',
# Boolean Negation
rf'!({WORD})',
'not {1}',
# Increment
rf'\+\+',
' += 1',
# Decrement
rf'--',
' -= 1',
# Slice with two arguments
rf'{L_SQBRACE} *([^{L_SQBRACE}{R_SQBRACE}: ]+?) *: *([^{L_SQBRACE}{R_SQBRACE}: ]+?) *{R_SQBRACE}',
'[({1})\.\.({2} - 1)]',
# Slice with right argument
rf'{L_SQBRACE} *: *([^{L_SQBRACE}{R_SQBRACE}: ]+?) *{R_SQBRACE}',
'[0\.\.({1} - 1)]',
# Slice with left argument
rf'{L_SQBRACE} *([^{L_SQBRACE}{R_SQBRACE}: ]+?) *: *{R_SQBRACE}',
'[({1})\.\.^1]',
# Remove redundant maths
rf'\+ *1 *- *1',
'',
rf'- *1 *- *1',
'- 2',
# Backticks to triple-quoted strings
'`',
rf"'''",
## Statements ##
# for statement to while true
rf'({LINE_START})for *{L_CBRACE}{LINE_END}',
'{1}while true:',
# if/for statement
rf'({LINE_START})((if|for) .*?) *{L_CBRACE}{LINE_END}',
'{1}{2}:',
# else statement
rf'({LINE_START}){R_CBRACE} *else *{L_CBRACE}{LINE_END}',
'{1}else:',
# Return statement
rf'({LINE_START})return +([^{L_PAREN}{R_PAREN}].*?,.*[^{L_PAREN}{R_PAREN}]){LINE_END}',
'{1}return ({2})',
# Case statement
rf'({LINE_START})case ',
'{1}of ',
# Switch statement
rf'({LINE_START})switch +(.*) *{L_CBRACE}{LINE_END}',
'{1}case {2}',
# Package statement
rf'({LINE_START})(package .*{LINE_END})',
'# {2}',
## Function Calls ##
rf'fmt\.Println',
'echo',
rf'{L_SQRACE}{R_SQRACE}({WORD}){L_CBRACE}{R_CBRACE}',
'newSeq[{1}]()',
]
def sub_overlap(pattern, repl, string, count=0, flags=0):
print("Replacing:")
print(f"{TAB}{pattern}")
print("With:")
print(f"{TAB}{repl}")
print("Captures:")
changed = True
replacements_made = 0
def replacement_func(m):
nonlocal changed, replacements_made
changed = True
replacements_made += 1
if len(m.groups()) > 0:
print(f"{TAB}{m.groups()}")
return m.expandf(repl)
while changed:
changed = False
string = regex.sub(pattern, replacement_func, string, count, flags)
if replacements_made == 0:
print("Warning: No replacements made.")
else:
print(f"Replacements made: {replacements_made}")
print()
return string
def main():
data = sys.stdin.read()
i = 0
while i < len(subs):
find = subs[i]
i += 1
replace = subs[i]
i += 1
try:
data = sub_overlap(find, replace, data, 0, regex.M + regex.V1)
except:
print()
print('\t', find)
print('\t', replace)
print()
raise
sys.stdout.write(data)
main()
# ## Structures ##
# # Slice Literals
# {L_SQBRACE}{R_SQBRACE} *{WORD} *{CBRACE_PAIR_2}
# ({LINE_START})func (.*) +\{{LINE_END}
# $1proc $2 =
# !(\w+)
# not $1
# fmt\.Println
# echo
# ^(\t*proc .+\))( *\w+) +={LINE_END}
# $1:$2 =
# (proc .+?)(?!if|or)(\b\w+) +(string|int|bool|error|func|byte)
# $1$2: $3
# ({LINE_START})type +(\w+) +struct +\{{LINE_END}
# $1type $2 = object
# ({LINE_START})for +\{{LINE_END}
# $1while true:
# ({LINE_START})(if|for) +([^\{]+) +\{{LINE_END}
# $1$2 $3:
# ({LINE_START})\} +else +\{{LINE_END}
# $1else:
# \[\](byte|string|char|int|bool)
# seq[$1]
# (^\t*)return ([^,\(#]+(, [^,\(#]+)+){LINE_END}
# $1return ($2)
# if err != nil:
# if err != "":
# :\]
# ..^1]
# \[:([^\]]+)
# [..$1-1
# case
# of
# ({LINE_START})switch(.*) +\{{LINE_END}
# $1case $2:
# ({LINE_START})(proc.*\)) *\((.*)\)
# $1$2: tuple[$3]
# ({LINE_START})var (\w+) (\w+){LINE_END}
# $1var $2: $3
# runtime\.GOOS
# OS
# string\(Separator\)
# $Separator
# ({LINE_START})\} else if(.*?) *\{$
# $1elif
# tuple\[(\w+), error\]
# tuple[r: $1, err: error]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment