all repos — min @ 8b406804d2dc12bc96ea734ad44efb96fe1cacba

A small but practical concatenative programming language.

Implemented REPL, arithmetic.
h3rald h3rald@h3rald.com
Sun, 23 Nov 2014 14:25:00 +0100
commit

8b406804d2dc12bc96ea734ad44efb96fe1cacba

parent

6482875ac5918f3b68f5d5c2c3de1ec1376cd469

5 files changed, 240 insertions(+), 71 deletions(-)

jump to
M interpreter.niminterpreter.nim

@@ -13,25 +13,24 @@ errParser,

errGeneric, errEmptyStack, errNoQuotation, - errUndefined + errUndefined, + errIncorrect, + errTwoNumbersRequired, + errDivisionByZero + const ERRORS: array [TMinError, string] = [ "A parsing error occurred", "A generic error occurred", "The stack is empty", "Quotation not found on the stack", - "Symbol undefined" + "Symbol undefined", + "Incorrect items on the stack", + "Two numbers are required on the stack", + "Division by zero" ] var SYMBOLS* = initTable[string, TMinOperator]() -var TYPES* = initTable[string, TMinKind]() - -TYPES["int"] = minInt -TYPES["float"] = minFloat -TYPES["string"] = minString -TYPES["symbol"] = minSymbol -TYPES["quotation"] = minQuotation - proc newMinInterpreter*(): TMinInterpreter = var s:TMinStack = newSeq[TMinValue](0)

@@ -45,13 +44,21 @@ stderr.writeln("$1[$2,$3] `$4`: Error - $5" %[i.filename, $i.currSym.line, $i.currSym.last, i.currSym.symVal, msg])

quit(int(status)) proc open*(i: var TMinInterpreter, stream:PStream, filename: string) = + i.filename = filename i.parser.open(stream, filename) proc close*(i: var TMinInterpreter) = i.parser.close(); proc push*(i: var TMinInterpreter, val: TMinValue) = - i.stack.add(val) + if val.kind == minSymbol: + i.currSym = val + if SYMBOLS.hasKey val.symVal: + SYMBOLS[val.symVal](i) + else: + i.error(errUndefined, "Undefined symbol: '"&val.symVal&"'") + else: + i.stack.add(val) proc pop*(i: var TMinInterpreter): TMinValue = if i.stack.len > 0:

@@ -65,6 +72,13 @@ return i.stack[i.stack.len-1]

else: i.error(errEmptyStack) +proc dump*(i: TMinInterpreter) = + stdout.write "[ " + for item in i.stack: + item.print + stdout.write " " + stdout.writeln "]" + proc interpret*(i: var TMinInterpreter) = var val: TMinValue while i.parser.token != tkEof:

@@ -72,12 +86,4 @@ try:

val = i.parser.parseMinValue except: i.error errParser, getCurrentExceptionMsg() - if val.kind == minSymbol: - i.currSym = val - if SYMBOLS.hasKey val.symVal: - SYMBOLS[val.symVal](i) - else: - i.error(errUndefined, "Undefined symbol: '"&val.symVal&"'") - else: - i.push val - + i.push val
M minim.nimminim.nim

@@ -12,29 +12,62 @@ Usage:

minim [options] [filename] Arguments: - filename A minim file to interpret. + filename A minim file to interpret (default: STDIN). Options: -e, --evaluate Evaluate a minim program inline -h, --help Print this help - -v, --version Print the program version""" + -v, --version Print the program version + -i, --interactive Starts MiNiM's Read Evel Print Loop""" -proc minimStream*(s: PStream, filename: string) = +proc minimStream(s: PStream, filename: string) = var i = newMinInterpreter() i.open(s, filename) discard i.parser.getToken() i.interpret() i.close() + +proc handleReplCtrlC() {.noconv.}= + echo "\n-> Exiting..." + quit(0) + proc minimString*(buffer: string) = minimStream(newStringStream(buffer), "input") proc minimFile*(filename: string) = var stream = newFileStream(filename, fmRead) if stream == nil: - writeln(stderr, "Error - Cannot read from file: "& filename) - flushFile(stderr) + stderr.writeln("Error - Cannot read from file: "& filename) + stderr.flushFile() minimStream(stream, filename) +proc minimFile*(file: TFile, filename="stdin") = + var stream = newFileStream(stdin) + if stream == nil: + stderr.writeln("Error - Cannot read from "& filename) + stderr.flushFile() + minimStream(stream, filename) + +proc minimRepl*() = + var i = newMinInterpreter() + var s = newStringStream("") + i.open(s, "repl") + setControlCHook(handleReplCtrlC) + echo "MiNiM v"&version&" - REPL initialized." + echo "-> Press Ctrl+C to exit." + var pos = 0 + var line: string + while true: + stdout.write(": ") + line = stdin.readLine() + s.writeln(line) + i.parser.buf = $i.parser.buf & line + i.parser.bufLen = i.parser.buf.len + discard i.parser.getToken() + i.interpret() + stdout.write "-> " + i.dump + ### var file, str: string = ""

@@ -51,12 +84,15 @@ of "help", "h":

echo usage of "version", "v": echo version + of "interactive", "i": + minimRepl() + quit(0) else: discard if str != "": minimString(str) elif file != "": - minimFile(file) + minimFile file else: - echo usage + minimFile stdin
M parser.nimparser.nim

@@ -22,8 +22,8 @@ first*: int

last*: int line*: int case kind*: TMinKind - of minInt: intVal*: BiggestInt - of minFloat: floatVal*: BiggestFloat + of minInt: intVal*: int + of minFloat: floatVal*: float of minQuotation: qVal*: seq[TMinValue] of minString: strVal*: string of minSymbol: symVal*: string

@@ -92,9 +92,9 @@

proc close*(my: var TMinParser) {.inline.} = lexbase.close(my) -proc getInt*(my: TMinParser): BiggestInt {.inline.} = +proc getInt*(my: TMinParser): int {.inline.} = assert(my.kind == eMinInt) - return parseBiggestInt(my.a) + return parseint(my.a) proc getFloat*(my: TMinParser): float {.inline.} = assert(my.kind == eMinFloat)

@@ -303,7 +303,16 @@ proc getToken*(my: var TMinParser): TMinTokenKind =

setLen(my.a, 0) skip(my) case my.buf[my.bufpos] - of '-', '.', '0'..'9': + of '-', '.': + if my.bufpos+1 <= my.buf.len and my.buf[my.bufpos+1] in '0'..'9': + parseNumber(my) + if {'.', 'e', 'E'} in my.a: + result = tkFloat + else: + result = tkInt + else: + result = parseSymbol(my) + of '0'..'9': parseNumber(my) if {'.', 'e', 'E'} in my.a: result = tkFloat

@@ -383,7 +392,7 @@ result = TMinValue(kind: minString, strVal: p.a, first: p.bufpos-p.a.len, last: p.bufpos, line: p.lineNumber)

p.a = "" discard getToken(p) of tkInt: - result = TMinValue(kind: minInt, intVal: parseBiggestInt(p.a), first: p.bufpos-p.a.len, last: p.bufpos, line: p.lineNumber) + result = TMinValue(kind: minInt, intVal: parseint(p.a), first: p.bufpos-p.a.len, last: p.bufpos, line: p.lineNumber) discard getToken(p) of tkFloat: result = TMinValue(kind: minFloat, floatVal: parseFloat(p.a), first: p.bufpos-p.a.len, last: p.bufpos, line: p.lineNumber)

@@ -402,3 +411,20 @@ discard getToken(p)

else: raiseUndefinedError(p, "Undefined value: '"&p.a&"'") +proc print*(a: TMinValue) = + case a.kind: + of minSymbol: + stdout.write a.symVal + of minString: + stdout.write "\""&a.strVal&"\"" + of minInt: + stdout.write a.intVal + of minFloat: + stdout.write a.floatVal + of minQuotation: + stdout.write "[ " + for i in a.qVal: + i.print + stdout.write " " + stdout.write "]" +
M primitives.nimprimitives.nim

@@ -1,11 +1,11 @@

import tables, strutils import parser, interpreter, utils -minsym "dup": - i.push i.peek - minsym "pop": discard i.pop + +minsym "dup": + i.push i.peek minsym "swap": let a = i.pop

@@ -17,6 +17,13 @@ minsym "quote":

let a = i.pop i.push TMinValue(kind: minQuotation, qVal: @[a]) +minsym "unquote": + let q = i.pop + if not q.isQuotation: + i.error errNoQuotation + for item in q.qVal: + i.push item + minsym "i": discard

@@ -25,23 +32,111 @@ let a = i.peek

a.print echo "" -minsym "def": +minsym "dump": + i.dump + +minsym "dip": + let q = i.pop + if not q.isQuotation: + i.error errNoQuotation + let v = i.pop + for item in q.qVal: + i.push item + i.push v + +minsym "cons": var q = i.pop - var v = i.pop - if q.qVal.len != 1 or q.qVal[0].kind != minSymbol: - i.error(errNoQuotation, "Definition quotation not found on the stack.") - if v.qVal.len != 1 or q.qVal[0].kind != minSymbol: - i.error(errNoQuotation, "Value quotation not found on the stack.") - let defname = q.qVal[0].symVal - let value = v.qVal[0] - case value.kind: - of minSymbol: - if SYMBOLS.hasKey value.symVal: - SYMBOLS[defname] = SYMBOLS[value.symVal] - else: - i.error(errUndefined, "Undefined symbol: '"&value.symVal&"'") + let v = i.pop + if not q.isQuotation: + i.error errNoQuotation + q.qVal.add v + i.push q + +minsym "concat": + var q1 = i.pop + var q2 = i.pop + if q1.isString and q2.isString: + let s = q2.strVal & q1.strVal + i.push newString(s) + elif q1.isQuotation and q2.isQuotation: + let q = q2.qVal & q1.qVal + i.push newQuotation(q) + else: + i.error(errIncorrect, "Two quotations or two strings required on the stack") + +minsym "+": + let a = i.pop + let b = i.pop + if a.isInt: + if b.isInt: + i.push newInt(a.intVal + b.intVal) + elif b.isFloat: + i.push newFloat(a.intVal.float + b.floatVal) + else: + i.error(errTwoNumbersRequired) + elif a.isFloat: + if b.isFloat: + i.push newFloat(a.floatVal + b.floatVal) + elif b.isInt: + i.push newFloat(a.floatVal + b.intVal.float) + else: + i.error(errTwoNumbersRequired) + +minsym "-": + let a = i.pop + let b = i.pop + if a.isInt: + if b.isInt: + i.push newInt(b.intVal - a.intVal) + elif b.isFloat: + i.push newFloat(b.floatVal - a.intVal.float) + else: + i.error(errTwoNumbersRequired) + elif a.isFloat: + if b.isFloat: + i.push newFloat(b.floatVal - a.floatVal) + elif b.isInt: + i.push newFloat(b.intVal.float - a.floatVal) + else: + i.error(errTwoNumbersRequired) + +minsym "*": + let a = i.pop + let b = i.pop + if a.isInt: + if b.isInt: + i.push newInt(a.intVal * b.intVal) + elif b.isFloat: + i.push newFloat(a.intVal.float * b.floatVal) + else: + i.error(errTwoNumbersRequired) + elif a.isFloat: + if b.isFloat: + i.push newFloat(a.floatVal * b.floatVal) + elif b.isInt: + i.push newFloat(a.floatVal * b.intVal.float) + else: + i.error(errTwoNumbersRequired) + +minsym "/": + let a = i.pop + let b = i.pop + if b.isInt and b.intVal == 0: + i.error errDivisionByZero + if a.isInt: + if b.isInt: + i.push newFloat(b.intVal / a.intVal) + elif b.isFloat: + i.push newFloat(b.floatVal / a.intVal.float) + else: + i.error(errTwoNumbersRequired) + elif a.isFloat: + if b.isFloat: + i.push newFloat(b.floatVal / a.floatVal) + elif b.isInt: + i.push newFloat(b.intVal.float / a.floatVal) else: - SYMBOLS[defname] = proc(i: var TMinInterpreter) = i.push value + i.error(errTwoNumbersRequired) -minalias ":", "def" -minalias "bind", "def" +minalias "&", "concat" +minalias "%", "print"
M utils.nimutils.nim

@@ -1,23 +1,6 @@

import tables, strutils import parser, interpreter -proc print*(a: TMinValue) = - case a.kind: - of minSymbol: - stdout.write a.symVal - of minString: - stdout.write "\""&a.strVal&"\"" - of minInt: - stdout.write a.intVal - of minFloat: - stdout.write a.floatVal - of minQuotation: - stdout.write "[ " - for i in a.qVal: - i.print - stdout.write " " - stdout.write "]" - template minsym*(name: string, body: stmt): stmt {.immediate.} = SYMBOLS[name] = proc (i: var TMinInterpreter) = body

@@ -25,6 +8,29 @@

proc minalias*(newname: string, oldname: string) = SYMBOLS[newname] = SYMBOLS[oldname] -proc isSymbol(s: TMinValue): bool = +proc isSymbol*(s: TMinValue): bool = return s.kind == minSymbol +proc isQuotation*(s: TMinValue): bool = + return s.kind == minQuotation + +proc isString*(s: TMinValue): bool = + return s.kind == minString + +proc isFloat*(s: TMinValue): bool = + return s.kind == minFloat + +proc isInt*(s: TMinValue): bool = + return s.kind == minInt + +proc newString*(s: string): TMinValue = + return TMinValue(kind: minString, strVal: s) + +proc newQuotation*(q: seq[TMinValue]): TMinValue = + return TMinValue(kind: minQuotation, qVal: q) + +proc newInt*(s: int): TMinValue = + return TMinValue(kind: minInt, intVal: s) + +proc newFloat*(s: float): TMinValue = + return TMinValue(kind: minFloat, floatVal: s)