From 3c588789afaddc9b499ae86577e3af8a5e98721c Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 8 Jan 2024 21:30:19 -0500 Subject: [PATCH 01/14] wip --- scrapscript.py | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/scrapscript.py b/scrapscript.py index b63d918a..84bc3283 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -1254,6 +1254,55 @@ def bencode(obj: object) -> bytes: raise NotImplementedError(f"bencode not implemented for {type(obj)}") +class JSCompiler: + def __init__(self): + self.code: typing.List(str) = [] + + def compile(self, env: Env, exp: Object) -> str: + if isinstance(exp, Int): + return str(exp.value) + if isinstance(exp, Binop): + left = self.compile(env, exp.left) + right = self.compile(env, exp.right) + return f"({left})" + BinopKind.to_str(exp.op) + f"({right})" + if isinstance(exp, Var): + # assert exp.name in env + return exp.name + if isinstance(exp, Where): + binding = self.compile(env, exp.binding) + self.code.append(binding) + return self.compile(env, exp.body) + if isinstance(exp, Assign): + value = self.compile(env, exp.value) + self.code.append(f"const {exp.name.name} = {value};") + return "" + if isinstance(exp, Apply): + func = self.compile(env, exp.func) + arg = self.compile(env, exp.arg) + return f"({func})({arg})" + if isinstance(exp, Function): + arg = self.compile(env, exp.arg) + body = self.compile(env, exp.body) + return f"({arg}) => ({body})" + if isinstance(exp, List): + items = [self.compile(env, item) for item in exp.items] + return "[" + ", ".join(items) + "]" + if isinstance(exp, MatchFunction): + for case in exp.cases: + self.code.append(self.compile_match(exp.arg, case.pattern)) + return "" + raise NotImplementedError(exp) + + def compile_match(self, arg: Object, case: MatchCase) -> str: + pass + + +def compile_exp_js(env: Env, exp: Object) -> str: + compiler = JSCompiler() + result = compiler.compile(env, exp) + return "\n".join(compiler.code) + result + + class Bdecoder: def __init__(self, msg: str) -> None: self.msg: str = msg @@ -4158,6 +4207,52 @@ def test_pretty_print_symbol(self) -> None: self.assertEqual(str(obj), "#x") +class JSCompilerTests(unittest.TestCase): + def test_compile_int(self) -> None: + exp = Int(123) + self.assertEqual(compile_exp_js({}, exp), "123") + + def test_compile_binop_add(self) -> None: + exp = Binop(BinopKind.ADD, Int(3), Int(4)) + self.assertEqual(compile_exp_js({}, exp), "(3)+(4)") + + def test_compile_binop_rec(self) -> None: + exp = Binop(BinopKind.MUL, Binop(BinopKind.ADD, Int(3), Int(4)), Int(5)) + self.assertEqual(compile_exp_js({}, exp), "((3)+(4))*(5)") + + def test_compile_where(self) -> None: + exp = Where(Var("x"), Assign(Var("x"), Int(1))) + self.assertEqual(compile_exp_js({}, exp), "const x = 1;\nx") + + def test_compile_nested_where(self) -> None: + exp = parse(tokenize("x + y . x = 1 . y = 2")) + self.assertEqual(compile_exp_js({}, exp), "const y = 2;\n\nconst x = 1;\n(x)+(y)") + + def test_compile_apply(self) -> None: + exp = Apply(Var("f"), Var("x")) + self.assertEqual(compile_exp_js({}, exp), "(f)(x)") + + def test_compile_apply_nested(self) -> None: + exp = Apply(Apply(Var("f"), Var("x")), Var("y")) + self.assertEqual(compile_exp_js({}, exp), "((f)(x))(y)") + + def test_compile_function(self) -> None: + exp = Function(Var("x"), Binop(BinopKind.ADD, Var("x"), Int(1))) + self.assertEqual(compile_exp_js({}, exp), "(x) => ((x)+(1))") + + def test_compile_function_nested(self) -> None: + exp = parse(tokenize("x -> y -> x + y")) + self.assertEqual(compile_exp_js({}, exp), "(x) => ((y) => ((x)+(y)))") + + def test_compile_list(self) -> None: + exp = List([Binop(BinopKind.ADD, Int(1), Int(2)), Binop(BinopKind.MUL, Int(3), Int(4))]) + self.assertEqual(compile_exp_js({}, exp), "[(1)+(2), (3)*(4)]") + + def test_compile_match_function(self) -> None: + exp = parse(tokenize("| 1 -> 2 | 2 -> 3 | _ -> 100")) + self.assertEqual(compile_exp_js({}, exp), None) + + def fetch(url: Object) -> Object: if not isinstance(url, String): raise TypeError(f"fetch expected String, but got {type(url).__name__}") From 6d829151afa3610e99900ad0a9e7092187d78403 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 10 Jan 2024 00:13:05 -0500 Subject: [PATCH 02/14] Support pattern match on int and var --- scrapscript.py | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/scrapscript.py b/scrapscript.py index 84bc3283..0a6c00ae 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -1255,8 +1255,8 @@ def bencode(obj: object) -> bytes: class JSCompiler: - def __init__(self): - self.code: typing.List(str) = [] + def __init__(self) -> None: + self.code: typing.List[str] = [] def compile(self, env: Env, exp: Object) -> str: if isinstance(exp, Int): @@ -1288,13 +1288,24 @@ def compile(self, env: Env, exp: Object) -> str: items = [self.compile(env, item) for item in exp.items] return "[" + ", ".join(items) + "]" if isinstance(exp, MatchFunction): + # TODO(max): Gensym arg name or something + arg = "__x" + result = f"({arg}) => {{\n" for case in exp.cases: - self.code.append(self.compile_match(exp.arg, case.pattern)) - return "" + cond, body = self.compile_match_case(env, arg, case) + result += f"if ({cond}) {{ {body} }}\n" + return result + "}" raise NotImplementedError(exp) - def compile_match(self, arg: Object, case: MatchCase) -> str: - pass + def compile_match_case(self, env: Env, arg: str, case: MatchCase) -> Tuple[str, str]: + pattern = case.pattern + body = case.body + ret: Callable[[str], str] = lambda body: f"return {body};" + if isinstance(pattern, Int): + return f"{arg} === {pattern.value}", ret(self.compile(env, body)) + if isinstance(pattern, Var): + return "true", f"const {pattern.name} = {arg}; " + ret(self.compile(env, body)) + raise NotImplementedError(type(pattern)) def compile_exp_js(env: Env, exp: Object) -> str: @@ -4249,8 +4260,17 @@ def test_compile_list(self) -> None: self.assertEqual(compile_exp_js({}, exp), "[(1)+(2), (3)*(4)]") def test_compile_match_function(self) -> None: - exp = parse(tokenize("| 1 -> 2 | 2 -> 3 | _ -> 100")) - self.assertEqual(compile_exp_js({}, exp), None) + exp = parse(tokenize("| 1 -> 2 | 2 -> 3")) + self.assertEqual( + compile_exp_js({}, exp), "(__x) => {\nif (__x === 1) { return 2; }\nif (__x === 2) { return 3; }\n}" + ) + + def test_compile_match_function_var(self) -> None: + exp = parse(tokenize("| 1 -> 2 | x -> x")) + self.assertEqual( + compile_exp_js({}, exp), + "(__x) => {\nif (__x === 1) { return 2; }\nif (true) { const x = __x; return x; }\n}", + ) def fetch(url: Object) -> Object: From 5da9bdb7432e24a284324db21b4441de34e168dc Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 10 Jan 2024 11:48:20 -0500 Subject: [PATCH 03/14] Compile symbols --- scrapscript.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scrapscript.py b/scrapscript.py index 0a6c00ae..1a17ec30 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -1295,6 +1295,10 @@ def compile(self, env: Env, exp: Object) -> str: cond, body = self.compile_match_case(env, arg, case) result += f"if ({cond}) {{ {body} }}\n" return result + "}" + if isinstance(exp, Symbol): + if exp.value in ("true", "false"): + return exp.value + return repr(exp.value) raise NotImplementedError(exp) def compile_match_case(self, env: Env, arg: str, case: MatchCase) -> Tuple[str, str]: @@ -4272,6 +4276,18 @@ def test_compile_match_function_var(self) -> None: "(__x) => {\nif (__x === 1) { return 2; }\nif (true) { const x = __x; return x; }\n}", ) + def test_compile_symbol_bool_true(self) -> None: + exp = Symbol("true") + self.assertEqual(compile_exp_js({}, exp), "true") + + def test_compile_symbol_bool_false(self) -> None: + exp = Symbol("false") + self.assertEqual(compile_exp_js({}, exp), "false") + + def test_compile_symbol(self) -> None: + exp = Symbol("hello") + self.assertEqual(compile_exp_js({}, exp), "'hello'") + def fetch(url: Object) -> Object: if not isinstance(url, String): From 500f57500d0a8fc66f480771556d0a258b7a59a2 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 10 Jan 2024 11:48:28 -0500 Subject: [PATCH 04/14] Add compile-to-JS repl --- scrapscript.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/scrapscript.py b/scrapscript.py index 1a17ec30..b038a155 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -4454,6 +4454,25 @@ def runsource(self, source: str, filename: str = "", symbol: str = "singl return False +class JSRepl(ScrapRepl): + def runsource(self, source: str, filename: str = "", symbol: str = "single") -> bool: + try: + tokens = tokenize(source) + logger.debug("Tokens: %s", tokens) + ast = parse(tokens) + logger.debug("AST: %s", ast) + result = compile_exp_js(self.env, ast) + print(result) + except UnexpectedEOFError: + # Need to read more text + return True + except ParseError as e: + print(f"Parse error: {e}", file=sys.stderr) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + return False + + def eval_command(args: argparse.Namespace) -> None: if args.debug: logging.basicConfig(level=logging.DEBUG) @@ -4483,7 +4502,7 @@ def repl_command(args: argparse.Namespace) -> None: if args.debug: logging.basicConfig(level=logging.DEBUG) - repl = ScrapRepl() + repl = JSRepl() if args.js else ScrapRepl() if readline: repl.enable_readline() repl.interact(banner="") @@ -4521,6 +4540,7 @@ def main() -> None: repl = subparsers.add_parser("repl") repl.set_defaults(func=repl_command) repl.add_argument("--debug", action="store_true") + repl.add_argument("--js", action="store_true") test = subparsers.add_parser("test") test.set_defaults(func=test_command) From c34c328169b30646712a90549c9e31518988b6c1 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 10 Jan 2024 11:55:47 -0500 Subject: [PATCH 05/14] Simplify compiler --- scrapscript.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/scrapscript.py b/scrapscript.py index b038a155..fff480cb 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -1255,9 +1255,6 @@ def bencode(obj: object) -> bytes: class JSCompiler: - def __init__(self) -> None: - self.code: typing.List[str] = [] - def compile(self, env: Env, exp: Object) -> str: if isinstance(exp, Int): return str(exp.value) @@ -1270,12 +1267,10 @@ def compile(self, env: Env, exp: Object) -> str: return exp.name if isinstance(exp, Where): binding = self.compile(env, exp.binding) - self.code.append(binding) - return self.compile(env, exp.body) + return binding + self.compile(env, exp.body) if isinstance(exp, Assign): value = self.compile(env, exp.value) - self.code.append(f"const {exp.name.name} = {value};") - return "" + return f"const {exp.name.name} = {value};\n" if isinstance(exp, Apply): func = self.compile(env, exp.func) arg = self.compile(env, exp.arg) @@ -1315,7 +1310,7 @@ def compile_match_case(self, env: Env, arg: str, case: MatchCase) -> Tuple[str, def compile_exp_js(env: Env, exp: Object) -> str: compiler = JSCompiler() result = compiler.compile(env, exp) - return "\n".join(compiler.code) + result + return result class Bdecoder: @@ -4241,7 +4236,7 @@ def test_compile_where(self) -> None: def test_compile_nested_where(self) -> None: exp = parse(tokenize("x + y . x = 1 . y = 2")) - self.assertEqual(compile_exp_js({}, exp), "const y = 2;\n\nconst x = 1;\n(x)+(y)") + self.assertEqual(compile_exp_js({}, exp), "const y = 2;\nconst x = 1;\n(x)+(y)") def test_compile_apply(self) -> None: exp = Apply(Var("f"), Var("x")) From 3d2a593acace88b5cfacdad633e0ddcebd2c1f55 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 10 Jan 2024 11:56:01 -0500 Subject: [PATCH 06/14] Compile strings --- scrapscript.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/scrapscript.py b/scrapscript.py index fff480cb..c342e8b6 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -1294,7 +1294,9 @@ def compile(self, env: Env, exp: Object) -> str: if exp.value in ("true", "false"): return exp.value return repr(exp.value) - raise NotImplementedError(exp) + if isinstance(exp, String): + return repr(exp.value) + raise NotImplementedError(type(exp), exp) def compile_match_case(self, env: Env, arg: str, case: MatchCase) -> Tuple[str, str]: pattern = case.pattern @@ -4283,6 +4285,18 @@ def test_compile_symbol(self) -> None: exp = Symbol("hello") self.assertEqual(compile_exp_js({}, exp), "'hello'") + def test_compile_string(self) -> None: + exp = String("hello") + self.assertEqual(compile_exp_js({}, exp), "'hello'") + + def test_compile_string_single_quotes(self) -> None: + exp = String("'hello'") + self.assertEqual(compile_exp_js({}, exp), "\"'hello'\"") + + def test_compile_string_double_quotes(self) -> None: + exp = String('"hello"') + self.assertEqual(compile_exp_js({}, exp), "'\"hello\"'") + def fetch(url: Object) -> Object: if not isinstance(url, String): From 3279a5d620fbc1feac2cfb74af4d1b29065dc2fc Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 10 Jan 2024 12:02:22 -0500 Subject: [PATCH 07/14] Compile Access --- scrapscript.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scrapscript.py b/scrapscript.py index c342e8b6..946ddb10 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -1296,6 +1296,11 @@ def compile(self, env: Env, exp: Object) -> str: return repr(exp.value) if isinstance(exp, String): return repr(exp.value) + if isinstance(exp, Access): + if isinstance(exp.at, Int): + return f"{exp.obj}[{exp.at}]" + assert isinstance(exp.at, Var) + return f"{exp.obj}.{exp.at}" raise NotImplementedError(type(exp), exp) def compile_match_case(self, env: Env, arg: str, case: MatchCase) -> Tuple[str, str]: @@ -4297,6 +4302,14 @@ def test_compile_string_double_quotes(self) -> None: exp = String('"hello"') self.assertEqual(compile_exp_js({}, exp), "'\"hello\"'") + def test_compile_access_int(self) -> None: + exp = Access(Var("x"), Int(1)) + self.assertEqual(compile_exp_js({}, exp), "x[1]") + + def test_compile_access_field(self) -> None: + exp = Access(Var("x"), Var("y")) + self.assertEqual(compile_exp_js({}, exp), "x.y") + def fetch(url: Object) -> Object: if not isinstance(url, String): From a427990bcc7d991d81b78a7891b28c183b91235d Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 10 Jan 2024 12:07:03 -0500 Subject: [PATCH 08/14] Compile Record --- scrapscript.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scrapscript.py b/scrapscript.py index 946ddb10..d82ce217 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -1301,6 +1301,11 @@ def compile(self, env: Env, exp: Object) -> str: return f"{exp.obj}[{exp.at}]" assert isinstance(exp.at, Var) return f"{exp.obj}.{exp.at}" + if isinstance(exp, Record): + result = "{" + for key, rec_value in exp.data.items(): + result += repr(key) + ":" + self.compile(env, rec_value) + "," + return result + "}" raise NotImplementedError(type(exp), exp) def compile_match_case(self, env: Env, arg: str, case: MatchCase) -> Tuple[str, str]: @@ -4310,6 +4315,14 @@ def test_compile_access_field(self) -> None: exp = Access(Var("x"), Var("y")) self.assertEqual(compile_exp_js({}, exp), "x.y") + def test_compile_empty_record(self) -> None: + exp = Record({}) + self.assertEqual(compile_exp_js({}, exp), "{}") + + def test_compile_record(self) -> None: + exp = Record({"a": Int(1), "b": Int(2)}) + self.assertEqual(compile_exp_js({}, exp), "{'a':1,'b':2,}") + def fetch(url: Object) -> Object: if not isinstance(url, String): From b404c72aefb7d6734255be430490a656114098f7 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 10 Jan 2024 12:09:31 -0500 Subject: [PATCH 09/14] Fix nested access --- scrapscript.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scrapscript.py b/scrapscript.py index d82ce217..f0b083e0 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -1297,10 +1297,11 @@ def compile(self, env: Env, exp: Object) -> str: if isinstance(exp, String): return repr(exp.value) if isinstance(exp, Access): + obj = self.compile(env, exp.obj) if isinstance(exp.at, Int): - return f"{exp.obj}[{exp.at}]" + return f"{obj}[{exp.at}]" assert isinstance(exp.at, Var) - return f"{exp.obj}.{exp.at}" + return f"{obj}.{exp.at}" if isinstance(exp, Record): result = "{" for key, rec_value in exp.data.items(): @@ -4315,6 +4316,10 @@ def test_compile_access_field(self) -> None: exp = Access(Var("x"), Var("y")) self.assertEqual(compile_exp_js({}, exp), "x.y") + def test_compile_nested_access(self) -> None: + exp = Access(Access(Var("x"), Var("y")), Var("z")) + self.assertEqual(compile_exp_js({}, exp), "x.y.z") + def test_compile_empty_record(self) -> None: exp = Record({}) self.assertEqual(compile_exp_js({}, exp), "{}") From c02404b536dda3b740307c281da8d370e6987fc9 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 10 Jan 2024 12:23:17 -0500 Subject: [PATCH 10/14] Use lambda for Where/Assign --- scrapscript.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scrapscript.py b/scrapscript.py index f0b083e0..af28bdff 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -1266,8 +1266,11 @@ def compile(self, env: Env, exp: Object) -> str: # assert exp.name in env return exp.name if isinstance(exp, Where): - binding = self.compile(env, exp.binding) - return binding + self.compile(env, exp.body) + binding = exp.binding + assert isinstance(binding, Assign) + body = self.compile(env, exp.body) + name, value = binding.name, self.compile(env, binding.value) + return f"(({name}) => ({body}))({value})" if isinstance(exp, Assign): value = self.compile(env, exp.value) return f"const {exp.name.name} = {value};\n" @@ -4245,11 +4248,11 @@ def test_compile_binop_rec(self) -> None: def test_compile_where(self) -> None: exp = Where(Var("x"), Assign(Var("x"), Int(1))) - self.assertEqual(compile_exp_js({}, exp), "const x = 1;\nx") + self.assertEqual(compile_exp_js({}, exp), "((x) => (x))(1)") def test_compile_nested_where(self) -> None: exp = parse(tokenize("x + y . x = 1 . y = 2")) - self.assertEqual(compile_exp_js({}, exp), "const y = 2;\nconst x = 1;\n(x)+(y)") + self.assertEqual(compile_exp_js({}, exp), "((y) => (((x) => ((x)+(y)))(1)))(2)") def test_compile_apply(self) -> None: exp = Apply(Var("f"), Var("x")) From 1aec628b67e3b6ee005c510b5072dda089f965b9 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 10 Jan 2024 12:27:54 -0500 Subject: [PATCH 11/14] Use lambda for pattern matching too --- scrapscript.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/scrapscript.py b/scrapscript.py index af28bdff..97d23de2 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -1268,9 +1268,7 @@ def compile(self, env: Env, exp: Object) -> str: if isinstance(exp, Where): binding = exp.binding assert isinstance(binding, Assign) - body = self.compile(env, exp.body) - name, value = binding.name, self.compile(env, binding.value) - return f"(({name}) => ({body}))({value})" + return self.compile_let(env, binding.name.name, binding.value, exp.body) if isinstance(exp, Assign): value = self.compile(env, exp.value) return f"const {exp.name.name} = {value};\n" @@ -1312,6 +1310,11 @@ def compile(self, env: Env, exp: Object) -> str: return result + "}" raise NotImplementedError(type(exp), exp) + def compile_let(self, env: Env, name: str, value: Object, body: Object) -> str: + body_str = self.compile(env, body) + value_str = self.compile(env, value) + return f"(({name}) => ({body_str}))({value_str})" + def compile_match_case(self, env: Env, arg: str, case: MatchCase) -> Tuple[str, str]: pattern = case.pattern body = case.body @@ -1319,7 +1322,7 @@ def compile_match_case(self, env: Env, arg: str, case: MatchCase) -> Tuple[str, if isinstance(pattern, Int): return f"{arg} === {pattern.value}", ret(self.compile(env, body)) if isinstance(pattern, Var): - return "true", f"const {pattern.name} = {arg}; " + ret(self.compile(env, body)) + return "true", self.compile_let(env, pattern.name, Var(arg), body) raise NotImplementedError(type(pattern)) @@ -4284,7 +4287,7 @@ def test_compile_match_function_var(self) -> None: exp = parse(tokenize("| 1 -> 2 | x -> x")) self.assertEqual( compile_exp_js({}, exp), - "(__x) => {\nif (__x === 1) { return 2; }\nif (true) { const x = __x; return x; }\n}", + "(__x) => {\nif (__x === 1) { return 2; }\nif (true) { ((x) => (x))(__x) }\n}", ) def test_compile_symbol_bool_true(self) -> None: From 1955d25cef0b5abdc3384e35f926ac6415545df9 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 10 Jan 2024 12:30:17 -0500 Subject: [PATCH 12/14] Add return --- scrapscript.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scrapscript.py b/scrapscript.py index 97d23de2..5aa0c531 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -1322,7 +1322,7 @@ def compile_match_case(self, env: Env, arg: str, case: MatchCase) -> Tuple[str, if isinstance(pattern, Int): return f"{arg} === {pattern.value}", ret(self.compile(env, body)) if isinstance(pattern, Var): - return "true", self.compile_let(env, pattern.name, Var(arg), body) + return "true", ret(self.compile_let(env, pattern.name, Var(arg), body)) raise NotImplementedError(type(pattern)) @@ -4287,7 +4287,7 @@ def test_compile_match_function_var(self) -> None: exp = parse(tokenize("| 1 -> 2 | x -> x")) self.assertEqual( compile_exp_js({}, exp), - "(__x) => {\nif (__x === 1) { return 2; }\nif (true) { ((x) => (x))(__x) }\n}", + "(__x) => {\nif (__x === 1) { return 2; }\nif (true) { return ((x) => (x))(__x); }\n}", ) def test_compile_symbol_bool_true(self) -> None: From b143df5c5505e9a68b721c682f1f0ef59c7aadf0 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 10 Jan 2024 12:41:30 -0500 Subject: [PATCH 13/14] Compile match-case with ternary --- scrapscript.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/scrapscript.py b/scrapscript.py index 5aa0c531..a1ae78b1 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -4,6 +4,7 @@ import code import dataclasses import enum +import functools import http.server import json import logging @@ -1284,13 +1285,21 @@ def compile(self, env: Env, exp: Object) -> str: items = [self.compile(env, item) for item in exp.items] return "[" + ", ".join(items) + "]" if isinstance(exp, MatchFunction): + err = "(() => {throw 'oh no'})()" + if not exp.cases: + return err # TODO(max): Gensym arg name or something arg = "__x" - result = f"({arg}) => {{\n" - for case in exp.cases: + + def per_case(acc: str, case: MatchCase) -> str: cond, body = self.compile_match_case(env, arg, case) - result += f"if ({cond}) {{ {body} }}\n" - return result + "}" + return f"({cond}) ? ({body}) : ({acc})" + + return f"({arg}) => " + functools.reduce( + per_case, + reversed(exp.cases), + err, + ) if isinstance(exp, Symbol): if exp.value in ("true", "false"): return exp.value @@ -1318,11 +1327,10 @@ def compile_let(self, env: Env, name: str, value: Object, body: Object) -> str: def compile_match_case(self, env: Env, arg: str, case: MatchCase) -> Tuple[str, str]: pattern = case.pattern body = case.body - ret: Callable[[str], str] = lambda body: f"return {body};" if isinstance(pattern, Int): - return f"{arg} === {pattern.value}", ret(self.compile(env, body)) + return f"{arg} === {pattern.value}", self.compile(env, body) if isinstance(pattern, Var): - return "true", ret(self.compile_let(env, pattern.name, Var(arg), body)) + return "true", self.compile_let(env, pattern.name, Var(arg), body) raise NotImplementedError(type(pattern)) @@ -4280,14 +4288,14 @@ def test_compile_list(self) -> None: def test_compile_match_function(self) -> None: exp = parse(tokenize("| 1 -> 2 | 2 -> 3")) self.assertEqual( - compile_exp_js({}, exp), "(__x) => {\nif (__x === 1) { return 2; }\nif (__x === 2) { return 3; }\n}" + compile_exp_js({}, exp), "(__x) => (__x === 1) ? (2) : ((__x === 2) ? (3) : ((() => {throw 'oh no'})()))" ) def test_compile_match_function_var(self) -> None: exp = parse(tokenize("| 1 -> 2 | x -> x")) self.assertEqual( compile_exp_js({}, exp), - "(__x) => {\nif (__x === 1) { return 2; }\nif (true) { return ((x) => (x))(__x); }\n}", + "(__x) => (__x === 1) ? (2) : ((true) ? (((x) => (x))(__x)) : ((() => {throw 'oh no'})()))", ) def test_compile_symbol_bool_true(self) -> None: From 3bb689b89602a50005a817a52b6ed28c6b83283a Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Sat, 20 Jan 2024 12:23:15 -0500 Subject: [PATCH 14/14] wip --- scrapscript.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/scrapscript.py b/scrapscript.py index a1ae78b1..e3b5c1cc 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -4625,5 +4625,37 @@ def main() -> None: args.func(args) +print( + compile_exp_js( + {}, + parse( + tokenize( + """ +rand_array (new_generator 42) 0 100 10 + +. rand_array = gen -> min -> max -> n -> n |> + | 0 -> [] + | n -> (rand_val >+ rand_array new_gen min max (n - 1) + . rand_val = get_int new_gen + . new_gen = next gen min max) + +-- from Java's java.util.Random +. new_generator = seed -> ({params = params, seed = seed, state = state} + . params = {mod = 281474976710656, mult = 25214903917, inc = 11} + . state = {min = 0, max = 0}) + +. get_int = gen -> $$floor (get gen) + +. get = gen -> (gen@seed / gen@params@mod) * (gen@state@max - gen@state@min) + +. next = gen -> min -> max -> ({params = gen@params, seed = next_seed, state = {min = min, max = max}} + . next_seed = gen@state@min + (gen@seed * gen@params@mult + gen@params@inc) % gen@params@mod) +""" + ) + ), + ) +) + + if __name__ == "__main__": main()