all repos — min @ 4f0a90f14cab9bcd91700e306b5409d5c5b6a1cc

A small but practical concatenative programming language.

Moved line editing and regex to external packages.
h3rald h3rald@h3rald.com
Wed, 15 Feb 2017 19:55:51 +0100
commit

4f0a90f14cab9bcd91700e306b5409d5c5b6a1cc

parent

83084b03fcb8e473c6533dcb81ea477bbc614cfa

6 files changed, 9 insertions(+), 579 deletions(-)

jump to
D core/linedit.nim

@@ -1,499 +0,0 @@

-import - critbits, - terminal, - queues, - sequtils, - strutils, - os - -# getch/putch implementations -when defined(windows): - proc getchar*(): cint {.header: "<conio.h>", importc: "_getch".} - proc putchar*(c: cint): cint {.discardable, header: "<conio.h>", importc: "_putch".} - - proc termSetup*() = - discard - - proc termSave*(): string = - return "" - - proc termRestore*() = - discard -else: - import osproc - - proc termSetup*() = - discard execCmd "stty </dev/tty -icanon -echo -isig -iexten" - - proc termSave*(): string = - let res = execCmdEx "stty </dev/tty -g" - return res[0] - - let TERMSETTINGS* = termSave() - proc termRestore*() = - discard execCmd "stty </dev/tty " & TERMSETTINGS - - proc getchar*(): cint = - return stdin.readChar().ord.cint - - proc putchar*(c: cint) = - stdout.write(c.chr) - -# Types - -type - Key* = int - KeySeq* = seq[Key] - KeyCallback* = proc(ed: var LineEditor) - LineError* = ref Exception - LineEditorError* = ref Exception - LineEditorMode = enum - mdInsert - mdReplace - Line = object - text: string - position: int - LineHistory = object - file: string - tainted: bool - position: int - queue: Queue[string] - max: int - LineEditor* = object - completionCallback*: proc(ed: LineEditor): seq[string] - history: LineHistory - line: Line - mode: LineEditorMode - -# Internal Methods - -proc empty(line: Line): bool = - return line.text.len <= 0 - -proc full(line: Line): bool = - return line.position >= line.text.len - -proc first(line: Line): int = - if line.empty: - raise LineError(msg: "Line is empty!") - return 0 - -proc last(line: Line): int = - if line.empty: - raise LineError(msg: "Line is empty!") - return line.text.len-1 - -proc fromStart(line: Line): string = - if line.empty: - return "" - return line.text[line.first..line.position-1] - -proc toEnd(line: Line): string = - if line.empty: - return "" - return line.text[line.position..line.last] - -proc back*(ed: var LineEditor, n=1) = - if ed.line.position <= 0: - return - stdout.cursorBackward(n) - ed.line.position = ed.line.position - n - -proc forward*(ed: var LineEditor, n=1) = - if ed.line.full: - return - stdout.cursorForward(n) - ed.line.position += n - -proc `[]`( q: Queue[string], pos: int): string = - var c = 0 - for e in q.items: - if c == pos: - result = e - break - c.inc - -proc `[]=`( q: var Queue[string], pos: int, s: string) = - var c = 0 - for e in q.mitems: - if c == pos: - e = s - break - c.inc - -proc add(h: var LineHistory, s: string, force=false) = - if s == "" and not force: - return - if h.queue.len >= h.max: - discard h.queue.dequeue - if h.tainted: - h.queue[h.queue.len-1] = s - else: - h.queue.enqueue s - -proc previous(h: var LineHistory): string = - if h.queue.len == 0 or h.position <= 0: - return nil - h.position.dec - result = h.queue[h.position] - -proc next(h: var LineHistory): string = - if h.queue.len == 0 or h.position >= h.queue.len-1: - return nil - h.position.inc - result = h.queue[h.position] - -# Public API - -proc deletePrevious*(ed: var LineEditor) = - if ed.line.position <= 0: - return - if not ed.line.empty: - if ed.line.full: - stdout.cursorBackward - putchar(32) - stdout.cursorBackward - ed.line.position.dec - ed.line.text = ed.line.text[0..ed.line.last-1] - else: - let rest = ed.line.toEnd & " " - ed.back - for i in rest: - putchar i.ord - ed.line.text = ed.line.fromStart & ed.line.text[ed.line.position+1..ed.line.last] - stdout.cursorBackward(rest.len) - -proc deleteNext*(ed: var LineEditor) = - if not ed.line.empty: - if not ed.line.full: - let rest = ed.line.toEnd[1..^1] & " " - for c in rest: - putchar c.ord - stdout.cursorBackward(rest.len) - ed.line.text = ed.line.fromStart & ed.line.toEnd[1..^1] - -proc printChar*(ed: var LineEditor, c: int) = - if ed.line.full: - putchar(c.cint) - ed.line.text &= c.chr - ed.line.position += 1 - else: - if ed.mode == mdInsert: - putchar(c.cint) - let rest = ed.line.toEnd - ed.line.text.insert($c.chr, ed.line.position) - ed.line.position += 1 - for j in rest: - putchar(j.ord) - ed.line.position += 1 - ed.back(rest.len) - else: - putchar(c.cint) - ed.line.text[ed.line.position] = c.chr - ed.line.position += 1 - -proc changeLine*(ed: var LineEditor, s: string) = - let text = ed.line.text - let diff = text.len - s.len - let position = ed.line.position - if position > 0: - stdout.cursorBackward(position) - for c in s: - putchar(c.ord) - ed.line.position = s.len - ed.line.text = s - if diff > 0: - for i in 0.countup(diff-1): - putchar(32) - stdout.cursorBackward(diff) - -proc addToLineAtPosition(ed: var LineEditor, s: string) = - for c in s: - ed.printChar(c.ord) - -proc clearLine*(ed: var LineEditor) = - stdout.cursorBackward(ed.line.position+1) - for i in ed.line.text: - putchar(32) - putchar(32) - stdout.cursorBackward(ed.line.text.len) - ed.line.position = 0 - ed.line.text = "" - -proc goToStart*(ed: var LineEditor) = - stdout.cursorBackward(ed.line.position) - ed.line.position = 0 - -proc goToEnd*(ed: var LineEditor) = - let diff = ed.line.text.len - ed.line.position - stdout.cursorForward(diff) - ed.line.position = ed.line.text.len - -proc historyInit*(size = 256, historyFile: string = nil): LineHistory = - result.file = historyFile - result.queue = initQueue[string](size) - result.position = 0 - result.tainted = false - result.max = size - if historyFile.isNil: - return - if result.file.fileExists: - let lines = result.file.readFile.split("\n") - for line in lines: - if line != "": - result.add line - result.position = lines.len - else: - result.file.writeFile("") - -proc historyAdd*(ed: var LineEditor, force = false) = - ed.history.add ed.line.text, force - if ed.history.file.isNil: - return - ed.history.file.writeFile(toSeq(ed.history.queue.items).join("\n")) - -proc historyPrevious*(ed: var LineEditor) = - let s = ed.history.previous - if s.isNil: - return - let pos = ed.history.position - var current: int - if ed.history.tainted: - current = ed.history.queue.len-2 - else: - current = ed.history.queue.len-1 - if pos == current and ed.history.queue[current] != ed.line.text: - ed.historyAdd(force = true) - ed.history.tainted = true - if s != "": - ed.changeLine(s) - -proc historyNext*(ed: var LineEditor) = - let s = ed.history.next - if s.isNil: - return - ed.changeLine(s) - -proc historyFlush*(ed: var LineEditor) = - if ed.history.queue.len > 0: - ed.history.position = ed.history.queue.len - ed.history.tainted = false - -proc completeLine*(ed: var LineEditor): int = - if ed.completionCallback.isNil: - raise LineEditorError(msg: "Completion callback is not set") - let compl = ed.completionCallback(ed) - let position = ed.line.position - let words = ed.line.fromStart.split(" ") - var word: string - if words.len > 0: - word = words[words.len-1] - else: - word = ed.line.fromStart - var matches = compl.filterIt(it.toLowerAscii.startsWith(word.toLowerAscii)) - if ed.line.fromStart.len > 0 and matches.len > 0: - for i in 0..word.len-1: - ed.deletePrevious - var n = 0 - if matches.len > 0: - ed.addToLineAtPosition(matches[0]) - else: - return -1 - var ch = getchar() - while ch == 9: - n.inc - if n < matches.len: - let diff = ed.line.position - position - for i in 0.countup(diff-1 + word.len): - ed.deletePrevious - ed.addToLineAtPosition(matches[n]) - ch = getchar() - else: - n = -1 - return ch - -proc lineText*(ed: LineEditor): string = - return ed.line.text - -proc initEditor*(mode = mdInsert, historySize = 256, historyFile: string = nil): LineEditor = - termSetup() - result.mode = mode - result.history = historyInit(historySize, historyFile) - -# Character sets -const - CTRL* = {0 .. 31} - DIGIT* = {48 .. 57} - LETTER* = {65 .. 122} - UPPERLETTER* = {65 .. 90} - LOWERLETTER* = {97 .. 122} - PRINTABLE* = {32 .. 126} -when defined(windows): - const - ESCAPES* = {0, 22, 224} -else: - const - ESCAPES* = {27} - - -# Key Mappings -var KEYMAP*: CritBitTree[KeyCallBack] - -KEYMAP["backspace"] = proc(ed: var LineEditor) = - ed.deletePrevious() -KEYMAP["delete"] = proc(ed: var LineEditor) = - ed.deleteNext() -KEYMAP["insert"] = proc(ed: var LineEditor) = - if ed.mode == mdInsert: - ed.mode = mdReplace - else: - ed.mode = mdInsert -KEYMAP["down"] = proc(ed: var LineEditor) = - ed.historyNext() -KEYMAP["up"] = proc(ed: var LineEditor) = - ed.historyPrevious() -KEYMAP["left"] = proc(ed: var LineEditor) = - ed.back() -KEYMAP["right"] = proc(ed: var LineEditor) = - ed.forward() -KEYMAP["ctrl+c"] = proc(ed: var LineEditor) = - termRestore() - quit(0) -KEYMAP["ctrl+x"] = proc(ed: var LineEditor) = - ed.clearLine() -KEYMAP["ctrl+b"] = proc(ed: var LineEditor) = - ed.goToStart() -KEYMAP["ctrl+e"] = proc(ed: var LineEditor) = - ed.goToEnd() - -# Key Names -var KEYNAMES*: array[0..31, string] -KEYNAMES[1] = "ctrl+a" -KEYNAMES[2] = "ctrl+b" -KEYNAMES[3] = "ctrl+c" -KEYNAMES[4] = "ctrl+d" -KEYNAMES[5] = "ctrl+e" -KEYNAMES[6] = "ctrl+f" -KEYNAMES[7] = "ctrl+g" -KEYNAMES[8] = "ctrl+h" -KEYNAMES[9] = "ctrl+i" -KEYNAMES[9] = "tab" -KEYNAMES[10] = "ctrl+j" -KEYNAMES[11] = "ctrl+k" -KEYNAMES[12] = "ctrl+l" -KEYNAMES[13] = "ctrl+m" -KEYNAMES[14] = "ctrl+n" -KEYNAMES[15] = "ctrl+o" -KEYNAMES[16] = "ctrl+p" -KEYNAMES[17] = "ctrl+q" -KEYNAMES[18] = "ctrl+r" -KEYNAMES[19] = "ctrl+s" -KEYNAMES[20] = "ctrl+t" -KEYNAMES[21] = "ctrl+u" -KEYNAMES[22] = "ctrl+v" -KEYNAMES[23] = "ctrl+w" -KEYNAMES[24] = "ctrl+x" -KEYNAMES[25] = "ctrl+y" -KEYNAMES[26] = "ctrl+z" - -# Key Sequences -var KEYSEQS*: CritBitTree[KeySeq] - -when defined(windows): - KEYSEQS["up"] = @[224, 72] - KEYSEQS["down"] = @[224, 80] - KEYSEQS["right"] = @[224, 77] - KEYSEQS["left"] = @[224, 75] - KEYSEQS["insert"] = @[224, 82] - KEYSEQS["delete"] = @[224, 83] -else: - KEYSEQS["up"] = @[27, 91, 65] - KEYSEQS["down"] = @[27, 91, 66] - KEYSEQS["right"] = @[27, 91, 67] - KEYSEQS["left"] = @[27, 91, 68] - KEYSEQS["insert"] = @[27, 91, 50, 126] - KEYSEQS["delete"] = @[27, 91, 51, 126] - - -proc readLine*(ed: var LineEditor, prompt="", hidechars = false): string = - stdout.write(prompt) - ed.line = Line(text: "", position: 0) - var c = -1 # Used to manage completions - while true: - var c1: int - if c > 0: - c1 = c - c = -1 - else: - c1 = getchar() - if c1 in {10, 13}: - stdout.write("\n") - ed.historyAdd() - ed.historyFlush() - return ed.line.text - elif c1 in {8, 127}: - KEYMAP["backspace"](ed) - elif c1 in PRINTABLE: - if hidechars: - putchar('*'.ord) - ed.line.text &= c1.chr - ed.line.position.inc - else: - ed.printChar(c1) - elif c1 == 9: # TAB - c = ed.completeLine() - elif c1 in ESCAPES: - var s = newSeq[Key](0) - s.add(c1) - let c2 = getchar() - s.add(c2) - if s == KEYSEQS["left"]: - KEYMAP["left"](ed) - elif s == KEYSEQS["right"]: - KEYMAP["right"](ed) - elif s == KEYSEQS["up"]: - KEYMAP["up"](ed) - elif s == KEYSEQS["down"]: - KEYMAP["down"](ed) - elif s == KEYSEQS["delete"]: - KEYMAP["delete"](ed) - elif s == KEYSEQS["insert"]: - KEYMAP["insert"](ed) - elif c2 == 91: - let c3 = getchar() - s.add(c3) - if s == KEYSEQS["right"]: - KEYMAP["right"](ed) - elif s == KEYSEQS["left"]: - KEYMAP["left"](ed) - elif s == KEYSEQS["up"]: - KEYMAP["up"](ed) - elif s == KEYSEQS["down"]: - KEYMAP["down"](ed) - elif c3 in {50, 51}: - let c4 = getchar() - s.add(c4) - if c4 == 126 and c3 == 50: - KEYMAP["insert"](ed) - elif c4 == 126 and c3 == 51: - KEYMAP["delete"](ed) - elif KEYMAP.hasKey(KEYNAMES[c1]): - KEYMAP[KEYNAMES[c1]](ed) - -proc password*(ed: var LineEditor, prompt=""): string = - return ed.readLine(prompt, true) - -when isMainModule: - proc testChar() = - while true: - let a = getch().ord - echo "\n->", a - if a == 3: - termRestore() - quit(0) - proc testLineEditor() = - while true: - var ed = initEditor(historyFile = nil) - echo "---", ed.readLine("-> "), "---" - - testChar()
D core/regex.nim

@@ -1,77 +0,0 @@

-import strutils -import ../vendor/sgregex - -type - InvalidRegexError = ref SystemError - -proc newRegex(pattern, mods: string): ptr srx_Context = - result = srx_Create(pattern, mods) - if result.isNil: - raise(InvalidRegexError(msg: "Invalid regular expression: \"$1\"" % pattern)) - -proc match*(str, pattern, mods: string): bool = - let r = newRegex(pattern, mods) - result = srx_Match(r, str, 0) == 1 - discard srx_Destroy(r) - -proc match*(str, pattern: string): bool = - return match(str, pattern, "") - -proc search*(str, pattern, mods: string): seq[string] = - let r = newRegex(pattern, mods) - discard srx_Match(r, str, 0) == 1 - let count = srx_GetCaptureCount(r) - result = newSeq[string](count) - for i in 0..count-1: - var first = 0 - var last = 0 - discard srx_GetCaptured(r, i, addr first, addr last) - result[i] = str.substr(first, last-1) - discard srx_Destroy(r) - -proc search*(str, pattern: string): seq[string] = - return search(str, pattern, "") - -proc replace*(str, pattern, repl, mods: string): string = - var r = newRegex(pattern, mods) - result = $srx_Replace(r, str, repl) - discard srx_Destroy(r) - -proc replace*(str, pattern, repl: string): string = - return replace(str, pattern, repl, "") - -proc `=~`*(str, r: string): seq[string] = - let m = r.search("(s)?/(.+?)/((.+?)/)?([mis]{0,3})?") - # full match, s, reg, replace/, replace, flags - if m[1] == "s" and m[3] != "": - return @[replace(str, m[2], m[4], m[5])] - else: - return search(str, m[2], m[5]) - -when isMainModule: - - proc tmatch(str, pattern: string) = - echo str, " =~ ", "/", pattern, "/", " -> ", str.match(pattern) - - proc tsearch(str, pattern: string) = - echo str, " =~ ", "/", pattern, "/", " -> ", str.search(pattern) - - proc tsearch(str, pattern, mods: string) = - echo str, " =~ ", "/", pattern, "/", mods, " -> ", str.search(pattern, mods) - - proc treplace(str, pattern, repl: string) = - echo str, " =~ ", "s/", pattern, "/", repl, "/", " -> ", str.replace(pattern, repl) - - proc toperator(str, pattern: string) = - echo str, " =~ ", pattern, " -> ", str =~ pattern - - "HELLO".tmatch("^H(.*)O$") - "HELLO".tmatch("^H(.*)S$") - "HELLO".tsearch("^H(E)(.*)O$") - "Hello, World!".treplace("[a-zA-Z]+,", "Goodbye,") - "127.0.0.1".tsearch("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$") - "127.0.0.1".treplace("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$", "$4.$3.$1.$2") - "127.0.0.1".treplace("[0-9]+", "255") - "Hello".tsearch("HELLO", "i") - "Hello\nWorld!".tsearch("HELLO.WORLD", "mis") - "Testing".toperator("s/test/eat/i")
M lib/min_io.nimlib/min_io.nim

@@ -3,7 +3,7 @@ os,

strutils, logging import - ../core/linedit, + ../packages/nimline/nimline, ../packages/nim-sgregex/sgregex, ../core/parser, ../core/value,
M lib/min_lang.nimlib/min_lang.nim

@@ -13,7 +13,7 @@ ../core/value,

../core/interpreter, ../core/utils, ../packages/nim-sgregex/sgregex, - ../core/linedit, + ../packages/nimline/nimline, ../core/scope # Dictionary Methods
M minim.nimminim.nim

@@ -9,7 +9,7 @@ sequtils,

algorithm, logging import - core/linedit, + packages/nimline/nimline, core/consts, core/parser, core/value,
M nifty.jsonnifty.json

@@ -53,6 +53,12 @@ {

"name": "sha1", "src": "https://github.com/onionhammer/sha1.git", "git": true + }, + "nimline": + { + "name": "nimline", + "src": "https://github.com/h3rald/nimline.git", + "git": true } } }