diff --git a/0-lang0py/lang0py.py b/0-lang0py/lang0py.py index 7de85d1..0ed69f7 100644 --- a/0-lang0py/lang0py.py +++ b/0-lang0py/lang0py.py @@ -1,6 +1,9 @@ import os import sys +def eq(a, b): + return a == b + def emit(string): sys.stdout.write(string) sys.stdout.flush() @@ -39,6 +42,46 @@ def skip(): PEEK = None +def __check(func, args: list[str], msg: list[str]) -> None: + result = func(*args) + if result: + return + + sys.stderr.write(' '.join(msg) + '\n') + sys.stderr.flush() + exit(1) + +def intinc(a: str) -> str: + if not a: + return "1" + + return str(int(a) + 1) + +MAPSTORE = {} + +def mapclear(mapname: str) -> str: + MAPSTORE.pop(mapname) + return "" + +def mapgetkey(mapname: str, key: str, default: str) -> str: + return MAPSTORE.get(mapname, {}).get(key, default) + +def mapsetkey(mapname: str, key: str, value: str) -> str: + MAPSTORE.setdefault(mapname, {}) + MAPSTORE[mapname][key] = value + return "" + +def registerid(id: str) -> str: + idname = mapgetkey("REGISTERID", id, "") + if idname: + return idname + newidx = mapgetkey("REGISTERID", "__len__", "0") + idname = "identifier" + newidx + mapsetkey("REGISTERID", id, idname) + newlen = intinc(newidx) + mapsetkey("REGISTERID", "__len__", newlen) + return idname + def emitln(data): emit(data) emit(eol) @@ -81,12 +124,15 @@ def parseconststring(): emit(quote) def parseexprvarref(): - var = lexident() - emit(var) + varname = lexident() + varid = registerid(varname) + emit(varid) def parseexprcall(): funcname = lexident() - emit(funcname) + __check(mapgetkey, ["FUNCREG", funcname, ""], ["Function", funcname, "does not exist"]) + funcid = registerid(funcname) + emit(funcid) emit('(') first = True @@ -109,11 +155,12 @@ def parsestatdeclare(indent): def parsestatset(indent): skipchar(' ') - var = lexident() + varname = lexident() + varid = registerid(varname) skipchar(' ') emit(' ' * indent) - emit(var) + emit(varid) emit(' = ') parseconststring() @@ -123,11 +170,12 @@ def parsestatset(indent): def parsestatcalc(indent): skipchar(' ') - var = lexident() + varname = lexident() + varid = registerid(varname) skipchar(' ') emit(' ' * indent) - emit(var) + emit(varid) emit(' = ') parseexprcall() @@ -172,11 +220,13 @@ def parsestatreturn(indent): def parsestatcheck(indent): skipchar(' ') emit(' ' * indent) - emit('assert ') + emit('__check(') - func_name = lexident() - emit(func_name) - emit('(') + funcname = lexident() + funcid = registerid(funcname) + trace("funcid", funcid) + emit(funcid) + emit(', [') notfirst = False while True: @@ -197,7 +247,7 @@ def parsestatcheck(indent): skipchar(':') - emit('), (') + emit('], [') notfirst = False while True: @@ -214,19 +264,20 @@ def parsestatcheck(indent): if eol == peek(): break notfirst = True - emitln(')') + emitln('])') def parsestattrace(indent): skipchar(' ') emit(' ' * indent) emit('trace("') - var_name = lexident() + varname = lexident() + varid = registerid(varname) skipchar(eol) - emit(var_name) + emit(varname) emit('", ') - emit(var_name) + emit(varid) emitln(')') def parsestat(indent): @@ -269,8 +320,10 @@ def parsestat(indent): parsestattrace(indent) return + funcid = registerid(call) + emit(' ' * indent) - emit(call) + emit(funcid) emit('(') first = True @@ -307,19 +360,22 @@ def parseblock(indent): def parsefunc(): funcname = lexident() + funcid = registerid(funcname) + mapsetkey("FUNCREG", funcname, "1") trace('funcname', funcname) emit('def ') - emit(funcname) + emit(funcid) emit('(') first = True while ' ' == peek(): skip() - var = lexident() + varname = lexident() + varid = registerid(varname) if not first: emit(", ") - emit(var) + emit(varid) first = False if '/' == peek(): @@ -334,7 +390,8 @@ def parsefunc(): skipchar(':') skipchar(eol) - emitln('):') + emit('): # ') + emitln(funcname) parseblock(1) @@ -344,16 +401,16 @@ def emitheader(): emitln("import os") emitln("import sys") emitln("") - emitln("def eq(a, b):") + emitln("def __eq(a, b):") emitln(" return a == b") emitln("") - emitln("def lt(a, b):") + emitln("def __lt(a, b):") emitln(" return a[0] < b[0]") emitln("") - emitln("def addstringchar(a, b):") + emitln("def __addstringchar(a, b):") emitln(" return a + b[0]") emitln("") - emitln("def emit(string):") + emitln("def __emit(string):") emitln(" sys.stdout.write(string)") emitln(" sys.stdout.flush()") emitln("") @@ -361,9 +418,9 @@ def emitheader(): emitln(" if os.environ.get('TRACE'):") emitln(" sys.stderr.write(f'{header}={value!r}\\n')") emitln("") - emitln("eof = chr(0)") - emitln("eol = chr(10)") - emitln("quote = chr(34)") + emitln("__EOF = chr(0)") + emitln("__EOL = chr(10)") + emitln("__QUOTE = chr(34)") emitln("") emitln("STDINCOLNO = 0") emitln("STDINLINENO = 1") @@ -373,39 +430,98 @@ def emitheader(): emitln(" char = sys.stdin.read(1)") emitln(" trace('char', char)") emitln(" if not char:") - emitln(" return eof") + emitln(" return __EOF") emitln(" return char") emitln("") - emitln("def peek():") + emitln("def __peek():") emitln(" return STDINPEEK") emitln("") - emitln("def skip():") + emitln("def __skip():") emitln(" global STDINCOLNO") emitln(" global STDINLINENO") emitln(" global STDINPEEK") - emitln(" if eol == STDINPEEK:") + emitln(" if __EOL == STDINPEEK:") emitln(" STDINLINENO += 1") emitln(" STDINCOLNO = 0") emitln(" STDINCOLNO += 1") emitln(" STDINPEEK = _readchar()") emitln("") - emitln("def stdinlineno():") + emitln("def __stdinlineno():") emitln(" global STDINLINENO") emitln(" return str(STDINLINENO)") emitln("") - emitln("def stdincolno():") + emitln("def __stdincolno():") emitln(" global STDINCOLNO") emitln(" return str(STDINCOLNO)") emitln("") - emitln("skip()") + emitln("__skip()") emitln("") + emitln("MAPSTORE = {}") + emitln("") + emitln("def mapclear(mapname: str) -> str:") + emitln(" MAPSTORE.pop(mapname)") + emitln(" return ''") + emitln("") + emitln("def mapgetkey(mapname: str, key: str, default: str) -> str:") + emitln(" return MAPSTORE.get(mapname, {}).get(key, default)") + emitln("") + emitln("def mapsetkey(mapname: str, key: str, value: str) -> str:") + emitln(" MAPSTORE.setdefault(mapname, {})") + emitln(" MAPSTORE[mapname][key] = value") + emitln(" return ''") + emitln("") + emitln("def __not(a: str) -> str:") + emitln(" if a:") + emitln(" return ''") + emitln(" return '1'") + emitln("") + emitln("def __check(func, args: list[str], msg: list[str]) -> None:") + emitln(" result = func(*args)") + emitln(" if result:") + emitln(" return") + emitln("") + emitln(" sys.stderr.write(' '.join(msg) + '\\n')") + emitln(" sys.stderr.flush()") + emitln(" exit(1)") def emitfooter(): - emit("if __name__ == '__main__':\n") - emit(" main()\n") + mainname = registerid("main") + emitln("if __name__ == '__main__':") + emit(" ") + emit(mainname) + emitln("()") def main(): emitheader() + + # Standard library constants + mapsetkey("REGISTERID", "eof", "__EOF") + mapsetkey("REGISTERID", "eol", "__EOL") + mapsetkey("REGISTERID", "quote", "__QUOTE") + + # Standard library functions + mapsetkey("FUNCREG", "addstringchar", "1") + mapsetkey("REGISTERID", "addstringchar", "__addstringchar") + mapsetkey("FUNCREG", "emit", "1") + mapsetkey("REGISTERID", "emit", "__emit") + mapsetkey("FUNCREG", "eq", "1") + mapsetkey("REGISTERID", "eq", "__eq") + mapsetkey("FUNCREG", "lt", "1") + mapsetkey("REGISTERID", "lt", "__lt") + mapsetkey("FUNCREG", "not", "1") + mapsetkey("REGISTERID", "not", "__not") + mapsetkey("FUNCREG", "mapclear", "1") + mapsetkey("FUNCREG", "mapgetkey", "1") + mapsetkey("FUNCREG", "mapsetkey", "1") + mapsetkey("FUNCREG", "peek", "1") + mapsetkey("REGISTERID", "peek", "__peek") + mapsetkey("FUNCREG", "skip", "1") + mapsetkey("REGISTERID", "skip", "__skip") + mapsetkey("FUNCREG", "stdincolno", "1") + mapsetkey("REGISTERID", "stdincolno", "__stdincolno") + mapsetkey("FUNCREG", "stdinlineno", "1") + mapsetkey("REGISTERID", "stdinlineno", "__stdinlineno") + while True: if eof == peek(): break diff --git a/1-lang0py/Makefile b/1-lang0py/Makefile index ad189bd..e96fdf5 100644 --- a/1-lang0py/Makefile +++ b/1-lang0py/Makefile @@ -36,5 +36,8 @@ lang0py.py: lang0py.lang0 lang0py2.exe mv $@.tmp $@ -diff lang0py2.py lang0py.py +$(LANG0PY): $(CURDIR)/../0-lang0py/lang0py.py $(CURDIR)/../0-lang0py/lang0py.lang0 + cd ../0-lang0py && make lang0py.exe + clean: rm -f lang0py*.py lang0py*.c lang0py*.o lang0py*.exe diff --git a/README.md b/README.md index 1bee0d4..396e783 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,20 @@ Calls the given function with the given arguments. If the function's return valu Writes the name and value of the variable passed to stderr if the TRACE environment variable is set. +### Standard library constants + +#### eof + +End of file character, zero byte. + +#### eol + +End of line character. + +#### quote + +Double quote character. + ### Standard library functions #### addstringchar a b @@ -143,6 +157,29 @@ Return true if the given strings are the same. Return true if a would sort before b. +#### mapclear mapname + +Maps are global and can be used from any function. + +Clears all values set in the map named `mapname`. + +#### mapgetkey mapname key + +Maps are global and can be used from any function. + +Looks up `key` in the map named `mapname` and returns it. +If not found, returns an empty string. + +#### mapsetkey mapname key value + +Maps are global and can be used from any function. + +Adds a mapping from `key` to `value` to the map named `mapname`. + +#### not a + +Returns "1" if a is empty; otherwise, returns "". + #### peek Checks stdin for the next character and returns it. diff --git a/tests/Makefile b/tests/Makefile index 47dea90..c35e2a3 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -9,7 +9,7 @@ CYTHON=cython3 CC=gcc # builtincheckfalse is separate since it's supposed to crash. It's a test for the test 'framework', if you will. -TESTLIST=$(shell ls *.lang0 | grep -v 'builtincheckfalse' | sed 's/.lang0//') +TESTLIST=$(shell ls *.lang0 | grep -v -e 'builtincheckfalse' -e 'funcnotexists' | sed 's/.lang0//') all: check @@ -18,17 +18,20 @@ check: all-it0.results all-it1.results all-it2.results all-it0.results: $(addprefix build/,$(addsuffix .it0, $(TESTLIST))) build/builtincheckfalse.it0 -rm -f $@ + (cat funcnotexists.lang0 | ../0-lang0py/lang0py.exe 2>&1 1>/dev/null || true) | grep 'Function forgottodeclare does not exist' cat test-input.txt | build/builtincheckfalse.it0 2> /dev/null 2>&1 | grep 'Success' $(foreach test,$(TESTLIST), echo -n "$(test) " >> $@; cat test-input.txt | ./build/$(test).it0 >> $@ ; echo "" >> $@ ;) all-it1.results: $(addprefix build/,$(addsuffix .it1, $(TESTLIST))) build/builtincheckfalse.it1 -rm -f $@ cat test-input.txt | build/builtincheckfalse.it1 2> /dev/null 2>&1 | grep 'Success' + (cat funcnotexists.lang0 | ../1-lang0py/lang0py.exe 2>&1 1>/dev/null || true) | grep 'Function forgottodeclare does not exist' $(foreach test,$(TESTLIST), echo -n "$(test) " >> $@; cat test-input.txt | ./build/$(test).it1 >> $@ ; echo "" >> $@ ;) all-it2.results: $(addprefix build/,$(addsuffix .it2, $(TESTLIST))) build/builtincheckfalse.it2 -rm -f $@ cat test-input.txt | build/builtincheckfalse.it2 2> /dev/null 2>&1 | grep 'Success' + (cat funcnotexists.lang0 | ../2-lang0py/lang0c.exe 2>&1 1>/dev/null || true) | grep 'Function forgottodeclare does not exist' $(foreach test,$(TESTLIST), echo -n "$(test) " >> $@; cat test-input.txt | ./build/$(test).it2 >> $@ ; echo "" >> $@ ;) clean: diff --git a/tests/funcnotexists.lang0 b/tests/funcnotexists.lang0 new file mode 100644 index 0000000..30b302f --- /dev/null +++ b/tests/funcnotexists.lang0 @@ -0,0 +1,4 @@ +main: + declare foo + calc foo forgottodeclare "1" "2" +/