From 4a1ca8f98479ff2cba4954290ef8db6be936cce9 Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Thu, 6 Jul 2023 16:44:53 +0200 Subject: [PATCH] latex3: new implementation Self hosting fails at step4 because of exceeded TeX capacity. --- Makefile.impls | 4 +- README.md | 13 + impls/latex3/Dockerfile | 22 ++ impls/latex3/Makefile | 3 + impls/latex3/core.sty | 496 ++++++++++++++++++++++++++++++ impls/latex3/env.sty | 61 ++++ impls/latex3/printer.sty | 111 +++++++ impls/latex3/reader.sty | 205 ++++++++++++ impls/latex3/run | 45 +++ impls/latex3/step0_repl.tex | 41 +++ impls/latex3/step1_read_print.tex | 44 +++ impls/latex3/step2_eval.tex | 173 +++++++++++ impls/latex3/step3_env.tex | 222 +++++++++++++ impls/latex3/step4_if_fn_do.tex | 293 ++++++++++++++++++ impls/latex3/step6_file.tex | 314 +++++++++++++++++++ impls/latex3/step7_quote.tex | 381 +++++++++++++++++++++++ impls/latex3/step8_macros.tex | 443 ++++++++++++++++++++++++++ impls/latex3/step9_try.tex | 471 ++++++++++++++++++++++++++++ impls/latex3/stepA_mal.tex | 475 ++++++++++++++++++++++++++++ impls/latex3/types.sty | 90 ++++++ 20 files changed, 3906 insertions(+), 1 deletion(-) create mode 100644 impls/latex3/Dockerfile create mode 100644 impls/latex3/Makefile create mode 100644 impls/latex3/core.sty create mode 100644 impls/latex3/env.sty create mode 100644 impls/latex3/printer.sty create mode 100644 impls/latex3/reader.sty create mode 100755 impls/latex3/run create mode 100644 impls/latex3/step0_repl.tex create mode 100644 impls/latex3/step1_read_print.tex create mode 100644 impls/latex3/step2_eval.tex create mode 100644 impls/latex3/step3_env.tex create mode 100644 impls/latex3/step4_if_fn_do.tex create mode 100644 impls/latex3/step6_file.tex create mode 100644 impls/latex3/step7_quote.tex create mode 100644 impls/latex3/step8_macros.tex create mode 100644 impls/latex3/step9_try.tex create mode 100644 impls/latex3/stepA_mal.tex create mode 100644 impls/latex3/types.sty diff --git a/Makefile.impls b/Makefile.impls index 831bbcc7d2..ddc28c6025 100644 --- a/Makefile.impls +++ b/Makefile.impls @@ -36,13 +36,14 @@ wasm_MODE = wasmtime IMPLS = ada ada.2 awk bash basic bbc-basic c c.2 chuck clojure coffee common-lisp cpp crystal cs d dart \ elisp elixir elm erlang es6 factor fantom fennel forth fsharp go groovy gnu-smalltalk \ - guile haskell haxe hy io janet java java-truffle js jq julia kotlin livescript logo lua make mal \ + guile haskell haxe hy io janet java java-truffle js jq julia kotlin latex3 livescript logo lua make mal \ matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp pike plpgsql \ plsql powershell prolog ps purs python python.2 r racket rexx rpython ruby ruby.2 rust scala scheme skew sml \ swift swift3 swift4 swift5 tcl ts vala vb vhdl vimscript wasm wren yorick xslt zig step5_EXCLUDES += bash # never completes at 10,000 step5_EXCLUDES += basic # too slow, and limited to ints of 2^16 +step5_EXCLUDES += latex3 # no iteration, limited native stack step5_EXCLUDES += make # no TCO capability (iteration or recursion) step5_EXCLUDES += mal # host impl dependent step5_EXCLUDES += matlab # never completes at 10,000 @@ -145,6 +146,7 @@ js_STEP_TO_PROG = impls/js/$($(1)).js jq_STEP_PROG = impls/jq/$($(1)).jq julia_STEP_TO_PROG = impls/julia/$($(1)).jl kotlin_STEP_TO_PROG = impls/kotlin/$($(1)).jar +latex3_STEP_TO_PROG = impls/latex3/$($(1)).tex livescript_STEP_TO_PROG = impls/livescript/$($(1)).js logo_STEP_TO_PROG = impls/logo/$($(1)).lg lua_STEP_TO_PROG = impls/lua/$($(1)).lua diff --git a/README.md b/README.md index 8979bfcb59..93a05ffabc 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ FAQ](docs/FAQ.md) where I attempt to answer some common questions. | [jq](#jq) | [Ali MohammadPur](https://github.com/alimpfard) | | [Julia](#julia) | [Joel Martin](https://github.com/kanaka) | | [Kotlin](#kotlin) | [Javier Fernandez-Ivern](https://github.com/ivern) | +| [LaTeX3](#latex3) | [Nicolas Boulenguez](https://github.com/asarhaddon) | | [LiveScript](#livescript) | [Jos van Bakel](https://github.com/c0deaddict) | | [Logo](#logo) | [Dov Murik](https://github.com/dubek) | | [Lua](#lua) | [Joel Martin](https://github.com/kanaka) | @@ -684,6 +685,18 @@ make java -jar stepX_YYY.jar ``` +### LaTeX3 + +The LaTeX3 implementation of mal has been tested with pdfTeX +3.141592653-2.6-1.40.24. + +Self hosting is too slow for any sensible timeout, and crashes in +step4, apparently because of hard-coded limitations. + +Anybody working on this should uncomment the two lines of (slow) +debugging options in the step file, and export DEBUG=1 (for more +output than tests accept). + ### LiveScript The LiveScript implementation of mal has been tested with LiveScript 1.5. diff --git a/impls/latex3/Dockerfile b/impls/latex3/Dockerfile new file mode 100644 index 0000000000..421c1ef499 --- /dev/null +++ b/impls/latex3/Dockerfile @@ -0,0 +1,22 @@ +FROM ubuntu:20.04 +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python3 +RUN ln -fs /usr/bin/python3 /usr/local/bin/python + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +RUN apt-get -y install texlive-latex-base diff --git a/impls/latex3/Makefile b/impls/latex3/Makefile new file mode 100644 index 0000000000..399a4e44bb --- /dev/null +++ b/impls/latex3/Makefile @@ -0,0 +1,3 @@ +all: +clean: + rm -f *~ *.aux *.dvi *.log argv diff --git a/impls/latex3/core.sty b/impls/latex3/core.sty new file mode 100644 index 0000000000..c7dbcb123e --- /dev/null +++ b/impls/latex3/core.sty @@ -0,0 +1,496 @@ +\ProvidesExplPackage {core} {2023/01/01} {0.0.1} {MAL~core~functions} +\RequirePackage{types} +\RequirePackage{printer} +\RequirePackage{reader} + +\cs_new:Nn \mal_def_builtin:nN + { \prop_put:Nxn \l_mal_repl_env_prop { y \tl_to_str:n { #1 } } { b n #2 } } +\cs_generate_variant:Nn \mal_def_builtin:nN { nc } +\cs_new:Nn \mal_def_builtin:nnn + { + \cs_new:cn { mal_ #2 :n } { #3 } + \mal_def_builtin:nc { #1 } { mal_ #2 :n } + } + +% Integer operations + +\cs_new:Nn \mal_int_op:nnN + { + % \iow_term:n {int_op~left=#1~right=#2~operator=#3} + \tl_set:Nx \l_tmpa_tl + { i \int_eval:n { \use_none:n #1 #3 \use_none:n #2 } } + } + +\mal_def_builtin:nnn { + } { add } { \mal_int_op:nnN #1 + } +\mal_def_builtin:nnn { - } { sub } { \mal_int_op:nnN #1 - } +\mal_def_builtin:nnn { * } { mul } { \mal_int_op:nnN #1 * } +\mal_def_builtin:nnn { / } { div } { \mal_int_op:nnN #1 / } + +% Integer comparisons + +\cs_new:Nn \mal_int_comp:nnNnn + { + \tl_set:Nx \l_tmpa_tl + { \int_compare:oNoTF { \use_none:n #1 } #3 { \use_none:n #2 } #4 #5 } + } + +\mal_def_builtin:nnn { < } { lt} { \mal_int_comp:nnNnn #1 < { t } { f } } +\mal_def_builtin:nnn { > } { gt} { \mal_int_comp:nnNnn #1 > { t } { f } } +\mal_def_builtin:nnn { <= } { le} { \mal_int_comp:nnNnn #1 > { f } { t } } +\mal_def_builtin:nnn { >= } { ge} { \mal_int_comp:nnNnn #1 < { f } { t } } + +% Type tests + +\cs_new:Nn \mal_type_p:nN + { + \tl_set:Nx \l_tmpa_tl { \tl_if_head_eq_charcode:nNTF {#1} #2 { t } { f } } + } + +\mal_def_builtin:nnn { list? } { list_p } { \mal_type_p:nN #1 l } +\mal_def_builtin:nnn { atom? } { atom_p } { \mal_type_p:nN #1 a } +\mal_def_builtin:nnn { nil? } { nil_p } { \mal_type_p:nN #1 n } +\mal_def_builtin:nnn { true? } { true_p } { \mal_type_p:nN #1 t } +\mal_def_builtin:nnn { false? } { false_p } { \mal_type_p:nN #1 f } +\mal_def_builtin:nnn { symbol? } { symbol_p } { \mal_type_p:nN #1 y } +\mal_def_builtin:nnn { keyword? } { keyword_p } { \mal_type_p:nN #1 k } +\mal_def_builtin:nnn { vector? } { vector_p } { \mal_type_p:nN #1 v } +\mal_def_builtin:nnn { map? } { map_p } { \mal_type_p:nN #1 m } +\mal_def_builtin:nnn { string? } { string_p } { \mal_type_p:nN #1 s } +\mal_def_builtin:nnn { number? } { number_p } { \mal_type_p:nN #1 i } +\mal_def_builtin:nnn { macro? } { macro_p } { \mal_type_p:nN #1 c } +\mal_def_builtin:nnn { fn? } { fn_p } + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:nN #1 b } + { \tl_if_head_eq_charcode_p:nN #1 u } + { \tl_set:Nn \l_tmpa_tl { t } } + { \tl_set:Nn \l_tmpa_tl { f } } + } +\mal_def_builtin:nnn { sequential? } { sequential_p } + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:nN #1 l } + { \tl_if_head_eq_charcode_p:nN #1 v } + { \tl_set:Nn \l_tmpa_tl { t } } + { \tl_set:Nn \l_tmpa_tl { f } } + } + +% Other functions, in the order of the process guide. + +\mal_def_builtin:nnn { prn } { prn } + { + \iow_term:x { \mal_printer_tl:nVN {#1} \c_space_tl \c_true_bool } + \tl_set:Nn \l_tmpa_tl { n } + } + +\mal_def_builtin:nnn { list } { list } { \tl_set:Nn \l_tmpa_tl { l n #1 } } + +\cs_new:Nn \mal_empty_p_aux:n + { + \tl_set:Nx \l_tmpa_tl + { \tl_if_empty:oTF { \use_none:nn #1 } { t } { f } } + } +\mal_def_builtin:nnn { empty? } { empty_p } { \mal_empty_p_aux:n #1 } + +\cs_new:Nn \mal_equal_token_lists:nn + { + % \iow_term:n {equal_token_lists~#1~#2} + \tl_if_empty:nTF {#1} + { + \tl_if_empty:nTF {#2} + { \tl_set:Nn \l_tmpa_tl { t } } + { \tl_set:Nn \l_tmpa_tl { f } } + } + { + \tl_if_empty:nTF {#2} + { \tl_set:Nn \l_tmpa_tl { f } } + { + \mal_equal_form:xx { \tl_head:n {#1} } { \tl_head:n {#2} } + \tl_if_head_eq_charcode:VNT \l_tmpa_tl t + { + \mal_equal_token_lists:oo + { \use_none:n #1 } + { \use_none:n #2 } + } + % nothing to do if already false + } + } + } +\cs_generate_variant:Nn \mal_equal_token_lists:nn { oo } + +\cs_new:Nn \mal_equal_map:nn + { + \prop_set_eq:Nc \l_tmpa_prop { #1 } + \prop_set_eq:Nc \l_tmpb_prop { #2 } + \prop_remove:Nn \l_tmpa_prop { __meta__ } + \prop_remove:Nn \l_tmpb_prop { __meta__ } + \tl_if_eq:xxTF + { \prop_count:N \l_tmpa_prop } + { \prop_count:N \l_tmpb_prop } + { + \prop_if_empty:NTF \l_tmpa_prop + { \tl_set:Nn \l_tmpa_tl { t } } + { + \prop_map_inline:Nn \l_tmpa_prop + { + \prop_get:NnNTF \l_tmpb_prop {##1} \l_tmpb_tl + { + \mal_equal_form:Vn \l_tmpb_tl {##2} + \tl_if_head_eq_charcode:VNT \l_tmpa_tl f + { \prop_map_break: } + } + { + \tl_set:Nn \l_tmpa_tl { f } + \prop_map_break: + } + } + % Finish with true if not interrupted + } + } + { \tl_set:Nn \l_tmpa_tl { f } } + } + +\cs_new:Nn \mal_equal_form:nn + { + % \iow_term:n {equal_form~#1~#2} + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:nN {#1} l } + { \tl_if_head_eq_charcode_p:nN {#1} v } + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:nN {#2} l } + { \tl_if_head_eq_charcode_p:nN {#2} v } + { \mal_equal_token_lists:oo { \use_none:nn #1 } { \use_none:nn #2 } } + { \tl_set:Nn \l_tmpa_tl { f } } + } + { + % \iow_term:n {not~a~sequence} + \tl_if_head_eq_charcode:nNTF {#1} m + { + \tl_if_head_eq_charcode:nNTF {#2} m + { \mal_equal_map:nn { #1 } { #2 } } + { \tl_set:Nn \l_tmpa_tl { f } } + } + { + % \iow_term:n {neither~a~sequence~nor~a~map} + \str_if_eq:nnTF {#1} {#2} + { \tl_set:Nn \l_tmpa_tl { t } } + { \tl_set:Nn \l_tmpa_tl { f } } + } + } + } +\cs_generate_variant:Nn \mal_equal_form:nn { Vn, xx } + +\mal_def_builtin:nnn { = } { equal_p } { \mal_equal_form:nn #1 } + +\mal_def_builtin:nnn { count } { count } + { + \tl_if_head_eq_charcode:nNTF #1 n + { \tl_set:Nn \l_tmpa_tl { i 0 } } + { \tl_set:Nx \l_tmpa_tl { i \int_eval:n { \tl_count:n #1 - 2 } } } + } + +\mal_def_builtin:nnn { pr-str } { pr_str } + { + % \iow_term:n {pr_str~#1} + \tl_set:Nx \l_tmpa_tl + { s \mal_printer_tl:nVN { #1 } \c_space_tl \c_true_bool } + } + +\mal_def_builtin:nnn { str } { str } + { \tl_set:Nx \l_tmpa_tl { s \mal_printer_tl:nnN { #1 } { } \c_false_bool } } + +\mal_def_builtin:nnn { println } { println } + { + \iow_term:x { \mal_printer_tl:nVN {#1} \c_space_tl \c_false_bool } + \tl_set:Nn \l_tmpa_tl n + } + +\cs_new:Nn \mal_read_string_aux:n + { + \tl_set:No \l_tmpa_str { \use_none:n #1 } + \mal_read_str: + } +\mal_def_builtin:nnn { read-string } { read_string } + { \mal_read_string_aux:n #1 } + +\cs_new:Nn \mal_slurp_aux:n + { + \tl_set:Nn \l_tmpa_tl { s } + \ior_open:Nx \g_tmpa_ior { \use_none:n #1 } + \ior_str_map_inline:Nn \g_tmpa_ior + { + \tl_put_right:Nn \l_tmpa_tl { ##1 } + \tl_put_right:NV \l_tmpa_tl \c_new_line_str + } + \ior_close:N \g_tmpa_ior + } +\mal_def_builtin:nnn { slurp } { slurp } { \mal_slurp_aux:n #1 } + +\mal_def_builtin:nnn { atom } { atom } + { + % \iow_term:n {atom~#1} + \int_incr:N \l_mal_object_counter_int + \tl_set:Nx \l_tmpa_tl { atom_ \int_use:N \l_mal_object_counter_int } + \tl_new:c \l_tmpa_tl + \tl_set:cn \l_tmpa_tl #1 + } + +\mal_def_builtin:nnn { deref } { deref } { \tl_set_eq:Nc \l_tmpa_tl #1 } + +\cs_new:Nn \mal_reset_aux:Nn + { + \tl_set:Nn #1 { #2 } + \tl_set:Nn \l_tmpa_tl { #2 } + } +\cs_generate_variant:Nn \mal_reset_aux:Nn { cn } +\mal_def_builtin:nnn { reset! } { reset } { \mal_reset_aux:cn #1 } + +\mal_def_builtin:nnn { swap! } { swap } + { + % \iow_term:n {swap~#1} + \mal_fn_apply:xx { \tl_item:nn { #1 }{ 2 } } + { { \exp_not:v { \tl_head:n { #1 } } } \exp_not:o { \use_none:nn #1 } } + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \tl_set_eq:cN { \tl_head:n { #1 } } \l_tmpa_tl } + } + +\cs_new:Nn \mal_cons_aux:nn + { + % \iow_term:n {cons~#1~#2} + \tl_set:No \l_tmpa_tl { \use_none:nn #2 } + \tl_put_left:Nn \l_tmpa_tl { l n {#1} } + } +\mal_def_builtin:nnn { cons } { cons } { \mal_cons_aux:nn #1 } + +\cs_new:Nn \mal_concat_fn:n { \use_none:nn #1 } +\mal_def_builtin:nnn { concat } { concat } + { \tl_set:Nx \l_tmpa_tl { l n \tl_map_function:nN {#1} \mal_concat_fn:n } } + +\cs_new:Nn \mal_vec_aux:n + { + % \iow_term:n {vec~#1} + \tl_set:No \l_tmpa_tl { \use_none:nn #1 } + \tl_put_left:Nn \l_tmpa_tl { v n } + } +\mal_def_builtin:nnn { vec } { vec } { \mal_vec_aux:n #1 } + +\cs_new:Nn \mal_nth_aux:nn + { + % \iow_term:n {nth~#1~#2} + \int_set:Nn \l_tmpa_int { 3 + \use_none:n #2 } + \tl_set:Nx \l_tmpa_tl { \tl_item:nV {#1} \l_tmpa_int } + \tl_if_empty:VT \l_tmpa_tl + { \tl_set:Nx \l_tmpa_tl { e s \tl_to_str:n {nth:~index~out~of~range} } } + } +\mal_def_builtin:nnn { nth } { nth } { \mal_nth_aux:nn #1 } + +\mal_def_builtin:nnn { first } { first } + { + % \iow_term:n {first~#1} + \tl_set:Nx \l_tmpa_tl { \tl_item:nn #1 {3} } + \tl_if_empty:NT \l_tmpa_tl + { \tl_set:Nn \l_tmpa_tl {n} } + } + +% This returns () for nil (unlike \use_none:nnn). +\mal_def_builtin:nnn { rest } { rest } + { \tl_set:Nx \l_tmpa_tl { l n \tl_range:nnn #1 4 {-1} } } + +\mal_def_builtin:nnn { throw } { throw } + { + % \iow_term:n {throw~#1} + \tl_set:Nn \l_tmpa_tl #1 + \tl_put_left:Nn \l_tmpa_tl {e} + } + +\mal_def_builtin:nnn { apply } { apply } + { + % \iow_term:n {apply~#1} + \tl_set:Nx \l_tmpb_tl { \tl_item:nn { #1 } { -1 } } % mal sequence + \mal_fn_apply:xx + { \tl_head:n { #1 } } + { + \tl_range:nnn { #1 } { 2 } { -2 } + \tl_range:Vnn \l_tmpb_tl { 3 } { -1 } % the same as a tl + } + } + +\cs_new:Nn \mal_map_rec:nnn + { + % \iow_term:n {map~acc=#1~forms=#2~func=#3} + \tl_if_empty:nTF {#2} + { \tl_set:Nn \l_tmpa_tl {#1} } + { + \mal_fn_apply:nx { #3 } { { \tl_head:n {#2} } } + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \mal_map_rec:xon + { \exp_not:n {#1} { \exp_not:V \l_tmpa_tl } } + { \use_none:n #2 } + { #3 } + } + } + } +\cs_generate_variant:Nn \mal_map_rec:nnn { non, xon } +\cs_new:Nn \mal_map_aux:nn + { \mal_map_rec:non { l n } { \use_none:nn #2 } { #1 } } +\mal_def_builtin:nnn { map } { map } { \mal_map_aux:nn #1 } + +\cs_new:Nn \mal_symbol_aux:n { \tl_set:Nx \l_tmpa_tl { y \use_none:n #1 } } +\mal_def_builtin:nnn { symbol } { symbol } { \mal_symbol_aux:n #1 } + +\cs_new:Nn \mal_keyword_aux:n { \tl_set:Nx \l_tmpa_tl { k \use_none:n #1 } } +\mal_def_builtin:nnn { keyword } { keyword } { \mal_keyword_aux:n #1 } + +\mal_def_builtin:nnn { vector } { vector } { \tl_set:Nn \l_tmpa_tl { v n #1 } } + +\mal_def_builtin:nN { hash-map } \mal_hash_map:n + +\mal_def_builtin:nnn { assoc } { assoc } + { + % \iow_term:n {assoc~#1} + \mal_map_new: + \prop_set_eq:cc \l_tmpa_tl { \tl_head:n { #1 } } + \mal_assoc_internal:o { \use_none:n #1 } + } + +\mal_def_builtin:nnn { dissoc } { dissoc } + { + % \iow_term:n {dissoc~prop=#1~keys=#2} + \mal_map_new: + \prop_set_eq:cc \l_tmpa_tl { \tl_head:n { #1 } } + \tl_map_inline:on { \use_none:n #1 } { \prop_remove:cn \l_tmpa_tl { ##1 } } + } + +\cs_new:Nn \mal_get_aux:nn + { + % \iow_term:n {get~#1~#2} + \tl_if_head_eq_charcode:nNTF { #1 } n + { \tl_set:Nn \l_tmpa_tl { n } } + { + \prop_get:cnNF { #1 } { #2 } \l_tmpa_tl + { \tl_set:Nn \l_tmpa_tl { n } } + } + } +\mal_def_builtin:nnn { get } { get } { \mal_get_aux:nn #1 } + +\mal_def_builtin:nnn { contains? } { contains } + { + % \iow_term:n {contains?~#1~#2} + \prop_if_in:cnTF #1 + { \tl_set:Nn \l_tmpa_tl { t } } + { \tl_set:Nn \l_tmpa_tl { f } } + } + +\cs_new:Nn \mal_keys_fn:nn + { \str_if_eq:nnF { #1 } { __meta__ } { \exp_not:n { { #1 } } } } +\mal_def_builtin:nnn { keys } { keys } + { \tl_set:Nx \l_tmpa_tl { l n \prop_map_function:cN #1 \mal_keys_fn:nn } } + +\cs_new:Nn \mal_vals_fn:nn + { \str_if_eq:nnF { #1 } { __meta__ } { \exp_not:n { { #2 } } } } +\mal_def_builtin:nnn { vals } { vals } + { \tl_set:Nx \l_tmpa_tl { l n \prop_map_function:cN #1 \mal_vals_fn:nn } } + +\mal_def_builtin:nnn { readline } { readline } + { + % \iow_term:n {readline:~|#1|} + \tl_set:Nx \l_tmpa_tl { \tl_head:n {#1} } + \str_set:Nx \l_tmpa_str { \str_tail:V \l_tmpa_tl } + \iow_term:V \l_tmpa_str + \ior_str_get_term:nN {} \l_tmpa_str + \tl_set:Nx \l_tmpa_tl { s \l_tmpa_str } + } + +\str_const:Nx \c_mal_date_str { date ~ + \c_percent_str s } +\sys_get_shell:VnN \c_mal_date_str {} \l_tmpa_str +\int_const:NV \c_mal_start_time_int \l_tmpa_str + +\mal_def_builtin:nnn { time-ms } { time_ms } + { + \iow_term:n {MAL_LATEX3_STRIP_ON} + \sys_get_shell:VnN \c_mal_date_str {} \l_tmpa_str + \iow_term:n {MAL_LATEX3_STRIP_OFF} + \int_set:Nn \l_tmpa_int { \l_tmpa_str - \c_mal_start_time_int } + \tl_set:Nx \l_tmpa_tl { i \int_to_arabic:V \l_tmpa_int 000 } + } + +\mal_def_builtin:nnn { meta } { meta } + { + % \iow_term:n {meta~#1} + \tl_if_head_eq_charcode:nNTF #1 m + { + \prop_get:cnNF #1 { __meta__ } \l_tmpa_tl + { \tl_set:Nx \l_tmpa_tl { n } } + } + { \tl_set:Nx \l_tmpa_tl { \tl_item:nn #1 { 2 } } } + } + +\cs_new:Nn \mal_with_meta_aux:nn + { + % \iow_term:n {with-meta~#1~#2} + \tl_if_head_eq_charcode:nNTF { #1 } m + { + \mal_map_new: + \prop_set_eq:cc \l_tmpa_tl { #1 } + \prop_put:cnn \l_tmpa_tl { __meta__ } { #2 } + } + { + \tl_set:Nx \l_tmpa_tl + { + \tl_head:n { #1 } + \exp_not:n { { #2 } } + \exp_not:o { \use_none:nn #1 } + } + } + } +\mal_def_builtin:nnn { with-meta } { with_meta } { \mal_with_meta_aux:nn #1 } + +\cs_new:Nn \mal_seq_fn:N { { s #1 } } +\cs_new:Nn \mal_seq_aux:n + { + % \iow_term:n {seq:~#1} + \exp_args:Nx \token_case_charcode:Nn { \tl_head:n {#1} } + { + n + { \tl_clear:N \l_tmpa_tl } + l + { \tl_set:No \l_tmpa_tl { \use_none:nn #1 } } + v + { \tl_set:No \l_tmpa_tl { \use_none:nn #1 } } + s + { + \tl_set:Nx \l_tmpa_tl + { \str_map_function:oN { \use_none:n #1 } \mal_seq_fn:N } + } + } + \tl_if_empty:NTF \l_tmpa_tl + { \tl_set:Nn \l_tmpa_tl n } + { \tl_put_left:Nn \l_tmpa_tl { l n } } + } +\mal_def_builtin:nnn { seq } { seq } { \mal_seq_aux:n #1 } + +\mal_def_builtin:nnn { conj } { conj } + { + % \iow_term:n {conj~#1} + \tl_set:Nx \l_tmpa_tl { \tl_head:n {#1} } + \tl_set:Nx \l_tmpb_tl { \tl_tail:n {#1} } + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl v + { + \tl_set:Nx \l_tmpa_tl + { + v n + \tl_range:Vnn \l_tmpa_tl 3 {-1} + \exp_not:V \l_tmpb_tl + } + } + { + \tl_set:Nx \l_tmpa_tl + { + l n + \tl_reverse:V \l_tmpb_tl + \tl_range:Vnn \l_tmpa_tl 3 {-1} + } + } + } diff --git a/impls/latex3/env.sty b/impls/latex3/env.sty new file mode 100644 index 0000000000..f1a5f42adc --- /dev/null +++ b/impls/latex3/env.sty @@ -0,0 +1,61 @@ +\ProvidesExplPackage {env} {2023/01/01} {0.0.1} {MAL~environments} +\RequirePackage{types} + +\prop_new:N \l_mal_repl_env_prop + +% Scratch variable containing the name of an enviromnent as a token +% list, intended to be used with :c expansion. +\tl_new:N \l_mal_tmp_env_prop + +% Note that __outer__ is not a valid key. + +% The new environment is returned in \l_mal_tmp_env_prop. +\cs_new:Nn \mal_env_new:N + { + % \iow_term:n {env_new:~outer=#1} + \int_incr:N \l_mal_object_counter_int + \tl_set:Nx \l_mal_tmp_env_prop { env_ \int_use:N \l_mal_object_counter_int } + \prop_new:c \l_mal_tmp_env_prop + \prop_put:cnn \l_mal_tmp_env_prop { __outer__ } { #1 } + } + +% \prop_put:Nnn is OK for a single assignment. + +% Shortcut for repeated '\prop_put:cnn \l_mal_tmp_env_prop' assignments, +% with special handling of & variable arguments. + +\tl_const:Nx \c_ampersand_symbol { y \tl_to_str:n { & } } + +\cs_new:Nn \mal_env_set_keys_values:nn + { + % \iow_term:n {apply_loop:~keys=#1~vals=#2} + \tl_if_empty:nF { #1 } + { + \tl_set:Nx \l_tmpb_tl { \tl_head:n { #1 } } + \tl_if_eq:NNTF \l_tmpb_tl \c_ampersand_symbol + { \prop_put:cxn \l_mal_tmp_env_prop { \tl_item:nn { #1 } { 2 } } + { l n #2 } } + { + \prop_put:cVx \l_mal_tmp_env_prop \l_tmpb_tl { \tl_head:n { #2 } } + \mal_env_set_keys_values:oo { \use_none:n #1 } { \use_none:n #2 } + } + } + } +\cs_generate_variant:Nn \mal_env_set_keys_values:nn { on, oo } + +\cs_new:Nn \mal_env_get:NnTF + { + % \iow_term:n {env_get:~env=#1~key=#2} + \prop_get:NnNTF #1 { #2 } \l_tmpa_tl + { #3 } + { + \prop_get:NnNTF #1 { __outer__ } \l_tmpa_tl + { \exp_args:NV \mal_env_get:NnTF \l_tmpa_tl { #2 } { #3 } { #4 } } + { #4 } + } + } +% This one is useful for macroexpand, but may disappear once it is removed. +\cs_generate_variant:Nn \mal_env_get:NnTF { NxTF } +\cs_new:Nn \mal_env_get:NnT { \mal_env_get:NnTF #1 { #2 } { #3 } { } } +\cs_new:Nn \mal_env_get:NnF { \mal_env_get:NnTF #1 { #2 } { } { #3 } } +\cs_generate_variant:Nn \mal_env_get:NnT { NVT } diff --git a/impls/latex3/printer.sty b/impls/latex3/printer.sty new file mode 100644 index 0000000000..104e95dab4 --- /dev/null +++ b/impls/latex3/printer.sty @@ -0,0 +1,111 @@ +\ProvidesExplPackage {printer} {2023/01/01} {0.0.1} {MAL~printer} +\RequirePackage{types} + +\str_const:Nx \c_new_line_str { \char_generate:nn {10} {12} } + +% \str_map_function:oN { \use_none:n #1 } skips space characters bug? +% It does not in core.sty... why? +% \str_map_inline does not, but is not expandable. +\cs_new:Nn \mal_printer_string:n + { + \tl_if_empty:nF { #1 } + { + \tl_if_head_is_space:nTF { #1 } + { \c_space_tl } + { + \exp_args:NnV \tl_if_head_eq_charcode:nNTF { #1 } \c_new_line_str + { \c_backslash_str \tl_to_str:n { n } } + { + \bool_lazy_or:nnT + { \tl_if_head_eq_charcode_p:nN { #1 } " } + { \exp_args:NnV \tl_if_head_eq_charcode_p:nN { #1 } \c_backslash_str } + { \c_backslash_str } + \tl_head:n { #1 } + } + } + \mal_printer_string:e { \str_tail:n { #1 } } + } + } + +\cs_generate_variant:Nn \mal_printer_string:n { e, o } + +\cs_new:Nn \mal_printer_pr_str_flip:Nn { \mal_printer_pr_str:nN { #2 } #1 } + +\cs_new:Nn \mal_printer_tl:nnN + { + % \iow_term:n {printer_tl~forms=#1~separator=#2~readably=#3} + \tl_if_empty:nF {#1} + { + \mal_printer_pr_str:fN { \tl_head:n { #1 } } #3 + \tl_map_tokens:on { \use_none:n #1 } + { #2 \mal_printer_pr_str_flip:Nn #3 } + } + } +\cs_generate_variant:Nn \mal_printer_tl:nnN { nVN, oVN, VVN, eVN } + +\cs_new:Nn \mal_printer_map_fn:nn + { \str_if_eq:nnF { #1 } { __meta__ } { \exp_not:n { { #1 } { #2 } } } } + +\cs_new:Nn \mal_printer_pr_str:nN + { + \exp_args:Nf \token_case_charcode:NnF { \tl_head:n {#1} } + { + n { \tl_to_str:n { nil } } + f { \tl_to_str:n { false } } + t { \tl_to_str:n { true } } + + i { \int_to_arabic:o { \use_none:n #1 } } + + y { \use_none:n #1 } + k { \c_colon_str \use_none:n #1 } + s + { + \bool_if:NTF #2 + { " \mal_printer_string:o { \use_none:n #1 } " } + { \use_none:n #1 } + } + + l { ( \mal_printer_tl:oVN { \use_none:nn #1 } \c_space_tl #2 ) } + v { [ \mal_printer_tl:oVN { \use_none:nn #1 } \c_space_tl #2 ] } + m + { + \c_left_brace_str + \mal_printer_tl:eVN + { \prop_map_function:cN { #1 } \mal_printer_map_fn:nn } + \c_space_tl #2 + \c_right_brace_str + } + + b { \tl_to_str:n { } } + u { \tl_to_str:n { } } + c { \tl_to_str:n { } } + a { \tl_to_str:n { (atom~ } \mal_printer_pr_str:vN { #1 } #2 ) } + e + { \tl_to_str:n { Error:~ } \mal_printer_pr_str:oN { \use_none:n #1 } #2 } + } + { \tl_to_str:n { Error:~invalid~print~argument~#1 } } + } +\cs_generate_variant:Nn \mal_printer_pr_str:nN { fN, oN, VN, vN } + +%% \mal_printer_pr_str:nN { n } \c_true_bool +%% \mal_printer_pr_str:nN { i 23 } \c_true_bool +%% \mal_printer_pr_str:oN { y \tl_to_str:n { symbol } } \c_true_bool +%% \mal_printer_pr_str:oN { k \tl_to_str:n { keyword } } \c_true_bool +%% \mal_printer_pr_str:nN { s } \c_false_bool +%% \mal_printer_pr_str:oN { s \tl_to_str:n { unreadable"string } } \c_false_bool +%% \mal_printer_pr_str:nN { l n } \c_true_bool +%% \mal_printer_pr_str:nN { l n n t } \c_true_bool +%% \mal_printer_pr_str:nN { l n { i 1 } { i 2 } } \c_true_bool +%% \mal_printer_pr_str:nN { v n { i 1 } { i 2 } } \c_true_bool +%% \mal_printer_pr_str:nN { l n { l n { i 1 } { i 2 } } t } \c_true_bool +%% \mal_printer_pr_str:oN { s \tl_to_str:n { d " q } } \c_true_bool +%% \mal_printer_pr_str:oN { s \tl_to_str:n { b } \c_backslash_str \tl_to_str:n { s } } \c_true_bool +%% \mal_printer_pr_str:oN { s \tl_to_str:n { n } \c_new_line_str \tl_to_str:n { l } } \c_true_bool + +%% \tl_set:Nn \l_tmpa_tl { i 3 } +%% \mal_printer_pr_str:nN { a \l_tmpa_tl } \c_true_bool + +%% \prop_clear:N \l_tmpa_prop +%% \prop_put:Nxn \l_tmpa_prop { k \tl_to_str:n {a} } { i 12 } +%% \prop_put:Nxn \l_tmpa_prop { s \tl_to_str:n {b} } { n } +%% \mal_printer_pr_str:xN { m n \exp_not:V \l_tmpa_prop } \c_true_bool diff --git a/impls/latex3/reader.sty b/impls/latex3/reader.sty new file mode 100644 index 0000000000..d68d8fa946 --- /dev/null +++ b/impls/latex3/reader.sty @@ -0,0 +1,205 @@ +\ProvidesExplPackage {reader} {2023/01/01} {0.0.1} {MAL~reader} +\RequirePackage{types} + +% It would be convenient to output the forms in a list directly, but +% this would require a fully expandable read_str. \prop_set and +% \regex_replace_once are too convenient. + +% \l_tmpa_str is used as a common buffer for the remaining input. + +% Compile the regular expressions once and for all. +\regex_const:Nn \c_mal_space_regex { ^ (?: \s | , | ; \N* \n )* } +\regex_const:Nn \c_mal_unescape_cr_regex { \\ n } +\regex_const:Nn \c_mal_unescape_regex { \\ ([^n]) } +\regex_const:Nn \c_mal_number_regex + { ^ ( -? \d+ ) (.*) } +\regex_const:Nn \c_mal_symbol_regex + { ^ ( [^ " ' \( \) , ; @ \[ \] ^ ` \{ \} \~ \s ] + ) (.*) } +\regex_const:Nn \c_mal_keyword_regex + { ^ : ( [^ " ' \( \) , : ; @ \[ \] ^ ` \{ \} \~ \s ] + ) (.*) } +\regex_const:Nn \c_mal_string_regex + { ^ " ( (?: [^ \\ "] | \\ . )* ) " (.*) } + +\cs_new:Nn \mal_skip_spaces: + { \regex_replace_once:NnN \c_mal_space_regex {} \l_tmpa_str } + +\cs_new:Nn \mal_skip_char: + { \tl_set:Nx \l_tmpa_str { \tl_tail:V \l_tmpa_str } } + +% Read forms until a closing brace #1. +% Return a tl of MAL forms or an error in \l_tmpa_tl. +% accumulator closing brace +\cs_new:Nn \mal_reader_seq_loop:nN + { + % \iow_term:n {reader_seq_loop~#1~#2} + \mal_skip_spaces: + \tl_if_head_eq_charcode:VNTF \l_tmpa_str #2 + { + \mal_skip_char: + \tl_set:Nn \l_tmpa_tl { #1 } + } + { + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_reader_seq_loop:xN { \exp_not:n {#1} { \exp_not:V \l_tmpa_tl } } #2 } + } + } +\cs_generate_variant:Nn \mal_reader_seq_loop:nN { xN } + +% #1: a token list without leading y +\cs_new:Nn \mal_reader_quote:n + { + % \iow_term:n {quote~#1} + \mal_skip_char: + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \tl_set:Nx \l_tmpa_tl + { + l n + { y \tl_to_str:n { #1 } } + { \exp_not:V \l_tmpa_tl } } + } + } + +% The only purpose of this macro is to store #1 during read_str. +\cs_new:Nn \mal_reader_with_meta:n + { + % \iow_term:n {with_meta~#1} + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \tl_set:Nx \l_tmpa_tl { + l n + { y \tl_to_str:n { with-meta } } + { \exp_not:V \l_tmpa_tl } + \exp_not:n { { #1 } } + } + } + } +\cs_generate_variant:Nn \mal_reader_with_meta:n { V } + +% Input in \l_tmpa str (modified) +% Write the MAL form to \l_tmpa_tl. +\cs_new:Nn \mal_read_str: + { + % \iow_term:x {reader_read_str~\l_tmpa_str} + \mal_skip_spaces: + \str_case_e:nnF { \str_head:V \l_tmpa_str } + { + { ' } + { \mal_reader_quote:n { quote } } + { @ } + { \mal_reader_quote:n { deref } } + { ` } + { \mal_reader_quote:n { quasiquote } } + { ( } + { + \mal_skip_char: + \mal_reader_seq_loop:nN { l n } ) + } + { [ } + { + \mal_skip_char: + \mal_reader_seq_loop:nN { v n } ] + } + \c_left_brace_str + { + \mal_skip_char: + \exp_args:NnV \mal_reader_seq_loop:nN { } \c_right_brace_str + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_hash_map:V \l_tmpa_tl } + } + \c_tilde_str + { + \str_if_eq:xnTF { \str_item:Vn \l_tmpa_str 2 } { @ } + { + \mal_skip_char: + \mal_reader_quote:n { splice-unquote } + } + { \mal_reader_quote:n { unquote } } + } + { ^ } + { + \mal_skip_char: + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_reader_with_meta:V \l_tmpa_tl } + } + } + { + \regex_extract_once:NVNTF \c_mal_string_regex \l_tmpa_str \l_tmpa_seq + { + \seq_get_right:NN \l_tmpa_seq \l_tmpa_str + \tl_set:Nx \l_tmpa_tl { s \seq_item:Nn \l_tmpa_seq 2 } + \regex_replace_case_all:nN + { + \c_mal_unescape_cr_regex { \n } + \c_mal_unescape_regex { \1 } + } + \l_tmpa_tl + } + { + \regex_extract_once:NVNTF \c_mal_keyword_regex \l_tmpa_str \l_tmpa_seq + { + \seq_get_right:NN \l_tmpa_seq \l_tmpa_str + \tl_set:Nx \l_tmpa_tl { k \seq_item:Nn \l_tmpa_seq 2 } + } + { + \regex_extract_once:NVNTF \c_mal_number_regex \l_tmpa_str \l_tmpa_seq + { + \seq_get_right:NN \l_tmpa_seq \l_tmpa_str + \tl_set:Nx \l_tmpa_tl { i \seq_item:Nn \l_tmpa_seq 2 } + } + { + \regex_extract_once:NVNTF \c_mal_symbol_regex \l_tmpa_str \l_tmpa_seq + { + \seq_get_right:NN \l_tmpa_seq \l_tmpa_str + \tl_set:Nx \l_tmpa_tl { \seq_item:Nn \l_tmpa_seq 2 } + \str_case:NnF \l_tmpa_tl + { + { nil } { \tl_set:Nn \l_tmpa_tl { n } } + { false } { \tl_set:Nn \l_tmpa_tl { f } } + { true } { \tl_set:Nn \l_tmpa_tl { t } } + } + { \tl_put_left:Nn \l_tmpa_tl { y } } % catcode is already Ok + } + { + \tl_set:Nn \l_tmpa_tl { e s unbalanced~expression } + } + } + } + } + } + % \iow_term:n {__ read_str~returns} + % \iow_term:V \l_tmpa_tl + } + +% \str_set:Nn \l_tmpa_str { ~, } \mal_read_str: \iow_term:V \l_tmpa_tl +% \str_set:Nn \l_tmpa_str { ~12~a } \mal_read_str: \iow_term:V \l_tmpa_tl +% \str_set:Nn \l_tmpa_str { -12 } \mal_read_str: \iow_term:V \l_tmpa_tl +% \str_set:Nn \l_tmpa_str { ab } \mal_read_str: \iow_term:V \l_tmpa_tl +% \str_set:Nn \l_tmpa_str { nil } \mal_read_str: \iow_term:V \l_tmpa_tl +% \str_set:Nn \l_tmpa_str { :ab } \mal_read_str: \iow_term:V \l_tmpa_tl +% \str_set:Nn \l_tmpa_str { "ab"w } \mal_read_str: \iow_term:V \l_tmpa_tl +% \str_set:Nn \l_tmpa_str { (,) } \mal_read_str: \iow_term:V \l_tmpa_tl +% \str_set:Nn \l_tmpa_str { (nil~:a) } \mal_read_str: \iow_term:V \l_tmpa_tl +% \str_set:Nn \l_tmpa_str { (nil,[:a]) } \mal_read_str: \iow_term:V \l_tmpa_tl +% \str_set:Nn \l_tmpa_str { 'a } \mal_read_str: \iow_term:V \l_tmpa_tl +% \str_set:Nn \l_tmpa_str { ^a~b } \mal_read_str: \iow_term:V \l_tmpa_tl + +% \str_set:Nx \l_tmpa_str { \c_left_brace_str "a"~1~:b~2 \c_right_brace_str } +% \mal_read_str: +% \iow_term:V \l_tmpa_tl + +% \str_set:Nx \l_tmpa_str +% { +% \c_left_brace_str +% "a"~1 +% ~:b~\c_left_brace_str +% :c~3 +% \c_right_brace_str +% \c_right_brace_str +% } +% \mal_read_str: +% \iow_term:V \l_tmpa_tl diff --git a/impls/latex3/run b/impls/latex3/run new file mode 100755 index 0000000000..7a05291e20 --- /dev/null +++ b/impls/latex3/run @@ -0,0 +1,45 @@ +#!/bin/sh +set -Cefu + +# LaTeX creates temporary files in the current directory. +cd $(dirname $0) + +# There is no way to directly provide command line arguments to LaTeX, +# use an intermediate file. +for arg; do + echo "$arg" +done >| argv + +# max_print_line: prevent TeX from wrapping lines written to the +# terminal (the default is around 80 columns). + +# Shell escapes are necessary for time-ms in core.sty. +# time-ms also requires to strip the output caused by accessing a subshell. + +# Halt on error... should be the default. + +# Remove the normal TeX initial and final output. The > characters +# confuse the test runner, especially in the *ARGV* test. + +# There is no way in latex3 to check if the terminal receives an +# END_OF_FILE character, handle Emergency stop as a normal ending. + +# When debugging, set DEBUG=1 to see the actual output. + +max_print_line=1000 \ +latex \ + -shell-escape \ + -halt-on-error \ + ${STEP:-stepA_mal}.tex \ + | { + if [ -n "${DEBUG:-}" ]; then + cat + else + sed ' + 1,/^MAL_LATEX3_START_OF_OUTPUT$/ d + /^MAL_LATEX3_END_OF_OUTPUT$/,$ d + /^MAL_LATEX3_STRIP_ON/,/MAL_LATEX3_STRIP_OFF/ d + /^! Emergency stop[.]$/,$ d + ' + fi +} diff --git a/impls/latex3/step0_repl.tex b/impls/latex3/step0_repl.tex new file mode 100644 index 0000000000..7575cbb4b5 --- /dev/null +++ b/impls/latex3/step0_repl.tex @@ -0,0 +1,41 @@ +\documentclass{article} +\usepackage +% Uncomment this and \debug_on below when debugging. +% [enable-debug] + {expl3} +\usepackage{types} +\ExplSyntaxOn + +% Slow but quite useful. +% \debug_on:n { all } + +% REPL + +\cs_new:Nn \repl_loop: + { + % \ior_str_get_term is able to display a prompt on the same line, + % but this would make ./run far more complex for little benefit. + \iow_term:n {user>~} + \ior_str_get_term:nN {} \l_tmpa_str + \str_if_eq:VnF \l_tmpa_str {MAL_LATEX3_END_OF_INPUT} % from ./run + { + % Ignore empty lines, the MAL self-hosting relies on this + % *not* triggering an error. + \str_if_eq:VnF \l_tmpa_str {} + { + \iow_term:V \l_tmpa_str + } + \repl_loop: + } + } + +% ./run removes the normal LaTeX output. +\iow_term:n {MAL_LATEX3_START_OF_OUTPUT} + +\repl_loop: + +\iow_term:n {MAL_LATEX3_END_OF_OUTPUT} % for ./run + +\ExplSyntaxOff +\begin{document} +\end{document} diff --git a/impls/latex3/step1_read_print.tex b/impls/latex3/step1_read_print.tex new file mode 100644 index 0000000000..b96c7c95b5 --- /dev/null +++ b/impls/latex3/step1_read_print.tex @@ -0,0 +1,44 @@ +\documentclass{article} +\usepackage +% Uncomment this and \debug_on below when debugging. +% [enable-debug] + {expl3} +\usepackage{types} +\usepackage{printer} +\usepackage{reader} +\ExplSyntaxOn + +% Slow but quite useful. +% \debug_on:n { all } + +% REPL + +\cs_new:Nn \repl_loop: + { + % \ior_str_get_term is able to display a prompt on the same line, + % but this would make ./run far more complex for little benefit. + \iow_term:n {user>~} + \ior_str_get_term:nN {} \l_tmpa_str + \str_if_eq:VnF \l_tmpa_str {MAL_LATEX3_END_OF_INPUT} % from ./run + { + % Ignore empty lines, the MAL self-hosting relies on this + % *not* triggering an error. + \str_if_eq:VnF \l_tmpa_str {} + { + \mal_read_str: + \iow_term:x { \mal_printer_pr_str:VN \l_tmpa_tl \c_true_bool } + } + \repl_loop: + } + } + +% ./run removes the normal LaTeX output. +\iow_term:n {MAL_LATEX3_START_OF_OUTPUT} + +\repl_loop: + +\iow_term:n {MAL_LATEX3_END_OF_OUTPUT} % for ./run + +\ExplSyntaxOff +\begin{document} +\end{document} diff --git a/impls/latex3/step2_eval.tex b/impls/latex3/step2_eval.tex new file mode 100644 index 0000000000..f815a70cba --- /dev/null +++ b/impls/latex3/step2_eval.tex @@ -0,0 +1,173 @@ +\documentclass{article} +\usepackage +% Uncomment this and \debug_on below when debugging. +% [enable-debug] + {expl3} +\usepackage{types} +\usepackage{printer} +\usepackage{reader} +\ExplSyntaxOn + +% Slow but quite useful. +% \debug_on:n { all } + +% Step 2 + +\cs_new:Nn \mal_eval_map:nN + { + % \iow_term:n {eval_map~ast=#1~env=#2} + \mal_map_new: + \prop_map_inline:cn { #1 } + { + \str_if_eq:nnF { ##1 } { __meta__ } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##2 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \prop_map_break: } + { + \prop_put:cnV \l_tmpb_tl { ##1 } \l_tmpa_tl + \tl_set_eq:NN \l_tmpa_tl \l_tmpb_tl + } + } + } + } + +\cs_new:Nn \mal_eval_iterate_tl:nN + { + % The evaluated elements are appended to \l_tmpa_tl. + % \iow_term:n {eval_tl:~forms=#1~env=#2} + \tl_map_inline:nn { #1 } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \tl_map_break: } + { + \tl_set:Nx \l_tmpa_tl + { \exp_not:V \l_tmpb_tl { \exp_not:V \l_tmpa_tl } } + } + } + } +\cs_generate_variant:Nn \mal_eval_iterate_tl:nN { oN } + +% EVAL + +\cs_new:Nn \mal_fn_apply:nn + { + % \iow_term:n {fn_apply:~func=#1~args=#2} + \tl_if_head_eq_charcode:nNTF { #1 } b + { \use_none:nn #1 { #2 } } + { + \tl_set:Nx \l_tmpa_tl + { e s \tl_to_str:n { can~only~apply~functions } } + } + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_fn_apply:nn { nx, Vo, VV, xx } + +\cs_new:Nn \mal_eval_list:nN + { + % \iow_term:n {eval_mal_list~tl=#1~env=#2} + \tl_set:Nx \l_tmpa_tl { \tl_head:n {#1} } + \bool_case_true:nF + { + { \tl_if_eq_p:NN \l_tmpa_tl \c_empty_tl } + { \tl_set:Nn \l_tmpa_tl { l n } } + } + { + % \iow_term:n {eval_mal_list~apply_phase~tl=#1~env=#2} + \mal_eval:xN { \tl_head:n { #1 } } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \tl_clear:N \l_tmpa_tl + \mal_eval_iterate_tl:oN { \use_none:n #1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_fn_apply:VV \l_tmpb_tl \l_tmpa_tl } + } + } + } +\cs_generate_variant:Nn \mal_eval_list:nN { oN } + +\cs_new:Nn \mal_eval:nN + { + % \iow_term:n {EVAL:~ast=#1~env=#2} + \exp_args:Nx \token_case_charcode:NnF { \tl_head:n {#1} } + { + l + { \mal_eval_list:oN { \use_none:nn #1 } #2 } + y + { + \prop_get:NnNF #2 { #1 } \l_tmpa_tl + { + \tl_set:Nx \l_tmpa_tl + { e s \use_none:n #1 \tl_to_str:n { ~not~found } } + } + } + v + { + \tl_set:Nn \l_tmpa_tl { v n } + \mal_eval_iterate_tl:oN { \use_none:nn #1 } #2 + } + m + { \mal_eval_map:nN { #1 } #2 } + } + { \tl_set:Nn \l_tmpa_tl {#1} } + % \iow_term:n {EVAL:~ast=#1~returns} + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_eval:nN { oN, VN, xN } + +% REPL + +\prop_new:N \l_mal_repl_env_prop +\cs_new:Nn \mal_int_op:nnN + { + \tl_set:Nx \l_tmpa_tl + { i \int_eval:n { \use_none:n #1 #3 \use_none:n #2 } } + } +\cs_new:Nn \mal_add:n { \mal_int_op:nnN #1 + } +\cs_new:Nn \mal_sub:n { \mal_int_op:nnN #1 - } +\cs_new:Nn \mal_mul:n { \mal_int_op:nnN #1 * } +\cs_new:Nn \mal_div:n { \mal_int_op:nnN #1 / } +\prop_put:Nnn \l_mal_repl_env_prop { y + } { b n \mal_add:n } +\prop_put:Nnn \l_mal_repl_env_prop { y - } { b n \mal_sub:n } +\prop_put:Nnn \l_mal_repl_env_prop { y * } { b n \mal_mul:n } +\prop_put:Nnn \l_mal_repl_env_prop { y / } { b n \mal_div:n } + + +\cs_new:Nn \repl_loop: + { + % \ior_str_get_term is able to display a prompt on the same line, + % but this would make ./run far more complex for little benefit. + \iow_term:n {user>~} + \ior_str_get_term:nN {} \l_tmpa_str + \str_if_eq:VnF \l_tmpa_str {MAL_LATEX3_END_OF_INPUT} % from ./run + { + % Ignore empty lines, the MAL self-hosting relies on this + % *not* triggering an error. + \str_if_eq:VnF \l_tmpa_str {} + { + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl \l_mal_repl_env_prop } + \iow_term:x { \mal_printer_pr_str:VN \l_tmpa_tl \c_true_bool } + } + \repl_loop: + } + } + +% ./run removes the normal LaTeX output. +\iow_term:n {MAL_LATEX3_START_OF_OUTPUT} + +\repl_loop: + +\iow_term:n {MAL_LATEX3_END_OF_OUTPUT} % for ./run + +\ExplSyntaxOff +\begin{document} +\end{document} diff --git a/impls/latex3/step3_env.tex b/impls/latex3/step3_env.tex new file mode 100644 index 0000000000..9aa083f212 --- /dev/null +++ b/impls/latex3/step3_env.tex @@ -0,0 +1,222 @@ +\documentclass{article} +\usepackage +% Uncomment this and \debug_on below when debugging. +% [enable-debug] + {expl3} +\usepackage{types} +\usepackage{printer} +\usepackage{reader} +\usepackage{env} +\ExplSyntaxOn + +% Slow but quite useful. +% \debug_on:n { all } + +% Step 2 + +\cs_new:Nn \mal_eval_map:nN + { + % \iow_term:n {eval_map~ast=#1~env=#2} + \mal_map_new: + \prop_map_inline:cn { #1 } + { + \str_if_eq:nnF { ##1 } { __meta__ } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##2 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \prop_map_break: } + { + \prop_put:cnV \l_tmpb_tl { ##1 } \l_tmpa_tl + \tl_set_eq:NN \l_tmpa_tl \l_tmpb_tl + } + } + } + } + +\cs_new:Nn \mal_eval_iterate_tl:nN + { + % The evaluated elements are appended to \l_tmpa_tl. + % \iow_term:n {eval_tl:~forms=#1~env=#2} + \tl_map_inline:nn { #1 } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \tl_map_break: } + { + \tl_set:Nx \l_tmpa_tl + { \exp_not:V \l_tmpb_tl { \exp_not:V \l_tmpa_tl } } + } + } + } +\cs_generate_variant:Nn \mal_eval_iterate_tl:nN { oN } + +% Step 3 + +\tl_const:Nx \c_def_symbol { y \tl_to_str:n { def! } } +\tl_const:Nx \c_let_symbol { y \tl_to_str:n { let* } } +\tl_const:Nx \c_debug_eval_symbol { y \tl_to_str:n { DEBUG-EVAL } } + +\cs_new:Nn \mal_eval_let_loop:nNn + { + % \iow_term:n {mal_eval_let_loop~binds=#1~env=#2~form=#3} + \tl_if_empty:nTF { #1 } + { \mal_eval:nN { #3 } #2 } + { + \mal_eval:xN { \tl_item:nn { #1 } 2 } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \prop_put:NxV #2 { \tl_head:n { #1 } } \l_tmpa_tl + \mal_eval_let_loop:oNn { \use_none:nn #1 } #2 { #3 } + } + } + } +\cs_generate_variant:Nn \mal_eval_let_loop:nNn { ocn, oNn } + +\cs_new:Nn \mal_eval_let:nnnN + { + % \iow_term:n {mal_eval_let~let*=#1~binds=#2~form=#3~env=#4} + \mal_env_new:N #4 + \mal_eval_let_loop:ocn { \use_none:nn #2 } \l_mal_tmp_env_prop { #3 } + } + +% EVAL + +\cs_new:Nn \mal_fn_apply:nn + { + % \iow_term:n {fn_apply:~func=#1~args=#2} + \tl_if_head_eq_charcode:nNTF { #1 } b + { \use_none:nn #1 { #2 } } + { + \tl_set:Nx \l_tmpa_tl + { e s \tl_to_str:n { can~only~apply~functions } } + } + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_fn_apply:nn { nx, Vo, VV, xx } + +\cs_new:Nn \mal_eval_list:nN + { + % \iow_term:n {eval_mal_list~tl=#1~env=#2} + \tl_set:Nx \l_tmpa_tl { \tl_head:n {#1} } + \bool_case_true:nF + { + { \tl_if_eq_p:NN \l_tmpa_tl \c_empty_tl } + { \tl_set:Nn \l_tmpa_tl { l n } } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_def_symbol } + { + \mal_eval:oN { \use_iii:nnn #1 } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \tl_set:No \l_tmpb_tl { \use_ii:nnn #1 } + \prop_put:NVV #2 \l_tmpb_tl \l_tmpa_tl + } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_let_symbol } + { \mal_eval_let:nnnN #1 #2 } + } + { + % \iow_term:n {eval_mal_list~apply_phase~tl=#1~env=#2} + \mal_eval:xN { \tl_head:n { #1 } } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \tl_clear:N \l_tmpa_tl + \mal_eval_iterate_tl:oN { \use_none:n #1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_fn_apply:VV \l_tmpb_tl \l_tmpa_tl } + } + } + } +\cs_generate_variant:Nn \mal_eval_list:nN { oN } + +\cs_new:Nn \mal_eval:nN + { + % \iow_term:n {EVAL:~ast=#1~env=#2} + \mal_env_get:NVT #2 \c_debug_eval_symbol + { + \bool_lazy_or:nnF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \iow_term:x { EVAL: ~ \mal_printer_pr_str:nN { #1 } \c_true_bool } } + } + \exp_args:Nx \token_case_charcode:NnF { \tl_head:n {#1} } + { + l + { \mal_eval_list:oN { \use_none:nn #1 } #2 } + y + { + \mal_env_get:NnF #2 { #1 } + { + \tl_set:Nx \l_tmpa_tl + { e s \use_none:n #1 \tl_to_str:n { ~not~found } } + } + } + v + { + \tl_set:Nn \l_tmpa_tl { v n } + \mal_eval_iterate_tl:oN { \use_none:nn #1 } #2 + } + m + { \mal_eval_map:nN { #1 } #2 } + } + { \tl_set:Nn \l_tmpa_tl {#1} } + % \iow_term:n {EVAL:~ast=#1~returns} + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_eval:nN { nc, oN, VN, xc, xN } + +% REPL + +\cs_new:Nn \mal_int_op:nnN + { + \tl_set:Nx \l_tmpa_tl + { i \int_eval:n { \use_none:n #1 #3 \use_none:n #2 } } + } +\cs_new:Nn \mal_add:n { \mal_int_op:nnN #1 + } +\cs_new:Nn \mal_sub:n { \mal_int_op:nnN #1 - } +\cs_new:Nn \mal_mul:n { \mal_int_op:nnN #1 * } +\cs_new:Nn \mal_div:n { \mal_int_op:nnN #1 / } +\prop_put:Nnn \l_mal_repl_env_prop { y + } { b n \mal_add:n } +\prop_put:Nnn \l_mal_repl_env_prop { y - } { b n \mal_sub:n } +\prop_put:Nnn \l_mal_repl_env_prop { y * } { b n \mal_mul:n } +\prop_put:Nnn \l_mal_repl_env_prop { y / } { b n \mal_div:n } + + +\cs_new:Nn \repl_loop: + { + % \ior_str_get_term is able to display a prompt on the same line, + % but this would make ./run far more complex for little benefit. + \iow_term:n {user>~} + \ior_str_get_term:nN {} \l_tmpa_str + \str_if_eq:VnF \l_tmpa_str {MAL_LATEX3_END_OF_INPUT} % from ./run + { + % Ignore empty lines, the MAL self-hosting relies on this + % *not* triggering an error. + \str_if_eq:VnF \l_tmpa_str {} + { + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl \l_mal_repl_env_prop } + \iow_term:x { \mal_printer_pr_str:VN \l_tmpa_tl \c_true_bool } + } + \repl_loop: + } + } + +% ./run removes the normal LaTeX output. +\iow_term:n {MAL_LATEX3_START_OF_OUTPUT} + +\repl_loop: + +\iow_term:n {MAL_LATEX3_END_OF_OUTPUT} % for ./run + +\ExplSyntaxOff +\begin{document} +\end{document} diff --git a/impls/latex3/step4_if_fn_do.tex b/impls/latex3/step4_if_fn_do.tex new file mode 100644 index 0000000000..cc88332e67 --- /dev/null +++ b/impls/latex3/step4_if_fn_do.tex @@ -0,0 +1,293 @@ +\documentclass{article} +\usepackage +% Uncomment this and \debug_on below when debugging. +% [enable-debug] + {expl3} +\usepackage{types} +\usepackage{printer} +\usepackage{reader} +\usepackage{env} +\usepackage{core} +\ExplSyntaxOn + +% Slow but quite useful. +% \debug_on:n { all } + +% Step 2 + +\cs_new:Nn \mal_eval_map:nN + { + % \iow_term:n {eval_map~ast=#1~env=#2} + \mal_map_new: + \prop_map_inline:cn { #1 } + { + \str_if_eq:nnF { ##1 } { __meta__ } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##2 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \prop_map_break: } + { + \prop_put:cnV \l_tmpb_tl { ##1 } \l_tmpa_tl + \tl_set_eq:NN \l_tmpa_tl \l_tmpb_tl + } + } + } + } + +\cs_new:Nn \mal_eval_iterate_tl:nN + { + % The evaluated elements are appended to \l_tmpa_tl. + % \iow_term:n {eval_tl:~forms=#1~env=#2} + \tl_map_inline:nn { #1 } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \tl_map_break: } + { + \tl_set:Nx \l_tmpa_tl + { \exp_not:V \l_tmpb_tl { \exp_not:V \l_tmpa_tl } } + } + } + } +\cs_generate_variant:Nn \mal_eval_iterate_tl:nN { oN } + +% Step 3 + +\tl_const:Nx \c_def_symbol { y \tl_to_str:n { def! } } +\tl_const:Nx \c_let_symbol { y \tl_to_str:n { let* } } +\tl_const:Nx \c_debug_eval_symbol { y \tl_to_str:n { DEBUG-EVAL } } + +\cs_new:Nn \mal_eval_let_loop:nNn + { + % \iow_term:n {mal_eval_let_loop~binds=#1~env=#2~form=#3} + \tl_if_empty:nTF { #1 } + { \mal_eval:nN { #3 } #2 } + { + \mal_eval:xN { \tl_item:nn { #1 } 2 } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \prop_put:NxV #2 { \tl_head:n { #1 } } \l_tmpa_tl + \mal_eval_let_loop:oNn { \use_none:nn #1 } #2 { #3 } + } + } + } +\cs_generate_variant:Nn \mal_eval_let_loop:nNn { ocn, oNn } + +\cs_new:Nn \mal_eval_let:nnnN + { + % \iow_term:n {mal_eval_let~let*=#1~binds=#2~form=#3~env=#4} + \mal_env_new:N #4 + \mal_eval_let_loop:ocn { \use_none:nn #2 } \l_mal_tmp_env_prop { #3 } + } + +% Step 4 + +\tl_const:Nx \c_if_symbol { y \tl_to_str:n { if } } +\tl_const:Nx \c_do_symbol { y \tl_to_str:n { do } } +\tl_const:Nx \c_fn_symbol { y \tl_to_str:n { fn* } } + +\cs_new:Nn \mal_eval_if:nnnN + { + % \iow_term:n {if~test=#2~then=#3~env=#4} + \mal_eval:nN {#2} #4 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \tl_set:Nn \l_tmpa_tl { n } } + { \mal_eval:nN {#3} #4 } + } + } + +\cs_new:Nn \mal_eval_if:nnnnN + { + % \iow_term:n {if~test=#2~then=#3~else=#4~env=#5} + \mal_eval:nN {#2} #5 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \mal_eval:nN { #4 } #5 } + { \mal_eval:nN { #3 } #5 } + } + } + +\cs_new:Nn \mal_fn:nnnN + { + % \iow_term:n {fn*~params=#2~implem=#3~env=#4} + \tl_set:Nx \l_tmpa_tl { \exp_not:n { u n { #3 } #4 } \use_none:nn #2 } + % \iow_term:V \l_tmpa_tl + } + +% EVAL + +\cs_new:Nn \mal_fn_apply:nn + { + % \iow_term:n {fn_apply:~func=#1~args=#2} + \tl_if_head_eq_charcode:nNTF { #1 } b + { \use_none:nn #1 { #2 } } + { + \tl_if_head_eq_charcode:nNTF { #1 } u + { + \exp_args:Nx \mal_env_new:N { \tl_item:nn { #1 } { 4 } } + \mal_env_set_keys_values:on { \use_none:nnnn #1 } { #2 } + \mal_eval:xc { \tl_item:nn { #1 } { 3 } } \l_mal_tmp_env_prop + } + { + \tl_set:Nx \l_tmpa_tl + { e s \tl_to_str:n { can~only~apply~functions } } + } + } + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_fn_apply:nn { nx, Vo, VV, xx } + +\cs_new:Nn \mal_eval_list:nN + { + % \iow_term:n {eval_mal_list~tl=#1~env=#2} + \tl_set:Nx \l_tmpa_tl { \tl_head:n {#1} } + \bool_case_true:nF + { + { \tl_if_eq_p:NN \l_tmpa_tl \c_empty_tl } + { \tl_set:Nn \l_tmpa_tl { l n } } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_def_symbol } + { + \mal_eval:oN { \use_iii:nnn #1 } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \tl_set:No \l_tmpb_tl { \use_ii:nnn #1 } + \prop_put:NVV #2 \l_tmpb_tl \l_tmpa_tl + } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_let_symbol } + { \mal_eval_let:nnnN #1 #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_if_symbol } + { + \tl_if_empty:oTF { \use_none:nnn #1 } + { \mal_eval_if:nnnN #1 #2 } + { \mal_eval_if:nnnnN #1 #2 } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_do_symbol } + { + \tl_map_inline:on { \use_none:n #1 } + { + \mal_eval:nN { ##1 } #2 + \tl_if_head_eq_charcode:VNT \l_tmpa_tl e { \tl_map_break: } + } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_fn_symbol } + { \mal_fn:nnnN #1 #2 } + } + { + % \iow_term:n {eval_mal_list~apply_phase~tl=#1~env=#2} + \mal_eval:xN { \tl_head:n { #1 } } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \tl_clear:N \l_tmpa_tl + \mal_eval_iterate_tl:oN { \use_none:n #1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_fn_apply:VV \l_tmpb_tl \l_tmpa_tl } + } + } + } +\cs_generate_variant:Nn \mal_eval_list:nN { oN } + +\cs_new:Nn \mal_eval:nN + { + % \iow_term:n {EVAL:~ast=#1~env=#2} + \mal_env_get:NVT #2 \c_debug_eval_symbol + { + \bool_lazy_or:nnF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \iow_term:x { EVAL: ~ \mal_printer_pr_str:nN { #1 } \c_true_bool } } + } + \exp_args:Nx \token_case_charcode:NnF { \tl_head:n {#1} } + { + l + { \mal_eval_list:oN { \use_none:nn #1 } #2 } + y + { + \mal_env_get:NnF #2 { #1 } + { + \tl_set:Nx \l_tmpa_tl + { e s \use_none:n #1 \tl_to_str:n { ~not~found } } + } + } + v + { + \tl_set:Nn \l_tmpa_tl { v n } + \mal_eval_iterate_tl:oN { \use_none:nn #1 } #2 + } + m + { \mal_eval_map:nN { #1 } #2 } + } + { \tl_set:Nn \l_tmpa_tl {#1} } + % \iow_term:n {EVAL:~ast=#1~returns} + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_eval:nN { nc, oN, VN, xc, xN } + +% REPL + +\cs_new:Nn \repl_loop: + { + % \ior_str_get_term is able to display a prompt on the same line, + % but this would make ./run far more complex for little benefit. + \iow_term:n {user>~} + \ior_str_get_term:nN {} \l_tmpa_str + \str_if_eq:VnF \l_tmpa_str {MAL_LATEX3_END_OF_INPUT} % from ./run + { + % Ignore empty lines, the MAL self-hosting relies on this + % *not* triggering an error. + \str_if_eq:VnF \l_tmpa_str {} + { + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl \l_mal_repl_env_prop } + \iow_term:x { \mal_printer_pr_str:VN \l_tmpa_tl \c_true_bool } + } + \repl_loop: + } + } + +\cs_new:Nn \mal_re:n + { + % \iow_term:n {re:~#1} + \str_set:Nn \l_tmpa_str {#1} + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl \l_mal_repl_env_prop } + \tl_if_head_eq_charcode:VNT \l_tmpa_tl e + { + \iow_term:n {error~during~startup~#1} + \iow_term:x { \mal_printer_pr_str:VN \l_tmpa_tl \c_true_bool } + Trigger a missing begin document error + } + } + +\mal_re:n { (def!~not~(fn*~(a)~(if~a~false~true))) } + +% ./run removes the normal LaTeX output. +\iow_term:n {MAL_LATEX3_START_OF_OUTPUT} + +\repl_loop: + +\iow_term:n {MAL_LATEX3_END_OF_OUTPUT} % for ./run + +\ExplSyntaxOff +\begin{document} +\end{document} diff --git a/impls/latex3/step6_file.tex b/impls/latex3/step6_file.tex new file mode 100644 index 0000000000..188165cbe7 --- /dev/null +++ b/impls/latex3/step6_file.tex @@ -0,0 +1,314 @@ +\documentclass{article} +\usepackage +% Uncomment this and \debug_on below when debugging. +% [enable-debug] + {expl3} +\usepackage{types} +\usepackage{printer} +\usepackage{reader} +\usepackage{env} +\usepackage{core} +\ExplSyntaxOn + +% Slow but quite useful. +% \debug_on:n { all } + +% Step 2 + +\cs_new:Nn \mal_eval_map:nN + { + % \iow_term:n {eval_map~ast=#1~env=#2} + \mal_map_new: + \prop_map_inline:cn { #1 } + { + \str_if_eq:nnF { ##1 } { __meta__ } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##2 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \prop_map_break: } + { + \prop_put:cnV \l_tmpb_tl { ##1 } \l_tmpa_tl + \tl_set_eq:NN \l_tmpa_tl \l_tmpb_tl + } + } + } + } + +\cs_new:Nn \mal_eval_iterate_tl:nN + { + % The evaluated elements are appended to \l_tmpa_tl. + % \iow_term:n {eval_tl:~forms=#1~env=#2} + \tl_map_inline:nn { #1 } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \tl_map_break: } + { + \tl_set:Nx \l_tmpa_tl + { \exp_not:V \l_tmpb_tl { \exp_not:V \l_tmpa_tl } } + } + } + } +\cs_generate_variant:Nn \mal_eval_iterate_tl:nN { oN } + +% Step 3 + +\tl_const:Nx \c_def_symbol { y \tl_to_str:n { def! } } +\tl_const:Nx \c_let_symbol { y \tl_to_str:n { let* } } +\tl_const:Nx \c_debug_eval_symbol { y \tl_to_str:n { DEBUG-EVAL } } + +\cs_new:Nn \mal_eval_let_loop:nNn + { + % \iow_term:n {mal_eval_let_loop~binds=#1~env=#2~form=#3} + \tl_if_empty:nTF { #1 } + { \mal_eval:nN { #3 } #2 } + { + \mal_eval:xN { \tl_item:nn { #1 } 2 } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \prop_put:NxV #2 { \tl_head:n { #1 } } \l_tmpa_tl + \mal_eval_let_loop:oNn { \use_none:nn #1 } #2 { #3 } + } + } + } +\cs_generate_variant:Nn \mal_eval_let_loop:nNn { ocn, oNn } + +\cs_new:Nn \mal_eval_let:nnnN + { + % \iow_term:n {mal_eval_let~let*=#1~binds=#2~form=#3~env=#4} + \mal_env_new:N #4 + \mal_eval_let_loop:ocn { \use_none:nn #2 } \l_mal_tmp_env_prop { #3 } + } + +% Step 4 + +\tl_const:Nx \c_if_symbol { y \tl_to_str:n { if } } +\tl_const:Nx \c_do_symbol { y \tl_to_str:n { do } } +\tl_const:Nx \c_fn_symbol { y \tl_to_str:n { fn* } } + +\cs_new:Nn \mal_eval_if:nnnN + { + % \iow_term:n {if~test=#2~then=#3~env=#4} + \mal_eval:nN {#2} #4 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \tl_set:Nn \l_tmpa_tl { n } } + { \mal_eval:nN {#3} #4 } + } + } + +\cs_new:Nn \mal_eval_if:nnnnN + { + % \iow_term:n {if~test=#2~then=#3~else=#4~env=#5} + \mal_eval:nN {#2} #5 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \mal_eval:nN { #4 } #5 } + { \mal_eval:nN { #3 } #5 } + } + } + +\cs_new:Nn \mal_fn:nnnN + { + % \iow_term:n {fn*~params=#2~implem=#3~env=#4} + \tl_set:Nx \l_tmpa_tl { \exp_not:n { u n { #3 } #4 } \use_none:nn #2 } + % \iow_term:V \l_tmpa_tl + } + +% EVAL + +\cs_new:Nn \mal_fn_apply:nn + { + % \iow_term:n {fn_apply:~func=#1~args=#2} + \tl_if_head_eq_charcode:nNTF { #1 } b + { \use_none:nn #1 { #2 } } + { + \tl_if_head_eq_charcode:nNTF { #1 } u + { + \exp_args:Nx \mal_env_new:N { \tl_item:nn { #1 } { 4 } } + \mal_env_set_keys_values:on { \use_none:nnnn #1 } { #2 } + \mal_eval:xc { \tl_item:nn { #1 } { 3 } } \l_mal_tmp_env_prop + } + { + \tl_set:Nx \l_tmpa_tl + { e s \tl_to_str:n { can~only~apply~functions } } + } + } + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_fn_apply:nn { nx, Vo, VV, xx } + +\cs_new:Nn \mal_eval_list:nN + { + % \iow_term:n {eval_mal_list~tl=#1~env=#2} + \tl_set:Nx \l_tmpa_tl { \tl_head:n {#1} } + \bool_case_true:nF + { + { \tl_if_eq_p:NN \l_tmpa_tl \c_empty_tl } + { \tl_set:Nn \l_tmpa_tl { l n } } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_def_symbol } + { + \mal_eval:oN { \use_iii:nnn #1 } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \tl_set:No \l_tmpb_tl { \use_ii:nnn #1 } + \prop_put:NVV #2 \l_tmpb_tl \l_tmpa_tl + } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_let_symbol } + { \mal_eval_let:nnnN #1 #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_if_symbol } + { + \tl_if_empty:oTF { \use_none:nnn #1 } + { \mal_eval_if:nnnN #1 #2 } + { \mal_eval_if:nnnnN #1 #2 } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_do_symbol } + { + \tl_map_inline:on { \use_none:n #1 } + { + \mal_eval:nN { ##1 } #2 + \tl_if_head_eq_charcode:VNT \l_tmpa_tl e { \tl_map_break: } + } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_fn_symbol } + { \mal_fn:nnnN #1 #2 } + } + { + % \iow_term:n {eval_mal_list~apply_phase~tl=#1~env=#2} + \mal_eval:xN { \tl_head:n { #1 } } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \tl_clear:N \l_tmpa_tl + \mal_eval_iterate_tl:oN { \use_none:n #1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_fn_apply:VV \l_tmpb_tl \l_tmpa_tl } + } + } + } +\cs_generate_variant:Nn \mal_eval_list:nN { oN } + +\cs_new:Nn \mal_eval:nN + { + % \iow_term:n {EVAL:~ast=#1~env=#2} + \mal_env_get:NVT #2 \c_debug_eval_symbol + { + \bool_lazy_or:nnF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \iow_term:x { EVAL: ~ \mal_printer_pr_str:nN { #1 } \c_true_bool } } + } + \exp_args:Nx \token_case_charcode:NnF { \tl_head:n {#1} } + { + l + { \mal_eval_list:oN { \use_none:nn #1 } #2 } + y + { + \mal_env_get:NnF #2 { #1 } + { + \tl_set:Nx \l_tmpa_tl + { e s \use_none:n #1 \tl_to_str:n { ~not~found } } + } + } + v + { + \tl_set:Nn \l_tmpa_tl { v n } + \mal_eval_iterate_tl:oN { \use_none:nn #1 } #2 + } + m + { \mal_eval_map:nN { #1 } #2 } + } + { \tl_set:Nn \l_tmpa_tl {#1} } + % \iow_term:n {EVAL:~ast=#1~returns} + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_eval:nN { nc, oN, VN, xc, xN } + +% REPL + +\cs_new:Nn \repl_loop: + { + % \ior_str_get_term is able to display a prompt on the same line, + % but this would make ./run far more complex for little benefit. + \iow_term:n {user>~} + \ior_str_get_term:nN {} \l_tmpa_str + \str_if_eq:VnF \l_tmpa_str {MAL_LATEX3_END_OF_INPUT} % from ./run + { + % Ignore empty lines, the MAL self-hosting relies on this + % *not* triggering an error. + \str_if_eq:VnF \l_tmpa_str {} + { + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl \l_mal_repl_env_prop } + \iow_term:x { \mal_printer_pr_str:VN \l_tmpa_tl \c_true_bool } + } + \repl_loop: + } + } + +\cs_new:Nn \mal_re:n + { + % \iow_term:n {re:~#1} + \str_set:Nn \l_tmpa_str {#1} + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl \l_mal_repl_env_prop } + \tl_if_head_eq_charcode:VNT \l_tmpa_tl e + { + \iow_term:n {error~during~startup~#1} + \iow_term:x { \mal_printer_pr_str:VN \l_tmpa_tl \c_true_bool } + Trigger a missing begin document error + } + } +\cs_generate_variant:Nn \mal_re:n { x } + +\mal_re:n { (def!~not~(fn*~(a)~(if~a~false~true))) } +\mal_re:x { (def!~load-file~(fn*~(f) + ~(eval~(read-string~(str~"(do~"~(slurp~f)~"\c_backslash_str nnil)"))))) +} + +\mal_def_builtin:nnn { eval } { eval_builtin } + { \mal_eval:nN #1 \l_mal_repl_env_prop } + +\tl_clear:N \l_tmpa_tl +\ior_open:Nn \g_tmpa_ior {argv} +\ior_str_map_inline:Nn \g_tmpa_ior { + \tl_put_right:Nn \l_tmpa_tl { { s #1 } } +} +\ior_close:N \g_tmpa_ior +\prop_put:Nxx \l_mal_repl_env_prop { y \tl_to_str:n { *ARGV* } } + { l n \tl_tail:V \l_tmpa_tl } + +% ./run removes the normal LaTeX output. +\iow_term:n {MAL_LATEX3_START_OF_OUTPUT} + +\tl_if_empty:NTF \l_tmpa_tl { + \repl_loop: +} { + \tl_set:Nx \l_tmpa_tl { \tl_head:V \l_tmpa_tl } + \mal_re:x { (load-file~" \tl_tail:V \l_tmpa_tl ") } % without initial s +} + +\iow_term:n {MAL_LATEX3_END_OF_OUTPUT} % for ./run + +\ExplSyntaxOff +\begin{document} +\end{document} diff --git a/impls/latex3/step7_quote.tex b/impls/latex3/step7_quote.tex new file mode 100644 index 0000000000..8c689f956c --- /dev/null +++ b/impls/latex3/step7_quote.tex @@ -0,0 +1,381 @@ +\documentclass{article} +\usepackage +% Uncomment this and \debug_on below when debugging. +% [enable-debug] + {expl3} +\usepackage{types} +\usepackage{printer} +\usepackage{reader} +\usepackage{env} +\usepackage{core} +\ExplSyntaxOn + +% Slow but quite useful. +% \debug_on:n { all } + +% Step 2 + +\cs_new:Nn \mal_eval_map:nN + { + % \iow_term:n {eval_map~ast=#1~env=#2} + \mal_map_new: + \prop_map_inline:cn { #1 } + { + \str_if_eq:nnF { ##1 } { __meta__ } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##2 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \prop_map_break: } + { + \prop_put:cnV \l_tmpb_tl { ##1 } \l_tmpa_tl + \tl_set_eq:NN \l_tmpa_tl \l_tmpb_tl + } + } + } + } + +\cs_new:Nn \mal_eval_iterate_tl:nN + { + % The evaluated elements are appended to \l_tmpa_tl. + % \iow_term:n {eval_tl:~forms=#1~env=#2} + \tl_map_inline:nn { #1 } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \tl_map_break: } + { + \tl_set:Nx \l_tmpa_tl + { \exp_not:V \l_tmpb_tl { \exp_not:V \l_tmpa_tl } } + } + } + } +\cs_generate_variant:Nn \mal_eval_iterate_tl:nN { oN } + +% Step 3 + +\tl_const:Nx \c_def_symbol { y \tl_to_str:n { def! } } +\tl_const:Nx \c_let_symbol { y \tl_to_str:n { let* } } +\tl_const:Nx \c_debug_eval_symbol { y \tl_to_str:n { DEBUG-EVAL } } + +\cs_new:Nn \mal_eval_let_loop:nNn + { + % \iow_term:n {mal_eval_let_loop~binds=#1~env=#2~form=#3} + \tl_if_empty:nTF { #1 } + { \mal_eval:nN { #3 } #2 } + { + \mal_eval:xN { \tl_item:nn { #1 } 2 } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \prop_put:NxV #2 { \tl_head:n { #1 } } \l_tmpa_tl + \mal_eval_let_loop:oNn { \use_none:nn #1 } #2 { #3 } + } + } + } +\cs_generate_variant:Nn \mal_eval_let_loop:nNn { ocn, oNn } + +\cs_new:Nn \mal_eval_let:nnnN + { + % \iow_term:n {mal_eval_let~let*=#1~binds=#2~form=#3~env=#4} + \mal_env_new:N #4 + \mal_eval_let_loop:ocn { \use_none:nn #2 } \l_mal_tmp_env_prop { #3 } + } + +% Step 4 + +\tl_const:Nx \c_if_symbol { y \tl_to_str:n { if } } +\tl_const:Nx \c_do_symbol { y \tl_to_str:n { do } } +\tl_const:Nx \c_fn_symbol { y \tl_to_str:n { fn* } } + +\cs_new:Nn \mal_eval_if:nnnN + { + % \iow_term:n {if~test=#2~then=#3~env=#4} + \mal_eval:nN {#2} #4 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \tl_set:Nn \l_tmpa_tl { n } } + { \mal_eval:nN {#3} #4 } + } + } + +\cs_new:Nn \mal_eval_if:nnnnN + { + % \iow_term:n {if~test=#2~then=#3~else=#4~env=#5} + \mal_eval:nN {#2} #5 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \mal_eval:nN { #4 } #5 } + { \mal_eval:nN { #3 } #5 } + } + } + +\cs_new:Nn \mal_fn:nnnN + { + % \iow_term:n {fn*~params=#2~implem=#3~env=#4} + \tl_set:Nx \l_tmpa_tl { \exp_not:n { u n { #3 } #4 } \use_none:nn #2 } + % \iow_term:V \l_tmpa_tl + } + +% Step 7 + +\tl_const:Nx \c_quote_symbol { y \tl_to_str:n { quote } } +\tl_const:Nx \c_quasiquote_symbol { y \tl_to_str:n { quasiquote } } +\tl_const:Nx \c_quasiquoteexpand_symbol { y \tl_to_str:n { quasiquoteexpand } } +\tl_const:Nx \c_splice_unquote_symbol { y \tl_to_str:n { splice-unquote } } +\tl_const:Nx \c_unquote_symbol { y \tl_to_str:n { unquote } } + +\cs_new:Nn \mal_quasiquote_item:n + { + \bool_lazy_and:nnTF + { \tl_if_head_eq_charcode_p:nN { #1 } l } + { \str_if_eq_p:eV { \tl_item:nn { #1 } { 3 } } \c_splice_unquote_symbol } + { { y \tl_to_str:n { concat } } { \exp_not:o { \use_iv:nnnn #1 } } } + { { y \tl_to_str:n { cons } } { \mal_quasiquote:n { #1 } } } + } +\cs_generate_variant:Nn \mal_quasiquote_item:n { e } + +\cs_new:Nn \mal_qq_loop:n + { + l n + \tl_if_empty:nF {#1} + { + \mal_quasiquote_item:e { \tl_head:n { #1 } } + { \mal_qq_loop:o { \use_none:n #1 } } + } + } +\cs_generate_variant:Nn \mal_qq_loop:n { o } + +\cs_new:Nn \mal_quasiquote:n + { + \tl_if_head_eq_charcode:nNTF { #1 } l + { + \str_if_eq:eVTF { \tl_item:nn { #1 } 3 } \c_unquote_symbol + { \exp_not:o { \use_iv:nnnn #1 } } + { \mal_qq_loop:o { \use_none:nn #1 } } + } + { + \tl_if_head_eq_charcode:nNTF { #1 } v + { + l n { y \tl_to_str:n { vec } } + { \mal_qq_loop:o { \use_none:nn #1 } } + } + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:nN { #1 } m } + { \tl_if_head_eq_charcode_p:nN { #1 } y } + { l n { \c_quote_symbol } { \exp_not:n { #1 } } } + { \exp_not:n { #1 } } + } + } + } + +\cs_new:Nn \mal_eval_quasiquote:nn { \mal_quasiquote:n { #2 } } + +% EVAL + +\cs_new:Nn \mal_fn_apply:nn + { + % \iow_term:n {fn_apply:~func=#1~args=#2} + \tl_if_head_eq_charcode:nNTF { #1 } b + { \use_none:nn #1 { #2 } } + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:nN { #1 } u } + { \tl_if_head_eq_charcode_p:nN { #1 } c } + { + \exp_args:Nx \mal_env_new:N { \tl_item:nn { #1 } { 4 } } + \mal_env_set_keys_values:on { \use_none:nnnn #1 } { #2 } + \mal_eval:xc { \tl_item:nn { #1 } { 3 } } \l_mal_tmp_env_prop + } + { + \tl_set:Nx \l_tmpa_tl + { e s \tl_to_str:n { can~only~apply~functions } } + } + } + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_fn_apply:nn { nx, Vo, VV, xx } + +\cs_new:Nn \mal_eval_list:nN + { + % \iow_term:n {eval_mal_list~tl=#1~env=#2} + \tl_set:Nx \l_tmpa_tl { \tl_head:n {#1} } + \bool_case_true:nF + { + { \tl_if_eq_p:NN \l_tmpa_tl \c_empty_tl } + { \tl_set:Nn \l_tmpa_tl { l n } } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_def_symbol } + { + \mal_eval:oN { \use_iii:nnn #1 } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \tl_set:No \l_tmpb_tl { \use_ii:nnn #1 } + \prop_put:NVV #2 \l_tmpb_tl \l_tmpa_tl + } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_let_symbol } + { \mal_eval_let:nnnN #1 #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_if_symbol } + { + \tl_if_empty:oTF { \use_none:nnn #1 } + { \mal_eval_if:nnnN #1 #2 } + { \mal_eval_if:nnnnN #1 #2 } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_do_symbol } + { + \tl_map_inline:on { \use_none:n #1 } + { + \mal_eval:nN { ##1 } #2 + \tl_if_head_eq_charcode:VNT \l_tmpa_tl e { \tl_map_break: } + } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_fn_symbol } + { \mal_fn:nnnN #1 #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_quote_symbol } + { \tl_set:No \l_tmpa_tl { \use_ii:nn #1 } } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_quasiquote_symbol } + { \mal_eval:xN { \mal_eval_quasiquote:nn #1 } #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_quasiquoteexpand_symbol } + { \tl_set:Nx \l_tmpa_tl { \mal_eval_quasiquote:nn #1 } } + + } + { + % \iow_term:n {eval_mal_list~apply_phase~tl=#1~env=#2} + \mal_eval:xN { \tl_head:n { #1 } } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \tl_clear:N \l_tmpa_tl + \mal_eval_iterate_tl:oN { \use_none:n #1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_fn_apply:VV \l_tmpb_tl \l_tmpa_tl } + } + } + } +\cs_generate_variant:Nn \mal_eval_list:nN { oN } + +\cs_new:Nn \mal_eval:nN + { + % \iow_term:n {EVAL:~ast=#1~env=#2} + \mal_env_get:NVT #2 \c_debug_eval_symbol + { + \bool_lazy_or:nnF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \iow_term:x { EVAL: ~ \mal_printer_pr_str:nN { #1 } \c_true_bool } } + } + \exp_args:Nx \token_case_charcode:NnF { \tl_head:n {#1} } + { + l + { \mal_eval_list:oN { \use_none:nn #1 } #2 } + y + { + \mal_env_get:NnF #2 { #1 } + { + \tl_set:Nx \l_tmpa_tl + { e s \use_none:n #1 \tl_to_str:n { ~not~found } } + } + } + v + { + \tl_set:Nn \l_tmpa_tl { v n } + \mal_eval_iterate_tl:oN { \use_none:nn #1 } #2 + } + m + { \mal_eval_map:nN { #1 } #2 } + } + { \tl_set:Nn \l_tmpa_tl {#1} } + % \iow_term:n {EVAL:~ast=#1~returns} + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_eval:nN { nc, oN, VN, xc, xN } + +% REPL + +\cs_new:Nn \repl_loop: + { + % \ior_str_get_term is able to display a prompt on the same line, + % but this would make ./run far more complex for little benefit. + \iow_term:n {user>~} + \ior_str_get_term:nN {} \l_tmpa_str + \str_if_eq:VnF \l_tmpa_str {MAL_LATEX3_END_OF_INPUT} % from ./run + { + % Ignore empty lines, the MAL self-hosting relies on this + % *not* triggering an error. + \str_if_eq:VnF \l_tmpa_str {} + { + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl \l_mal_repl_env_prop } + \iow_term:x { \mal_printer_pr_str:VN \l_tmpa_tl \c_true_bool } + } + \repl_loop: + } + } + +\cs_new:Nn \mal_re:n + { + % \iow_term:n {re:~#1} + \str_set:Nn \l_tmpa_str {#1} + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl \l_mal_repl_env_prop } + \tl_if_head_eq_charcode:VNT \l_tmpa_tl e + { + \iow_term:n {error~during~startup~#1} + \iow_term:x { \mal_printer_pr_str:VN \l_tmpa_tl \c_true_bool } + Trigger a missing begin document error + } + } +\cs_generate_variant:Nn \mal_re:n { x } + +\mal_re:n { (def!~not~(fn*~(a)~(if~a~false~true))) } +\mal_re:x { (def!~load-file~(fn*~(f) + ~(eval~(read-string~(str~"(do~"~(slurp~f)~"\c_backslash_str nnil)"))))) +} + +\mal_def_builtin:nnn { eval } { eval_builtin } + { \mal_eval:nN #1 \l_mal_repl_env_prop } + +\tl_clear:N \l_tmpa_tl +\ior_open:Nn \g_tmpa_ior {argv} +\ior_str_map_inline:Nn \g_tmpa_ior { + \tl_put_right:Nn \l_tmpa_tl { { s #1 } } +} +\ior_close:N \g_tmpa_ior +\prop_put:Nxx \l_mal_repl_env_prop { y \tl_to_str:n { *ARGV* } } + { l n \tl_tail:V \l_tmpa_tl } + +% ./run removes the normal LaTeX output. +\iow_term:n {MAL_LATEX3_START_OF_OUTPUT} + +\tl_if_empty:NTF \l_tmpa_tl { + \repl_loop: +} { + \tl_set:Nx \l_tmpa_tl { \tl_head:V \l_tmpa_tl } + \mal_re:x { (load-file~" \tl_tail:V \l_tmpa_tl ") } % without initial s +} + +\iow_term:n {MAL_LATEX3_END_OF_OUTPUT} % for ./run + +\ExplSyntaxOff +\begin{document} +\end{document} diff --git a/impls/latex3/step8_macros.tex b/impls/latex3/step8_macros.tex new file mode 100644 index 0000000000..7ea08c1e64 --- /dev/null +++ b/impls/latex3/step8_macros.tex @@ -0,0 +1,443 @@ +\documentclass{article} +\usepackage +% Uncomment this and \debug_on below when debugging. +% [enable-debug] + {expl3} +\usepackage{types} +\usepackage{printer} +\usepackage{reader} +\usepackage{env} +\usepackage{core} +\ExplSyntaxOn + +% Slow but quite useful. +% \debug_on:n { all } + +% Step 2 + +\cs_new:Nn \mal_eval_map:nN + { + % \iow_term:n {eval_map~ast=#1~env=#2} + \mal_map_new: + \prop_map_inline:cn { #1 } + { + \str_if_eq:nnF { ##1 } { __meta__ } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##2 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \prop_map_break: } + { + \prop_put:cnV \l_tmpb_tl { ##1 } \l_tmpa_tl + \tl_set_eq:NN \l_tmpa_tl \l_tmpb_tl + } + } + } + } + +\cs_new:Nn \mal_eval_iterate_tl:nN + { + % The evaluated elements are appended to \l_tmpa_tl. + % \iow_term:n {eval_tl:~forms=#1~env=#2} + \tl_map_inline:nn { #1 } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \tl_map_break: } + { + \tl_set:Nx \l_tmpa_tl + { \exp_not:V \l_tmpb_tl { \exp_not:V \l_tmpa_tl } } + } + } + } +\cs_generate_variant:Nn \mal_eval_iterate_tl:nN { oN } + +% Step 3 + +\tl_const:Nx \c_def_symbol { y \tl_to_str:n { def! } } +\tl_const:Nx \c_let_symbol { y \tl_to_str:n { let* } } +\tl_const:Nx \c_debug_eval_symbol { y \tl_to_str:n { DEBUG-EVAL } } + +\cs_new:Nn \mal_eval_let_loop:nNn + { + % \iow_term:n {mal_eval_let_loop~binds=#1~env=#2~form=#3} + \tl_if_empty:nTF { #1 } + { \mal_eval:nN { #3 } #2 } + { + \mal_eval:xN { \tl_item:nn { #1 } 2 } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \prop_put:NxV #2 { \tl_head:n { #1 } } \l_tmpa_tl + \mal_eval_let_loop:oNn { \use_none:nn #1 } #2 { #3 } + } + } + } +\cs_generate_variant:Nn \mal_eval_let_loop:nNn { ocn, oNn } + +\cs_new:Nn \mal_eval_let:nnnN + { + % \iow_term:n {mal_eval_let~let*=#1~binds=#2~form=#3~env=#4} + \mal_env_new:N #4 + \mal_eval_let_loop:ocn { \use_none:nn #2 } \l_mal_tmp_env_prop { #3 } + } + +% Step 4 + +\tl_const:Nx \c_if_symbol { y \tl_to_str:n { if } } +\tl_const:Nx \c_do_symbol { y \tl_to_str:n { do } } +\tl_const:Nx \c_fn_symbol { y \tl_to_str:n { fn* } } + +\cs_new:Nn \mal_eval_if:nnnN + { + % \iow_term:n {if~test=#2~then=#3~env=#4} + \mal_eval:nN {#2} #4 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \tl_set:Nn \l_tmpa_tl { n } } + { \mal_eval:nN {#3} #4 } + } + } + +\cs_new:Nn \mal_eval_if:nnnnN + { + % \iow_term:n {if~test=#2~then=#3~else=#4~env=#5} + \mal_eval:nN {#2} #5 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \mal_eval:nN { #4 } #5 } + { \mal_eval:nN { #3 } #5 } + } + } + +\cs_new:Nn \mal_fn:nnnN + { + % \iow_term:n {fn*~params=#2~implem=#3~env=#4} + \tl_set:Nx \l_tmpa_tl { \exp_not:n { u n { #3 } #4 } \use_none:nn #2 } + % \iow_term:V \l_tmpa_tl + } + +% Step 7 + +\tl_const:Nx \c_quote_symbol { y \tl_to_str:n { quote } } +\tl_const:Nx \c_quasiquote_symbol { y \tl_to_str:n { quasiquote } } +\tl_const:Nx \c_quasiquoteexpand_symbol { y \tl_to_str:n { quasiquoteexpand } } +\tl_const:Nx \c_splice_unquote_symbol { y \tl_to_str:n { splice-unquote } } +\tl_const:Nx \c_unquote_symbol { y \tl_to_str:n { unquote } } + +\cs_new:Nn \mal_quasiquote_item:n + { + \bool_lazy_and:nnTF + { \tl_if_head_eq_charcode_p:nN { #1 } l } + { \str_if_eq_p:eV { \tl_item:nn { #1 } { 3 } } \c_splice_unquote_symbol } + { { y \tl_to_str:n { concat } } { \exp_not:o { \use_iv:nnnn #1 } } } + { { y \tl_to_str:n { cons } } { \mal_quasiquote:n { #1 } } } + } +\cs_generate_variant:Nn \mal_quasiquote_item:n { e } + +\cs_new:Nn \mal_qq_loop:n + { + l n + \tl_if_empty:nF {#1} + { + \mal_quasiquote_item:e { \tl_head:n { #1 } } + { \mal_qq_loop:o { \use_none:n #1 } } + } + } +\cs_generate_variant:Nn \mal_qq_loop:n { o } + +\cs_new:Nn \mal_quasiquote:n + { + \tl_if_head_eq_charcode:nNTF { #1 } l + { + \str_if_eq:eVTF { \tl_item:nn { #1 } 3 } \c_unquote_symbol + { \exp_not:o { \use_iv:nnnn #1 } } + { \mal_qq_loop:o { \use_none:nn #1 } } + } + { + \tl_if_head_eq_charcode:nNTF { #1 } v + { + l n { y \tl_to_str:n { vec } } + { \mal_qq_loop:o { \use_none:nn #1 } } + } + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:nN { #1 } m } + { \tl_if_head_eq_charcode_p:nN { #1 } y } + { l n { \c_quote_symbol } { \exp_not:n { #1 } } } + { \exp_not:n { #1 } } + } + } + } + +\cs_new:Nn \mal_eval_quasiquote:nn { \mal_quasiquote:n { #2 } } + +% Step 8 + +\tl_const:Nx \c_defmacro_symbol { y \tl_to_str:n { defmacro! } } +\tl_const:Nx \c_macroexpand_symbol { y \tl_to_str:n { macroexpand } } + +\cs_new:Nn \mal_eval_defmacro:nnnN + { + % \iow_term:n {defmacro~#2~#3~#4} + \mal_eval:nN {#3} #4 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \tl_set:Nx \l_tmpa_tl { c n \tl_range:Vnn \l_tmpa_tl { 3 } { -1 } } + \prop_put:NnV #4 {#2} \l_tmpa_tl + } + % \iow_term:V \l_tmpa_tl + } + +\cs_new:Nn \mal_macroexpand:nN + { + % \iow_term:n {macroexpand~ast=#1~env=#2} + \tl_if_head_eq_charcode:nNTF { #1 } l + { + \tl_set:No \l_tmpb_tl { \use_none:nn #1 } + \tl_if_empty:NTF \l_tmpb_tl + { \tl_set:Nn \l_tmpa_tl { #1 } } + { + \mal_env_get:NxTF #2 { \tl_head:N \l_tmpb_tl } + { + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl c + { + \mal_fn_apply:Vo \l_tmpa_tl { \use_none:nnn #1 } + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_macroexpand:VN \l_tmpa_tl #2 } + } + { \tl_set:Nn \l_tmpa_tl { #1 } } + } + { \tl_set:Nn \l_tmpa_tl { #1 } } + } + } + { \tl_set:Nn \l_tmpa_tl { #1 } } + } +\cs_generate_variant:Nn \mal_macroexpand:nN { VN } + +\cs_new:Nn \mal_eval_macroexpand:nnN { \mal_macroexpand:nN { #2 } #3 } + +% EVAL + +\cs_new:Nn \mal_fn_apply:nn + { + % \iow_term:n {fn_apply:~func=#1~args=#2} + \tl_if_head_eq_charcode:nNTF { #1 } b + { \use_none:nn #1 { #2 } } + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:nN { #1 } u } + { \tl_if_head_eq_charcode_p:nN { #1 } c } + { + \exp_args:Nx \mal_env_new:N { \tl_item:nn { #1 } { 4 } } + \mal_env_set_keys_values:on { \use_none:nnnn #1 } { #2 } + \mal_eval:xc { \tl_item:nn { #1 } { 3 } } \l_mal_tmp_env_prop + } + { + \tl_set:Nx \l_tmpa_tl + { e s \tl_to_str:n { can~only~apply~functions } } + } + } + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_fn_apply:nn { nx, Vo, VV, xx } + +\cs_new:Nn \mal_eval_list:nN + { + % \iow_term:n {eval_mal_list~tl=#1~env=#2} + \tl_set:Nx \l_tmpa_tl { \tl_head:n {#1} } + \bool_case_true:nF + { + { \tl_if_eq_p:NN \l_tmpa_tl \c_empty_tl } + { \tl_set:Nn \l_tmpa_tl { l n } } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_def_symbol } + { + \mal_eval:oN { \use_iii:nnn #1 } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \tl_set:No \l_tmpb_tl { \use_ii:nnn #1 } + \prop_put:NVV #2 \l_tmpb_tl \l_tmpa_tl + } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_let_symbol } + { \mal_eval_let:nnnN #1 #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_if_symbol } + { + \tl_if_empty:oTF { \use_none:nnn #1 } + { \mal_eval_if:nnnN #1 #2 } + { \mal_eval_if:nnnnN #1 #2 } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_do_symbol } + { + \tl_map_inline:on { \use_none:n #1 } + { + \mal_eval:nN { ##1 } #2 + \tl_if_head_eq_charcode:VNT \l_tmpa_tl e { \tl_map_break: } + } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_fn_symbol } + { \mal_fn:nnnN #1 #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_quote_symbol } + { \tl_set:No \l_tmpa_tl { \use_ii:nn #1 } } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_quasiquote_symbol } + { \mal_eval:xN { \mal_eval_quasiquote:nn #1 } #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_quasiquoteexpand_symbol } + { \tl_set:Nx \l_tmpa_tl { \mal_eval_quasiquote:nn #1 } } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_defmacro_symbol } + { \mal_eval_defmacro:nnnN #1 #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_macroexpand_symbol } + { \mal_eval_macroexpand:nnN #1 #2 } + } + { + % \iow_term:n {eval_mal_list~apply_phase~tl=#1~env=#2} + \mal_eval:xN { \tl_head:n { #1 } } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl c + { + \mal_fn_apply:Vo \l_tmpa_tl { \use_none:n #1 } + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl #2 } + } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \tl_clear:N \l_tmpa_tl + \mal_eval_iterate_tl:oN { \use_none:n #1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_fn_apply:VV \l_tmpb_tl \l_tmpa_tl } + } + } + } + } +\cs_generate_variant:Nn \mal_eval_list:nN { oN } + +\cs_new:Nn \mal_eval:nN + { + % \iow_term:n {EVAL:~ast=#1~env=#2} + \mal_env_get:NVT #2 \c_debug_eval_symbol + { + \bool_lazy_or:nnF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \iow_term:x { EVAL: ~ \mal_printer_pr_str:nN { #1 } \c_true_bool } } + } + \exp_args:Nx \token_case_charcode:NnF { \tl_head:n {#1} } + { + l + { \mal_eval_list:oN { \use_none:nn #1 } #2 } + y + { + \mal_env_get:NnF #2 { #1 } + { + \tl_set:Nx \l_tmpa_tl + { e s \use_none:n #1 \tl_to_str:n { ~not~found } } + } + } + v + { + \tl_set:Nn \l_tmpa_tl { v n } + \mal_eval_iterate_tl:oN { \use_none:nn #1 } #2 + } + m + { \mal_eval_map:nN { #1 } #2 } + } + { \tl_set:Nn \l_tmpa_tl {#1} } + % \iow_term:n {EVAL:~ast=#1~returns} + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_eval:nN { nc, oN, VN, xc, xN } + +% REPL + +\cs_new:Nn \repl_loop: + { + % \ior_str_get_term is able to display a prompt on the same line, + % but this would make ./run far more complex for little benefit. + \iow_term:n {user>~} + \ior_str_get_term:nN {} \l_tmpa_str + \str_if_eq:VnF \l_tmpa_str {MAL_LATEX3_END_OF_INPUT} % from ./run + { + % Ignore empty lines, the MAL self-hosting relies on this + % *not* triggering an error. + \str_if_eq:VnF \l_tmpa_str {} + { + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl \l_mal_repl_env_prop } + \iow_term:x { \mal_printer_pr_str:VN \l_tmpa_tl \c_true_bool } + } + \repl_loop: + } + } + +\cs_new:Nn \mal_re:n + { + % \iow_term:n {re:~#1} + \str_set:Nn \l_tmpa_str {#1} + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl \l_mal_repl_env_prop } + \tl_if_head_eq_charcode:VNT \l_tmpa_tl e + { + \iow_term:n {error~during~startup~#1} + \iow_term:x { \mal_printer_pr_str:VN \l_tmpa_tl \c_true_bool } + Trigger a missing begin document error + } + } +\cs_generate_variant:Nn \mal_re:n { x } + +\mal_re:n { (def!~not~(fn*~(a)~(if~a~false~true))) } +\mal_re:x { (def!~load-file~(fn*~(f) + ~(eval~(read-string~(str~"(do~"~(slurp~f)~"\c_backslash_str nnil)"))))) +} +\mal_re:n { (defmacro!~cond~(fn*~(&~xs) + ~(if~(>~(count~xs)~0)~(list~'if~(first~xs)~(if~(>~(count~xs)~1) + ~(nth~xs~1)~(throw~"odd~number~of~forms~to~cond")) + ~(cons~'cond~(rest~(rest~xs))))))) } + +\mal_def_builtin:nnn { eval } { eval_builtin } + { \mal_eval:nN #1 \l_mal_repl_env_prop } + +\tl_clear:N \l_tmpa_tl +\ior_open:Nn \g_tmpa_ior {argv} +\ior_str_map_inline:Nn \g_tmpa_ior { + \tl_put_right:Nn \l_tmpa_tl { { s #1 } } +} +\ior_close:N \g_tmpa_ior +\prop_put:Nxx \l_mal_repl_env_prop { y \tl_to_str:n { *ARGV* } } + { l n \tl_tail:V \l_tmpa_tl } + +% ./run removes the normal LaTeX output. +\iow_term:n {MAL_LATEX3_START_OF_OUTPUT} + +\tl_if_empty:NTF \l_tmpa_tl { + \repl_loop: +} { + \tl_set:Nx \l_tmpa_tl { \tl_head:V \l_tmpa_tl } + \mal_re:x { (load-file~" \tl_tail:V \l_tmpa_tl ") } % without initial s +} + +\iow_term:n {MAL_LATEX3_END_OF_OUTPUT} % for ./run + +\ExplSyntaxOff +\begin{document} +\end{document} diff --git a/impls/latex3/step9_try.tex b/impls/latex3/step9_try.tex new file mode 100644 index 0000000000..0a9d6593c8 --- /dev/null +++ b/impls/latex3/step9_try.tex @@ -0,0 +1,471 @@ +\documentclass{article} +\usepackage +% Uncomment this and \debug_on below when debugging. +% [enable-debug] + {expl3} +\usepackage{types} +\usepackage{printer} +\usepackage{reader} +\usepackage{env} +\usepackage{core} +\ExplSyntaxOn + +% Slow but quite useful. +% \debug_on:n { all } + +% Step 2 + +\cs_new:Nn \mal_eval_map:nN + { + % \iow_term:n {eval_map~ast=#1~env=#2} + \mal_map_new: + \prop_map_inline:cn { #1 } + { + \str_if_eq:nnF { ##1 } { __meta__ } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##2 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \prop_map_break: } + { + \prop_put:cnV \l_tmpb_tl { ##1 } \l_tmpa_tl + \tl_set_eq:NN \l_tmpa_tl \l_tmpb_tl + } + } + } + } + +\cs_new:Nn \mal_eval_iterate_tl:nN + { + % The evaluated elements are appended to \l_tmpa_tl. + % \iow_term:n {eval_tl:~forms=#1~env=#2} + \tl_map_inline:nn { #1 } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \tl_map_break: } + { + \tl_set:Nx \l_tmpa_tl + { \exp_not:V \l_tmpb_tl { \exp_not:V \l_tmpa_tl } } + } + } + } +\cs_generate_variant:Nn \mal_eval_iterate_tl:nN { oN } + +% Step 3 + +\tl_const:Nx \c_def_symbol { y \tl_to_str:n { def! } } +\tl_const:Nx \c_let_symbol { y \tl_to_str:n { let* } } +\tl_const:Nx \c_debug_eval_symbol { y \tl_to_str:n { DEBUG-EVAL } } + +\cs_new:Nn \mal_eval_let_loop:nNn + { + % \iow_term:n {mal_eval_let_loop~binds=#1~env=#2~form=#3} + \tl_if_empty:nTF { #1 } + { \mal_eval:nN { #3 } #2 } + { + \mal_eval:xN { \tl_item:nn { #1 } 2 } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \prop_put:NxV #2 { \tl_head:n { #1 } } \l_tmpa_tl + \mal_eval_let_loop:oNn { \use_none:nn #1 } #2 { #3 } + } + } + } +\cs_generate_variant:Nn \mal_eval_let_loop:nNn { ocn, oNn } + +\cs_new:Nn \mal_eval_let:nnnN + { + % \iow_term:n {mal_eval_let~let*=#1~binds=#2~form=#3~env=#4} + \mal_env_new:N #4 + \mal_eval_let_loop:ocn { \use_none:nn #2 } \l_mal_tmp_env_prop { #3 } + } + +% Step 4 + +\tl_const:Nx \c_if_symbol { y \tl_to_str:n { if } } +\tl_const:Nx \c_do_symbol { y \tl_to_str:n { do } } +\tl_const:Nx \c_fn_symbol { y \tl_to_str:n { fn* } } + +\cs_new:Nn \mal_eval_if:nnnN + { + % \iow_term:n {if~test=#2~then=#3~env=#4} + \mal_eval:nN {#2} #4 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \tl_set:Nn \l_tmpa_tl { n } } + { \mal_eval:nN {#3} #4 } + } + } + +\cs_new:Nn \mal_eval_if:nnnnN + { + % \iow_term:n {if~test=#2~then=#3~else=#4~env=#5} + \mal_eval:nN {#2} #5 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \mal_eval:nN { #4 } #5 } + { \mal_eval:nN { #3 } #5 } + } + } + +\cs_new:Nn \mal_fn:nnnN + { + % \iow_term:n {fn*~params=#2~implem=#3~env=#4} + \tl_set:Nx \l_tmpa_tl { \exp_not:n { u n { #3 } #4 } \use_none:nn #2 } + % \iow_term:V \l_tmpa_tl + } + +% Step 7 + +\tl_const:Nx \c_quote_symbol { y \tl_to_str:n { quote } } +\tl_const:Nx \c_quasiquote_symbol { y \tl_to_str:n { quasiquote } } +\tl_const:Nx \c_quasiquoteexpand_symbol { y \tl_to_str:n { quasiquoteexpand } } +\tl_const:Nx \c_splice_unquote_symbol { y \tl_to_str:n { splice-unquote } } +\tl_const:Nx \c_unquote_symbol { y \tl_to_str:n { unquote } } + +\cs_new:Nn \mal_quasiquote_item:n + { + \bool_lazy_and:nnTF + { \tl_if_head_eq_charcode_p:nN { #1 } l } + { \str_if_eq_p:eV { \tl_item:nn { #1 } { 3 } } \c_splice_unquote_symbol } + { { y \tl_to_str:n { concat } } { \exp_not:o { \use_iv:nnnn #1 } } } + { { y \tl_to_str:n { cons } } { \mal_quasiquote:n { #1 } } } + } +\cs_generate_variant:Nn \mal_quasiquote_item:n { e } + +\cs_new:Nn \mal_qq_loop:n + { + l n + \tl_if_empty:nF {#1} + { + \mal_quasiquote_item:e { \tl_head:n { #1 } } + { \mal_qq_loop:o { \use_none:n #1 } } + } + } +\cs_generate_variant:Nn \mal_qq_loop:n { o } + +\cs_new:Nn \mal_quasiquote:n + { + \tl_if_head_eq_charcode:nNTF { #1 } l + { + \str_if_eq:eVTF { \tl_item:nn { #1 } 3 } \c_unquote_symbol + { \exp_not:o { \use_iv:nnnn #1 } } + { \mal_qq_loop:o { \use_none:nn #1 } } + } + { + \tl_if_head_eq_charcode:nNTF { #1 } v + { + l n { y \tl_to_str:n { vec } } + { \mal_qq_loop:o { \use_none:nn #1 } } + } + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:nN { #1 } m } + { \tl_if_head_eq_charcode_p:nN { #1 } y } + { l n { \c_quote_symbol } { \exp_not:n { #1 } } } + { \exp_not:n { #1 } } + } + } + } + +\cs_new:Nn \mal_eval_quasiquote:nn { \mal_quasiquote:n { #2 } } + +% Step 8 + +\tl_const:Nx \c_defmacro_symbol { y \tl_to_str:n { defmacro! } } +\tl_const:Nx \c_macroexpand_symbol { y \tl_to_str:n { macroexpand } } + +\cs_new:Nn \mal_eval_defmacro:nnnN + { + % \iow_term:n {defmacro~#2~#3~#4} + \mal_eval:nN {#3} #4 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \tl_set:Nx \l_tmpa_tl { c n \tl_range:Vnn \l_tmpa_tl { 3 } { -1 } } + \prop_put:NnV #4 {#2} \l_tmpa_tl + } + % \iow_term:V \l_tmpa_tl + } + +\cs_new:Nn \mal_macroexpand:nN + { + % \iow_term:n {macroexpand~ast=#1~env=#2} + \tl_if_head_eq_charcode:nNTF { #1 } l + { + \tl_set:No \l_tmpb_tl { \use_none:nn #1 } + \tl_if_empty:NTF \l_tmpb_tl + { \tl_set:Nn \l_tmpa_tl { #1 } } + { + \mal_env_get:NxTF #2 { \tl_head:N \l_tmpb_tl } + { + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl c + { + \mal_fn_apply:Vo \l_tmpa_tl { \use_none:nnn #1 } + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_macroexpand:VN \l_tmpa_tl #2 } + } + { \tl_set:Nn \l_tmpa_tl { #1 } } + } + { \tl_set:Nn \l_tmpa_tl { #1 } } + } + } + { \tl_set:Nn \l_tmpa_tl { #1 } } + } +\cs_generate_variant:Nn \mal_macroexpand:nN { VN } + +\cs_new:Nn \mal_eval_macroexpand:nnN { \mal_macroexpand:nN { #2 } #3 } + +% Step 9 + +\tl_const:Nx \c_try_symbol { y \tl_to_str:n { try* } } + +\cs_new:Nn \mal_eval_catch:nnnnnnN + { + % \iow_term:n {catch~exception=#1~l=#2~meta=#3~catch*=#4~symbol=#5~handler=#6~env=#7} + \mal_env_new:N #7 + \prop_put:cno \l_mal_tmp_env_prop { #5 } { \use_none:n #1 } + \mal_eval:nc { #6 } \l_mal_tmp_env_prop + } +\cs_generate_variant:Nn \mal_eval_catch:nnnnnnN { VnnnnnN } + +\cs_new:Nn \mal_eval_try:nnnN + { + % \iow_term:n {try~try*=#1~tested=#2~catch_list=#3~env=#4} + \mal_eval:nN { #2 } #4 + \tl_if_head_eq_charcode:VNT \l_tmpa_tl e + { \mal_eval_catch:VnnnnnN \l_tmpa_tl #3 #4 } + } + +% EVAL + +\cs_new:Nn \mal_fn_apply:nn + { + % \iow_term:n {fn_apply:~func=#1~args=#2} + \tl_if_head_eq_charcode:nNTF { #1 } b + { \use_none:nn #1 { #2 } } + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:nN { #1 } u } + { \tl_if_head_eq_charcode_p:nN { #1 } c } + { + \exp_args:Nx \mal_env_new:N { \tl_item:nn { #1 } { 4 } } + \mal_env_set_keys_values:on { \use_none:nnnn #1 } { #2 } + \mal_eval:xc { \tl_item:nn { #1 } { 3 } } \l_mal_tmp_env_prop + } + { + \tl_set:Nx \l_tmpa_tl + { e s \tl_to_str:n { can~only~apply~functions } } + } + } + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_fn_apply:nn { nx, Vo, VV, xx } + +\cs_new:Nn \mal_eval_list:nN + { + % \iow_term:n {eval_mal_list~tl=#1~env=#2} + \tl_set:Nx \l_tmpa_tl { \tl_head:n {#1} } + \bool_case_true:nF + { + { \tl_if_eq_p:NN \l_tmpa_tl \c_empty_tl } + { \tl_set:Nn \l_tmpa_tl { l n } } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_def_symbol } + { + \mal_eval:oN { \use_iii:nnn #1 } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \tl_set:No \l_tmpb_tl { \use_ii:nnn #1 } + \prop_put:NVV #2 \l_tmpb_tl \l_tmpa_tl + } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_let_symbol } + { \mal_eval_let:nnnN #1 #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_if_symbol } + { + \tl_if_empty:oTF { \use_none:nnn #1 } + { \mal_eval_if:nnnN #1 #2 } + { \mal_eval_if:nnnnN #1 #2 } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_do_symbol } + { + \tl_map_inline:on { \use_none:n #1 } + { + \mal_eval:nN { ##1 } #2 + \tl_if_head_eq_charcode:VNT \l_tmpa_tl e { \tl_map_break: } + } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_fn_symbol } + { \mal_fn:nnnN #1 #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_quote_symbol } + { \tl_set:No \l_tmpa_tl { \use_ii:nn #1 } } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_quasiquote_symbol } + { \mal_eval:xN { \mal_eval_quasiquote:nn #1 } #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_quasiquoteexpand_symbol } + { \tl_set:Nx \l_tmpa_tl { \mal_eval_quasiquote:nn #1 } } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_defmacro_symbol } + { \mal_eval_defmacro:nnnN #1 #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_macroexpand_symbol } + { \mal_eval_macroexpand:nnN #1 #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_try_symbol } + { + \tl_if_empty:oTF { \use_none:nn #1 } + { \mal_eval:oN { \use_ii:nn #1 } #2 } + { \mal_eval_try:nnnN #1 #2 } + } + } + { + % \iow_term:n {eval_mal_list~apply_phase~tl=#1~env=#2} + \mal_eval:xN { \tl_head:n { #1 } } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl c + { + \mal_fn_apply:Vo \l_tmpa_tl { \use_none:n #1 } + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl #2 } + } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \tl_clear:N \l_tmpa_tl + \mal_eval_iterate_tl:oN { \use_none:n #1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_fn_apply:VV \l_tmpb_tl \l_tmpa_tl } + } + } + } + } +\cs_generate_variant:Nn \mal_eval_list:nN { oN } + +\cs_new:Nn \mal_eval:nN + { + % \iow_term:n {EVAL:~ast=#1~env=#2} + \mal_env_get:NVT #2 \c_debug_eval_symbol + { + \bool_lazy_or:nnF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \iow_term:x { EVAL: ~ \mal_printer_pr_str:nN { #1 } \c_true_bool } } + } + \exp_args:Nx \token_case_charcode:NnF { \tl_head:n {#1} } + { + l + { \mal_eval_list:oN { \use_none:nn #1 } #2 } + y + { + \mal_env_get:NnF #2 { #1 } + { + \tl_set:Nx \l_tmpa_tl + { e s \use_none:n #1 \tl_to_str:n { ~not~found } } + } + } + v + { + \tl_set:Nn \l_tmpa_tl { v n } + \mal_eval_iterate_tl:oN { \use_none:nn #1 } #2 + } + m + { \mal_eval_map:nN { #1 } #2 } + } + { \tl_set:Nn \l_tmpa_tl {#1} } + % \iow_term:n {EVAL:~ast=#1~returns} + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_eval:nN { nc, oN, VN, xc, xN } + +% REPL + +\cs_new:Nn \repl_loop: + { + % \ior_str_get_term is able to display a prompt on the same line, + % but this would make ./run far more complex for little benefit. + \iow_term:n {user>~} + \ior_str_get_term:nN {} \l_tmpa_str + \str_if_eq:VnF \l_tmpa_str {MAL_LATEX3_END_OF_INPUT} % from ./run + { + % Ignore empty lines, the MAL self-hosting relies on this + % *not* triggering an error. + \str_if_eq:VnF \l_tmpa_str {} + { + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl \l_mal_repl_env_prop } + \iow_term:x { \mal_printer_pr_str:VN \l_tmpa_tl \c_true_bool } + } + \repl_loop: + } + } + +\cs_new:Nn \mal_re:n + { + % \iow_term:n {re:~#1} + \str_set:Nn \l_tmpa_str {#1} + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl \l_mal_repl_env_prop } + \tl_if_head_eq_charcode:VNT \l_tmpa_tl e + { + \iow_term:n {error~during~startup~#1} + \iow_term:x { \mal_printer_pr_str:VN \l_tmpa_tl \c_true_bool } + Trigger a missing begin document error + } + } +\cs_generate_variant:Nn \mal_re:n { x } + +\mal_re:n { (def!~not~(fn*~(a)~(if~a~false~true))) } +\mal_re:x { (def!~load-file~(fn*~(f) + ~(eval~(read-string~(str~"(do~"~(slurp~f)~"\c_backslash_str nnil)"))))) +} +\mal_re:n { (defmacro!~cond~(fn*~(&~xs) + ~(if~(>~(count~xs)~0)~(list~'if~(first~xs)~(if~(>~(count~xs)~1) + ~(nth~xs~1)~(throw~"odd~number~of~forms~to~cond")) + ~(cons~'cond~(rest~(rest~xs))))))) } + +\mal_def_builtin:nnn { eval } { eval_builtin } + { \mal_eval:nN #1 \l_mal_repl_env_prop } + +\tl_clear:N \l_tmpa_tl +\ior_open:Nn \g_tmpa_ior {argv} +\ior_str_map_inline:Nn \g_tmpa_ior { + \tl_put_right:Nn \l_tmpa_tl { { s #1 } } +} +\ior_close:N \g_tmpa_ior +\prop_put:Nxx \l_mal_repl_env_prop { y \tl_to_str:n { *ARGV* } } + { l n \tl_tail:V \l_tmpa_tl } + +% ./run removes the normal LaTeX output. +\iow_term:n {MAL_LATEX3_START_OF_OUTPUT} + +\tl_if_empty:NTF \l_tmpa_tl { + \repl_loop: +} { + \tl_set:Nx \l_tmpa_tl { \tl_head:V \l_tmpa_tl } + \mal_re:x { (load-file~" \tl_tail:V \l_tmpa_tl ") } % without initial s +} + +\iow_term:n {MAL_LATEX3_END_OF_OUTPUT} % for ./run + +\ExplSyntaxOff +\begin{document} +\end{document} diff --git a/impls/latex3/stepA_mal.tex b/impls/latex3/stepA_mal.tex new file mode 100644 index 0000000000..ff04ddf423 --- /dev/null +++ b/impls/latex3/stepA_mal.tex @@ -0,0 +1,475 @@ +\documentclass{article} +\usepackage +% Uncomment this and \debug_on below when debugging. +% [enable-debug] + {expl3} +\usepackage{types} +\usepackage{printer} +\usepackage{reader} +\usepackage{env} +\usepackage{core} +\ExplSyntaxOn + +% Slow but quite useful. +% \debug_on:n { all } + +% Step 2 + +\cs_new:Nn \mal_eval_map:nN + { + % \iow_term:n {eval_map~ast=#1~env=#2} + \mal_map_new: + \prop_map_inline:cn { #1 } + { + \str_if_eq:nnF { ##1 } { __meta__ } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##2 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \prop_map_break: } + { + \prop_put:cnV \l_tmpb_tl { ##1 } \l_tmpa_tl + \tl_set_eq:NN \l_tmpa_tl \l_tmpb_tl + } + } + } + } + +\cs_new:Nn \mal_eval_iterate_tl:nN + { + % The evaluated elements are appended to \l_tmpa_tl. + % \iow_term:n {eval_tl:~forms=#1~env=#2} + \tl_map_inline:nn { #1 } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \mal_eval:nN { ##1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl e + { \tl_map_break: } + { + \tl_set:Nx \l_tmpa_tl + { \exp_not:V \l_tmpb_tl { \exp_not:V \l_tmpa_tl } } + } + } + } +\cs_generate_variant:Nn \mal_eval_iterate_tl:nN { oN } + +% Step 3 + +\tl_const:Nx \c_def_symbol { y \tl_to_str:n { def! } } +\tl_const:Nx \c_let_symbol { y \tl_to_str:n { let* } } +\tl_const:Nx \c_debug_eval_symbol { y \tl_to_str:n { DEBUG-EVAL } } + +\cs_new:Nn \mal_eval_let_loop:nNn + { + % \iow_term:n {mal_eval_let_loop~binds=#1~env=#2~form=#3} + \tl_if_empty:nTF { #1 } + { \mal_eval:nN { #3 } #2 } + { + \mal_eval:xN { \tl_item:nn { #1 } 2 } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \prop_put:NxV #2 { \tl_head:n { #1 } } \l_tmpa_tl + \mal_eval_let_loop:oNn { \use_none:nn #1 } #2 { #3 } + } + } + } +\cs_generate_variant:Nn \mal_eval_let_loop:nNn { ocn, oNn } + +\cs_new:Nn \mal_eval_let:nnnN + { + % \iow_term:n {mal_eval_let~let*=#1~binds=#2~form=#3~env=#4} + \mal_env_new:N #4 + \mal_eval_let_loop:ocn { \use_none:nn #2 } \l_mal_tmp_env_prop { #3 } + } + +% Step 4 + +\tl_const:Nx \c_if_symbol { y \tl_to_str:n { if } } +\tl_const:Nx \c_do_symbol { y \tl_to_str:n { do } } +\tl_const:Nx \c_fn_symbol { y \tl_to_str:n { fn* } } + +\cs_new:Nn \mal_eval_if:nnnN + { + % \iow_term:n {if~test=#2~then=#3~env=#4} + \mal_eval:nN {#2} #4 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \tl_set:Nn \l_tmpa_tl { n } } + { \mal_eval:nN {#3} #4 } + } + } + +\cs_new:Nn \mal_eval_if:nnnnN + { + % \iow_term:n {if~test=#2~then=#3~else=#4~env=#5} + \mal_eval:nN {#2} #5 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \mal_eval:nN { #4 } #5 } + { \mal_eval:nN { #3 } #5 } + } + } + +\cs_new:Nn \mal_fn:nnnN + { + % \iow_term:n {fn*~params=#2~implem=#3~env=#4} + \tl_set:Nx \l_tmpa_tl { \exp_not:n { u n { #3 } #4 } \use_none:nn #2 } + % \iow_term:V \l_tmpa_tl + } + +% Step 7 + +\tl_const:Nx \c_quote_symbol { y \tl_to_str:n { quote } } +\tl_const:Nx \c_quasiquote_symbol { y \tl_to_str:n { quasiquote } } +\tl_const:Nx \c_quasiquoteexpand_symbol { y \tl_to_str:n { quasiquoteexpand } } +\tl_const:Nx \c_splice_unquote_symbol { y \tl_to_str:n { splice-unquote } } +\tl_const:Nx \c_unquote_symbol { y \tl_to_str:n { unquote } } + +\cs_new:Nn \mal_quasiquote_item:n + { + \bool_lazy_and:nnTF + { \tl_if_head_eq_charcode_p:nN { #1 } l } + { \str_if_eq_p:eV { \tl_item:nn { #1 } { 3 } } \c_splice_unquote_symbol } + { { y \tl_to_str:n { concat } } { \exp_not:o { \use_iv:nnnn #1 } } } + { { y \tl_to_str:n { cons } } { \mal_quasiquote:n { #1 } } } + } +\cs_generate_variant:Nn \mal_quasiquote_item:n { e } + +\cs_new:Nn \mal_qq_loop:n + { + l n + \tl_if_empty:nF {#1} + { + \mal_quasiquote_item:e { \tl_head:n { #1 } } + { \mal_qq_loop:o { \use_none:n #1 } } + } + } +\cs_generate_variant:Nn \mal_qq_loop:n { o } + +\cs_new:Nn \mal_quasiquote:n + { + \tl_if_head_eq_charcode:nNTF { #1 } l + { + \str_if_eq:eVTF { \tl_item:nn { #1 } 3 } \c_unquote_symbol + { \exp_not:o { \use_iv:nnnn #1 } } + { \mal_qq_loop:o { \use_none:nn #1 } } + } + { + \tl_if_head_eq_charcode:nNTF { #1 } v + { + l n { y \tl_to_str:n { vec } } + { \mal_qq_loop:o { \use_none:nn #1 } } + } + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:nN { #1 } m } + { \tl_if_head_eq_charcode_p:nN { #1 } y } + { l n { \c_quote_symbol } { \exp_not:n { #1 } } } + { \exp_not:n { #1 } } + } + } + } + +\cs_new:Nn \mal_eval_quasiquote:nn { \mal_quasiquote:n { #2 } } + +% Step 8 + +\tl_const:Nx \c_defmacro_symbol { y \tl_to_str:n { defmacro! } } +\tl_const:Nx \c_macroexpand_symbol { y \tl_to_str:n { macroexpand } } + +\cs_new:Nn \mal_eval_defmacro:nnnN + { + % \iow_term:n {defmacro~#2~#3~#4} + \mal_eval:nN {#3} #4 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \tl_set:Nx \l_tmpa_tl { c n \tl_range:Vnn \l_tmpa_tl { 3 } { -1 } } + \prop_put:NnV #4 {#2} \l_tmpa_tl + } + % \iow_term:V \l_tmpa_tl + } + +\cs_new:Nn \mal_macroexpand:nN + { + % \iow_term:n {macroexpand~ast=#1~env=#2} + \tl_if_head_eq_charcode:nNTF { #1 } l + { + \tl_set:No \l_tmpb_tl { \use_none:nn #1 } + \tl_if_empty:NTF \l_tmpb_tl + { \tl_set:Nn \l_tmpa_tl { #1 } } + { + \mal_env_get:NxTF #2 { \tl_head:N \l_tmpb_tl } + { + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl c + { + \mal_fn_apply:Vo \l_tmpa_tl { \use_none:nnn #1 } + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_macroexpand:VN \l_tmpa_tl #2 } + } + { \tl_set:Nn \l_tmpa_tl { #1 } } + } + { \tl_set:Nn \l_tmpa_tl { #1 } } + } + } + { \tl_set:Nn \l_tmpa_tl { #1 } } + } +\cs_generate_variant:Nn \mal_macroexpand:nN { VN } + +\cs_new:Nn \mal_eval_macroexpand:nnN { \mal_macroexpand:nN { #2 } #3 } + +% Step 9 + +\tl_const:Nx \c_try_symbol { y \tl_to_str:n { try* } } + +\cs_new:Nn \mal_eval_catch:nnnnnnN + { + % \iow_term:n {catch~exception=#1~l=#2~meta=#3~catch*=#4~symbol=#5~handler=#6~env=#7} + \mal_env_new:N #7 + \prop_put:cno \l_mal_tmp_env_prop { #5 } { \use_none:n #1 } + \mal_eval:nc { #6 } \l_mal_tmp_env_prop + } +\cs_generate_variant:Nn \mal_eval_catch:nnnnnnN { VnnnnnN } + +\cs_new:Nn \mal_eval_try:nnnN + { + % \iow_term:n {try~try*=#1~tested=#2~catch_list=#3~env=#4} + \mal_eval:nN { #2 } #4 + \tl_if_head_eq_charcode:VNT \l_tmpa_tl e + { \mal_eval_catch:VnnnnnN \l_tmpa_tl #3 #4 } + } + +% EVAL + +\cs_new:Nn \mal_fn_apply:nn + { + % \iow_term:n {fn_apply:~func=#1~args=#2} + \tl_if_head_eq_charcode:nNTF { #1 } b + { \use_none:nn #1 { #2 } } + { + \bool_lazy_or:nnTF + { \tl_if_head_eq_charcode_p:nN { #1 } u } + { \tl_if_head_eq_charcode_p:nN { #1 } c } + { + \exp_args:Nx \mal_env_new:N { \tl_item:nn { #1 } { 4 } } + \mal_env_set_keys_values:on { \use_none:nnnn #1 } { #2 } + \mal_eval:xc { \tl_item:nn { #1 } { 3 } } \l_mal_tmp_env_prop + } + { + \tl_set:Nx \l_tmpa_tl + { e s \tl_to_str:n { can~only~apply~functions } } + } + } + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_fn_apply:nn { nx, Vo, VV, xx } + +\cs_new:Nn \mal_eval_list:nN + { + % \iow_term:n {eval_mal_list~tl=#1~env=#2} + \tl_set:Nx \l_tmpa_tl { \tl_head:n {#1} } + \bool_case_true:nF + { + { \tl_if_eq_p:NN \l_tmpa_tl \c_empty_tl } + { \tl_set:Nn \l_tmpa_tl { l n } } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_def_symbol } + { + \mal_eval:oN { \use_iii:nnn #1 } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \tl_set:No \l_tmpb_tl { \use_ii:nnn #1 } + \prop_put:NVV #2 \l_tmpb_tl \l_tmpa_tl + } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_let_symbol } + { \mal_eval_let:nnnN #1 #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_if_symbol } + { + \tl_if_empty:oTF { \use_none:nnn #1 } + { \mal_eval_if:nnnN #1 #2 } + { \mal_eval_if:nnnnN #1 #2 } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_do_symbol } + { + \tl_map_inline:on { \use_none:n #1 } + { + \mal_eval:nN { ##1 } #2 + \tl_if_head_eq_charcode:VNT \l_tmpa_tl e { \tl_map_break: } + } + } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_fn_symbol } + { \mal_fn:nnnN #1 #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_quote_symbol } + { \tl_set:No \l_tmpa_tl { \use_ii:nn #1 } } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_quasiquote_symbol } + { \mal_eval:xN { \mal_eval_quasiquote:nn #1 } #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_quasiquoteexpand_symbol } + { \tl_set:Nx \l_tmpa_tl { \mal_eval_quasiquote:nn #1 } } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_defmacro_symbol } + { \mal_eval_defmacro:nnnN #1 #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_macroexpand_symbol } + { \mal_eval_macroexpand:nnN #1 #2 } + + { \tl_if_eq_p:NN \l_tmpa_tl \c_try_symbol } + { + \tl_if_empty:oTF { \use_none:nn #1 } + { \mal_eval:oN { \use_ii:nn #1 } #2 } + { \mal_eval_try:nnnN #1 #2 } + } + } + { + % \iow_term:n {eval_mal_list~apply_phase~tl=#1~env=#2} + \mal_eval:xN { \tl_head:n { #1 } } #2 + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { + \tl_if_head_eq_charcode:VNTF \l_tmpa_tl c + { + \mal_fn_apply:Vo \l_tmpa_tl { \use_none:n #1 } + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl #2 } + } + { + \seq_push:NV \l_mal_stack_seq \l_tmpa_tl + \tl_clear:N \l_tmpa_tl + \mal_eval_iterate_tl:oN { \use_none:n #1 } #2 + \seq_pop:NN \l_mal_stack_seq \l_tmpb_tl + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_fn_apply:VV \l_tmpb_tl \l_tmpa_tl } + } + } + } + } +\cs_generate_variant:Nn \mal_eval_list:nN { oN } + +\cs_new:Nn \mal_eval:nN + { + % \iow_term:n {EVAL:~ast=#1~env=#2} + \mal_env_get:NVT #2 \c_debug_eval_symbol + { + \bool_lazy_or:nnF + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl n } + { \tl_if_head_eq_charcode_p:VN \l_tmpa_tl f } + { \iow_term:x { EVAL: ~ \mal_printer_pr_str:nN { #1 } \c_true_bool } } + } + \exp_args:Nx \token_case_charcode:NnF { \tl_head:n {#1} } + { + l + { \mal_eval_list:oN { \use_none:nn #1 } #2 } + y + { + \mal_env_get:NnF #2 { #1 } + { + \tl_set:Nx \l_tmpa_tl + { e s \use_none:n #1 \tl_to_str:n { ~not~found } } + } + } + v + { + \tl_set:Nn \l_tmpa_tl { v n } + \mal_eval_iterate_tl:oN { \use_none:nn #1 } #2 + } + m + { \mal_eval_map:nN { #1 } #2 } + } + { \tl_set:Nn \l_tmpa_tl {#1} } + % \iow_term:n {EVAL:~ast=#1~returns} + % \iow_term:V \l_tmpa_tl + } +\cs_generate_variant:Nn \mal_eval:nN { nc, oN, VN, xc, xN } + +% REPL + +\cs_new:Nn \repl_loop: + { + % \ior_str_get_term is able to display a prompt on the same line, + % but this would make ./run far more complex for little benefit. + \iow_term:n {user>~} + \ior_str_get_term:nN {} \l_tmpa_str + \str_if_eq:VnF \l_tmpa_str {MAL_LATEX3_END_OF_INPUT} % from ./run + { + % Ignore empty lines, the MAL self-hosting relies on this + % *not* triggering an error. + \str_if_eq:VnF \l_tmpa_str {} + { + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl \l_mal_repl_env_prop } + \iow_term:x { \mal_printer_pr_str:VN \l_tmpa_tl \c_true_bool } + } + \repl_loop: + } + } + +\cs_new:Nn \mal_re:n + { + % \iow_term:n {re:~#1} + \str_set:Nn \l_tmpa_str {#1} + \mal_read_str: + \tl_if_head_eq_charcode:VNF \l_tmpa_tl e + { \mal_eval:VN \l_tmpa_tl \l_mal_repl_env_prop } + \tl_if_head_eq_charcode:VNT \l_tmpa_tl e + { + \iow_term:n {error~during~startup~#1} + \iow_term:x { \mal_printer_pr_str:VN \l_tmpa_tl \c_true_bool } + Trigger a missing begin document error + } + } +\cs_generate_variant:Nn \mal_re:n { x } + +\mal_re:n { (def!~not~(fn*~(a)~(if~a~false~true))) } +\mal_re:x { (def!~load-file~(fn*~(f) + ~(eval~(read-string~(str~"(do~"~(slurp~f)~"\c_backslash_str nnil)"))))) +} +\mal_re:n { (defmacro!~cond~(fn*~(&~xs) + ~(if~(>~(count~xs)~0)~(list~'if~(first~xs)~(if~(>~(count~xs)~1) + ~(nth~xs~1)~(throw~"odd~number~of~forms~to~cond")) + ~(cons~'cond~(rest~(rest~xs))))))) } + +\mal_def_builtin:nnn { eval } { eval_builtin } + { \mal_eval:nN #1 \l_mal_repl_env_prop } + +\prop_put:Nxx \l_mal_repl_env_prop { y \tl_to_str:n { *host-language* } } + { s \tl_to_str:n { LaTeX3 } } + +\tl_clear:N \l_tmpa_tl +\ior_open:Nn \g_tmpa_ior {argv} +\ior_str_map_inline:Nn \g_tmpa_ior { + \tl_put_right:Nn \l_tmpa_tl { { s #1 } } +} +\ior_close:N \g_tmpa_ior +\prop_put:Nxx \l_mal_repl_env_prop { y \tl_to_str:n { *ARGV* } } + { l n \tl_tail:V \l_tmpa_tl } + +% ./run removes the normal LaTeX output. +\iow_term:n {MAL_LATEX3_START_OF_OUTPUT} + +\tl_if_empty:NTF \l_tmpa_tl { + \mal_re:n { (println (str "Mal [" *host-language* "]")) } + \repl_loop: +} { + \tl_set:Nx \l_tmpa_tl { \tl_head:V \l_tmpa_tl } + \mal_re:x { (load-file~" \tl_tail:V \l_tmpa_tl ") } % without initial s +} + +\iow_term:n {MAL_LATEX3_END_OF_OUTPUT} % for ./run + +\ExplSyntaxOff +\begin{document} +\end{document} diff --git a/impls/latex3/types.sty b/impls/latex3/types.sty new file mode 100644 index 0000000000..b93cbaf399 --- /dev/null +++ b/impls/latex3/types.sty @@ -0,0 +1,90 @@ +\ProvidesExplPackage {types} {2023/01/01} {0.0.1} {MAL~types} + +% This file is included almost everywhere, it seems a good place to +% define the variants we need. + +\cs_generate_variant:Nn \int_compare:nNnTF { oNoTF } +\cs_generate_variant:Nn \int_const:Nn { NV } +\cs_generate_variant:Nn \int_to_alph:n { V } +\cs_generate_variant:Nn \int_to_arabic:n { o, V } +\cs_generate_variant:Nn \ior_open:Nn {Nx} +\cs_generate_variant:Nn \iow_term:n { x, V } +\cs_generate_variant:Nn \prop_put:Nnn { cxn, Nxn, NxV } +\cs_generate_variant:Nn \regex_extract_once:NnNTF {NVNTF} +\cs_generate_variant:Nn \str_head:n { V } +\cs_generate_variant:Nn \str_if_eq:nnTF { eVTF, xnTF } +\cs_generate_variant:Nn \str_if_eq_p:nn { eV } +\cs_generate_variant:Nn \str_item:nn { Vn } +\cs_generate_variant:Nn \str_map_function:nN { oN } +\cs_generate_variant:Nn \str_map_inline:nn { on } +\cs_generate_variant:Nn \str_set:Nn { Nx } +\cs_generate_variant:Nn \str_tail:n { V} +\cs_generate_variant:Nn \sys_get_shell:nnN { VnN } +\cs_generate_variant:Nn \tl_const:Nn { cx } +\cs_generate_variant:Nn \tl_if_eq:nnTF { xxTF } +\cs_generate_variant:Nn \tl_if_head_eq_charcode:nNF { VNF } +\cs_generate_variant:Nn \tl_if_head_eq_charcode:nNT { VNT } +\cs_generate_variant:Nn \tl_if_head_eq_charcode:nNTF { VNTF } +\cs_generate_variant:Nn \tl_if_head_eq_charcode_p:nN { VN } +\cs_generate_variant:Nn \tl_item:nn { nV } +\cs_generate_variant:Nn \tl_map_inline:nn { on } +\cs_generate_variant:Nn \tl_map_tokens:nn { on } +\cs_generate_variant:Nn \tl_range:nnn { Vnn } +\cs_generate_variant:Nn \tl_tail:n { V } + +% A global stack is convenient for storage of local variables during +% recursive computations. +\seq_new:N \l_mal_stack_seq +% TeX usually uses local assignments for this, but the number of +% groups is limited to 255, which is not enough for MAL recursions. + +% A mal form is represented by a token list starting with a letter +% defining the type (this sometimes allows f expansion). + +% n nil +% f false +% t true +% y .. symbol the rest is a str +% s .. string the rest is a str +% k .. keyword the rest is a str +% i .. number the rest is a tl/str of digits +% l meta elt elt.. list +% v meta elt elt.. vector +% map_... map \map_.. is a prop (may contain __meta__) +% atom_.. atom \atom_.. tl var contains a mal form +% e .. exception the rest is a mal form +% u meta impl env arg arg.. function the argument is a tl of mal forms +% c meta impl env arg arg.. macro (see function) +% b n \mal_..:n built-in function, expecting a tl of mal forms + +% Global counter used to create unique control sequences for atoms (in +% core.sty) and environments (in env.sty). +\int_new:N \l_mal_object_counter_int + +\cs_new:Nn \mal_map_new: + { + \int_incr:N \l_mal_object_counter_int + \tl_set:Nx \l_tmpa_tl { map_ \int_use:N \l_mal_object_counter_int } + \prop_new:c \l_tmpa_tl + } + +% Put keys and values read from a tl of MAL forms into \l_tmpa_tl, +% which must be a prop variable. +% Defined here because it is used by core.sty and reader.sty. +\cs_new:Nn \mal_assoc_internal:n + { + % \iow_term:n {assoc_internal~#1} + \tl_if_empty:nF { #1 } + { + \prop_put:cxx \l_tmpa_tl { \tl_head:n { #1 } } { \tl_item:nn { #1 } 2 } + \mal_assoc_internal:o { \use_none:nn #1 } + } + } +\cs_generate_variant:Nn \mal_assoc_internal:n { o } + +\cs_new:Nn \mal_hash_map:n + { + \mal_map_new: + \mal_assoc_internal:n { #1 } + } +\cs_generate_variant:Nn \mal_hash_map:n { V }