From e45b45dcccfd44cfce7125742baec29d7fa33c7d Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Sat, 12 Oct 2024 19:07:03 +0200 Subject: [PATCH] livescript: merge eval and eval_ast, fix some regressions * map evaluation in step3 * do in step 5 --- impls/livescript/env.ls | 14 +------ impls/livescript/step2_eval.ls | 3 ++ impls/livescript/step3_env.ls | 28 ++++++++----- impls/livescript/step4_if_fn_do.ls | 22 +++++----- impls/livescript/step5_tco.ls | 28 +++++++------ impls/livescript/step6_file.ls | 26 +++++++----- impls/livescript/step7_quote.ls | 27 +++++++------ impls/livescript/step8_macros.ls | 65 ++++++++++-------------------- impls/livescript/step9_try.ls | 65 ++++++++++-------------------- impls/livescript/stepA_mal.ls | 65 ++++++++++-------------------- 10 files changed, 147 insertions(+), 196 deletions(-) diff --git a/impls/livescript/env.ls b/impls/livescript/env.ls index 594048012d..b0d9840809 100644 --- a/impls/livescript/env.ls +++ b/impls/livescript/env.ls @@ -6,16 +6,6 @@ export class Env set: (symbol, ast) -> @data[symbol] = ast - find: (symbol) -> - if symbol of @data then @ - else if @outer? then @outer.find symbol - get: (symbol) -> - result = @try-get symbol - if not result - then throw new Error "'#{symbol}' not found" - else result - - try-get: (symbol) -> - env = @find symbol - if env then env.data[symbol] + if symbol of @data then @data[symbol] + else if @outer? then @outer.get symbol diff --git a/impls/livescript/step2_eval.ls b/impls/livescript/step2_eval.ls index aa4124da97..af898262b1 100644 --- a/impls/livescript/step2_eval.ls +++ b/impls/livescript/step2_eval.ls @@ -18,6 +18,9 @@ repl_env = do value: (a, b) -> {type: \int, value: parseInt(a.value / b.value)} eval_ast = (repl_env, {type, value}: ast) --> + + # console.log "EVAL: #{pr_str ast}" + switch type | \symbol => result = repl_env[value] diff --git a/impls/livescript/step3_env.ls b/impls/livescript/step3_env.ls index b6100f2d1b..8ff6aad67f 100644 --- a/impls/livescript/step3_env.ls +++ b/impls/livescript/step3_env.ls @@ -1,5 +1,5 @@ readline = require './node_readline' -{id, map, each} = require 'prelude-ls' +{id, map, Obj, each} = require 'prelude-ls' {read_str} = require './reader' {pr_str} = require './printer' {Env} = require './env' @@ -27,19 +27,25 @@ list-to-pairs = (list) -> [0 to (list.length - 2) by 2] \ |> map (idx) -> [list[idx], list[idx+1]] - -eval_simple = (env, {type, value}: ast) -> - switch type - | \symbol => env.get value - | \list, \vector => do - type: type - value: value |> map eval_ast env - | otherwise => ast +is-thruthy = ({type, value}) -> + type != \const or value not in [\nil \false] eval_ast = (env, {type, value}: ast) --> - if type != \list then eval_simple env, ast - else if value.length == 0 then ast + + dbgeval = env.get "DEBUG-EVAL" + if dbgeval and is-thruthy dbgeval then console.log "EVAL: #{pr_str ast}" + + switch type + | \symbol => return (env.get value + or throw new Error "'#{value}' not found") + | \list => + # Proceed after this switch + | \vector => return {type: \vector, value: value |> map eval_ast env} + | \map => return {type: \map, value: value |> Obj.map eval_ast env} + | otherwise => return ast + + if value.length == 0 then ast else if value[0].type == \symbol params = value[1 to] switch value[0].value diff --git a/impls/livescript/step4_if_fn_do.ls b/impls/livescript/step4_if_fn_do.ls index 61b74fd00a..5e6dcff786 100644 --- a/impls/livescript/step4_if_fn_do.ls +++ b/impls/livescript/step4_if_fn_do.ls @@ -15,17 +15,21 @@ fmap-ast = (fn, {type, value}: ast) --> {type: type, value: fn value} -eval_simple = (env, {type, value}: ast) -> - switch type - | \symbol => env.get value - | \list, \vector => ast |> fmap-ast map eval_ast env - | \map => ast |> fmap-ast Obj.map eval_ast env - | otherwise => ast +eval_ast = (env, {type, value}: ast) --> + dbgeval = env.get "DEBUG-EVAL" + if dbgeval and is-thruthy dbgeval then console.log "EVAL: #{pr_str ast}" -eval_ast = (env, {type, value}: ast) --> - if type != \list then eval_simple env, ast - else if value.length == 0 then ast + switch type + | \symbol => return (env.get value + or throw new Error "'#{value}' not found") + | \list => + # Proceed after this switch + | \vector => return (ast |> fmap-ast map eval_ast env) + | \map => return (ast |> fmap-ast Obj.map eval_ast env) + | otherwise => return ast + + if value.length == 0 then ast else if value[0].type == \symbol params = value[1 to] switch value[0].value diff --git a/impls/livescript/step5_tco.ls b/impls/livescript/step5_tco.ls index 10c845c60e..f759ee5253 100644 --- a/impls/livescript/step5_tco.ls +++ b/impls/livescript/step5_tco.ls @@ -21,20 +21,24 @@ fmap-ast = (fn, {type, value}: ast) --> {type: type, value: fn value} -eval_simple = (env, {type, value}: ast) -> - switch type - | \symbol => env.get value - | \list, \vector => ast |> fmap-ast map eval_ast env - | \map => ast |> fmap-ast Obj.map eval_ast env - | otherwise => ast +eval_ast = (env, {type, value}: ast) --> + loop + dbgeval = env.get "DEBUG-EVAL" + if dbgeval and is-thruthy dbgeval then console.log "EVAL: #{pr_str ast}" -eval_ast = (env, {type, value}: ast) --> - loop - if type != \list - return eval_simple env, ast - else if value.length == 0 + switch type + | \symbol => return (env.get value + or throw new Error "'#{value}' not found") + | \list => + # Proceed after this switch + | \vector => return (ast |> fmap-ast map eval_ast env) + | \map => return (ast |> fmap-ast Obj.map eval_ast env) + | otherwise => return ast + + if value.length == 0 return ast + else result = if value[0].type == \symbol params = value[1 to] @@ -112,7 +116,7 @@ eval_do = (env, params) -> [...rest, last-param] = params rest |> each eval_ast env - tco env, last-param + defer-tco env, last-param eval_if = (env, params) -> diff --git a/impls/livescript/step6_file.ls b/impls/livescript/step6_file.ls index fac53c29d9..0acaf58c96 100644 --- a/impls/livescript/step6_file.ls +++ b/impls/livescript/step6_file.ls @@ -22,20 +22,24 @@ fmap-ast = (fn, {type, value}: ast) --> {type: type, value: fn value} -eval_simple = (env, {type, value}: ast) -> - switch type - | \symbol => env.get value - | \list, \vector => ast |> fmap-ast map eval_ast env - | \map => ast |> fmap-ast Obj.map eval_ast env - | otherwise => ast +eval_ast = (env, {type, value}: ast) --> + loop + dbgeval = env.get "DEBUG-EVAL" + if dbgeval and is-thruthy dbgeval then console.log "EVAL: #{pr_str ast}" -eval_ast = (env, {type, value}: ast) --> - loop - if type != \list - return eval_simple env, ast - else if value.length == 0 + switch type + | \symbol => return (env.get value + or throw new Error "'#{value}' not found") + | \list => + # Proceed after this switch + | \vector => return (ast |> fmap-ast map eval_ast env) + | \map => return (ast |> fmap-ast Obj.map eval_ast env) + | otherwise => return ast + + if value.length == 0 return ast + else result = if value[0].type == \symbol params = value[1 to] diff --git a/impls/livescript/step7_quote.ls b/impls/livescript/step7_quote.ls index 9b83bb47c0..867e4b67f6 100644 --- a/impls/livescript/step7_quote.ls +++ b/impls/livescript/step7_quote.ls @@ -28,20 +28,24 @@ make-call = (name, params) -> make-list [make-symbol name] ++ params is-symbol = (ast, name) -> ast.type == \symbol and ast.value == name -eval_simple = (env, {type, value}: ast) -> - switch type - | \symbol => env.get value - | \list, \vector => ast |> fmap-ast map eval_ast env - | \map => ast |> fmap-ast Obj.map eval_ast env - | otherwise => ast +eval_ast = (env, {type, value}: ast) --> + loop + dbgeval = env.get "DEBUG-EVAL" + if dbgeval and is-thruthy dbgeval then console.log "EVAL: #{pr_str ast}" -eval_ast = (env, {type, value}: ast) --> - loop - if type != \list - return eval_simple env, ast - else if value.length == 0 + switch type + | \symbol => return (env.get value + or throw new Error "'#{value}' not found") + | \list => + # Proceed after this switch + | \vector => return (ast |> fmap-ast map eval_ast env) + | \map => return (ast |> fmap-ast Obj.map eval_ast env) + | otherwise => return ast + + if value.length == 0 return ast + else result = if value[0].type == \symbol params = value[1 to] @@ -52,7 +56,6 @@ eval_ast = (env, {type, value}: ast) --> | 'if' => eval_if env, params | 'fn*' => eval_fn env, params | 'quote' => eval_quote env, params - | 'quasiquoteexpand' => eval_quasiquoteexpand params | 'quasiquote' => eval_quasiquote env, params | otherwise => eval_apply env, value else diff --git a/impls/livescript/step8_macros.ls b/impls/livescript/step8_macros.ls index 337e678cd5..19690e36f5 100644 --- a/impls/livescript/step8_macros.ls +++ b/impls/livescript/step8_macros.ls @@ -28,24 +28,24 @@ make-call = (name, params) -> make-list [make-symbol name] ++ params is-symbol = (ast, name) -> ast.type == \symbol and ast.value == name -eval_simple = (env, {type, value}: ast) -> - switch type - | \symbol => env.get value - | \list, \vector => ast |> fmap-ast map eval_ast env - | \map => ast |> fmap-ast Obj.map eval_ast env - | otherwise => ast - +eval_ast = (env, {type, value}: ast) --> + loop -eval_ast = (env, ast) --> - loop - if ast.type != \list - return eval_simple env, ast + dbgeval = env.get "DEBUG-EVAL" + if dbgeval and is-thruthy dbgeval then console.log "EVAL: #{pr_str ast}" - ast = macroexpand env, ast - if ast.type != \list - return eval_simple env, ast - else if ast.value.length == 0 + switch type + | \symbol => return (env.get value + or throw new Error "'#{value}' not found") + | \list => + # Proceed after this switch + | \vector => return (ast |> fmap-ast map eval_ast env) + | \map => return (ast |> fmap-ast Obj.map eval_ast env) + | otherwise => return ast + + if value.length == 0 return ast + else result = if ast.value[0].type == \symbol params = ast.value[1 to] @@ -56,16 +56,15 @@ eval_ast = (env, ast) --> | 'if' => eval_if env, params | 'fn*' => eval_fn env, params | 'quote' => eval_quote env, params - | 'quasiquoteexpand' => eval_quasiquoteexpand params | 'quasiquote' => eval_quasiquote env, params | 'defmacro!' => eval_defmacro env, params - | 'macroexpand' => eval_macroexpand env, params | otherwise => eval_apply env, ast.value else eval_apply env, ast.value if result.type == \tco - {env, ast} = result + env = result.env + {type, value}: ast = result.ast else return result @@ -198,10 +197,14 @@ eval_fn = (env, params) -> eval_apply = (env, list) -> - [fn, ...args] = list |> map eval_ast env + [first, ...raw_args] = list + fn = first |> eval_ast env if fn.type != \function runtime-error "#{fn.value} is not a function, got a #{fn.type}" + if fn.is_macro + return (defer-tco env, (unpack-tco (fn.value.apply env, raw_args))) + args = raw_args |> map eval_ast env fn.value.apply env, args @@ -280,30 +283,6 @@ eval_defmacro = (env, params) -> env.set name.value, macro_fn -get-macro-fn = (env, ast) -> - if ast.type == \list and - ast.value.length != 0 and - ast.value[0].type == \symbol - fn = env.try-get ast.value[0].value - if fn and fn.type == \function and fn.is_macro - then fn - - -macroexpand = (env, ast) -> - loop # until ast is not a macro function call. - macro_fn = get-macro-fn env, ast - if not macro_fn then return ast - ast = unpack-tco <| macro_fn.value.apply env, ast.value[1 to] - - -eval_macroexpand = (env, params) -> - if params.length != 1 - runtime-error "'macroexpand' expected 1 parameter, - got #{params.length}" - - macroexpand env, params[0] - - repl_env = new Env for symbol, value of ns repl_env.set symbol, value diff --git a/impls/livescript/step9_try.ls b/impls/livescript/step9_try.ls index 7945a17884..240cc845bc 100644 --- a/impls/livescript/step9_try.ls +++ b/impls/livescript/step9_try.ls @@ -28,24 +28,24 @@ make-call = (name, params) -> make-list [make-symbol name] ++ params is-symbol = (ast, name) -> ast.type == \symbol and ast.value == name -eval_simple = (env, {type, value}: ast) -> - switch type - | \symbol => env.get value - | \list, \vector => ast |> fmap-ast map eval_ast env - | \map => ast |> fmap-ast Obj.map eval_ast env - | otherwise => ast - +eval_ast = (env, {type, value}: ast) --> + loop -eval_ast = (env, ast) --> - loop - if ast.type != \list - return eval_simple env, ast + dbgeval = env.get "DEBUG-EVAL" + if dbgeval and is-thruthy dbgeval then console.log "EVAL: #{pr_str ast}" - ast = macroexpand env, ast - if ast.type != \list - return eval_simple env, ast - else if ast.value.length == 0 + switch type + | \symbol => return (env.get value + or throw new Error "'#{value}' not found") + | \list => + # Proceed after this switch + | \vector => return (ast |> fmap-ast map eval_ast env) + | \map => return (ast |> fmap-ast Obj.map eval_ast env) + | otherwise => return ast + + if value.length == 0 return ast + else result = if ast.value[0].type == \symbol params = ast.value[1 to] @@ -56,17 +56,16 @@ eval_ast = (env, ast) --> | 'if' => eval_if env, params | 'fn*' => eval_fn env, params | 'quote' => eval_quote env, params - | 'quasiquoteexpand' => eval_quasiquoteexpand params | 'quasiquote' => eval_quasiquote env, params | 'defmacro!' => eval_defmacro env, params - | 'macroexpand' => eval_macroexpand env, params | 'try*' => eval_try env, params | otherwise => eval_apply env, ast.value else eval_apply env, ast.value if result.type == \tco - {env, ast} = result + env = result.env + {type, value}: ast = result.ast else return result @@ -199,10 +198,14 @@ eval_fn = (env, params) -> eval_apply = (env, list) -> - [fn, ...args] = list |> map eval_ast env + [first, ...raw_args] = list + fn = first |> eval_ast env if fn.type != \function runtime-error "#{fn.value} is not a function, got a #{fn.type}" + if fn.is_macro + return (defer-tco env, (unpack-tco (fn.value.apply env, raw_args))) + args = raw_args |> map eval_ast env fn.value.apply env, args @@ -281,30 +284,6 @@ eval_defmacro = (env, params) -> env.set name.value, macro_fn -get-macro-fn = (env, ast) -> - if ast.type == \list and - ast.value.length != 0 and - ast.value[0].type == \symbol - fn = env.try-get ast.value[0].value - if fn and fn.type == \function and fn.is_macro - then fn - - -macroexpand = (env, ast) -> - loop # until ast is not a macro function call. - macro_fn = get-macro-fn env, ast - if not macro_fn then return ast - ast = unpack-tco <| macro_fn.value.apply env, ast.value[1 to] - - -eval_macroexpand = (env, params) -> - if params.length != 1 - runtime-error "'macroexpand' expected 1 parameter, - got #{params.length}" - - macroexpand env, params[0] - - eval_try = (env, params) -> if params.length > 2 runtime-error "'try*' expected 1 or 2 parameters, diff --git a/impls/livescript/stepA_mal.ls b/impls/livescript/stepA_mal.ls index 7ee72908f0..81357dfc73 100644 --- a/impls/livescript/stepA_mal.ls +++ b/impls/livescript/stepA_mal.ls @@ -28,24 +28,24 @@ make-call = (name, params) -> make-list [make-symbol name] ++ params is-symbol = (ast, name) -> ast.type == \symbol and ast.value == name -eval_simple = (env, {type, value}: ast) -> - switch type - | \symbol => env.get value - | \list, \vector => ast |> fmap-ast map eval_ast env - | \map => ast |> fmap-ast Obj.map eval_ast env - | otherwise => ast - +eval_ast = (env, {type, value}: ast) --> + loop -eval_ast = (env, ast) --> - loop - if ast.type != \list - return eval_simple env, ast + dbgeval = env.get "DEBUG-EVAL" + if dbgeval and is-thruthy dbgeval then console.log "EVAL: #{pr_str ast}" - ast = macroexpand env, ast - if ast.type != \list - return eval_simple env, ast - else if ast.value.length == 0 + switch type + | \symbol => return (env.get value + or throw new Error "'#{value}' not found") + | \list => + # Proceed after this switch + | \vector => return (ast |> fmap-ast map eval_ast env) + | \map => return (ast |> fmap-ast Obj.map eval_ast env) + | otherwise => return ast + + if value.length == 0 return ast + else result = if ast.value[0].type == \symbol params = ast.value[1 to] @@ -56,17 +56,16 @@ eval_ast = (env, ast) --> | 'if' => eval_if env, params | 'fn*' => eval_fn env, params | 'quote' => eval_quote env, params - | 'quasiquoteexpand' => eval_quasiquoteexpand params | 'quasiquote' => eval_quasiquote env, params | 'defmacro!' => eval_defmacro env, params - | 'macroexpand' => eval_macroexpand env, params | 'try*' => eval_try env, params | otherwise => eval_apply env, ast.value else eval_apply env, ast.value if result.type == \tco - {env, ast} = result + env = result.env + {type, value}: ast = result.ast else return result @@ -199,10 +198,14 @@ eval_fn = (env, params) -> eval_apply = (env, list) -> - [fn, ...args] = list |> map eval_ast env + [first, ...raw_args] = list + fn = first |> eval_ast env if fn.type != \function runtime-error "#{fn.value} is not a function, got a #{fn.type}" + if fn.is_macro + return (defer-tco env, (unpack-tco (fn.value.apply env, raw_args))) + args = raw_args |> map eval_ast env fn.value.apply env, args @@ -281,30 +284,6 @@ eval_defmacro = (env, params) -> env.set name.value, macro_fn -get-macro-fn = (env, ast) -> - if ast.type == \list and - ast.value.length != 0 and - ast.value[0].type == \symbol - fn = env.try-get ast.value[0].value - if fn and fn.type == \function and fn.is_macro - then fn - - -macroexpand = (env, ast) -> - loop # until ast is not a macro function call. - macro_fn = get-macro-fn env, ast - if not macro_fn then return ast - ast = unpack-tco <| macro_fn.value.apply env, ast.value[1 to] - - -eval_macroexpand = (env, params) -> - if params.length != 1 - runtime-error "'macroexpand' expected 1 parameter, - got #{params.length}" - - macroexpand env, params[0] - - eval_try = (env, params) -> if params.length > 2 runtime-error "'try*' expected 1 or 2 parameters,