From d619d8d116ddcf8be59cb947a3ba9a80e82fc8f0 Mon Sep 17 00:00:00 2001 From: Donda Date: Sun, 19 Oct 2025 17:00:32 +0200 Subject: [PATCH 01/20] Use /usr/bin/env bash shebangs instead of /bin/bash --- impls/ada/run | 2 +- impls/awk/run | 2 +- impls/bash/run | 2 +- impls/basic/run | 2 +- impls/bbc-basic/run | 2 +- impls/c/run | 2 +- impls/chuck/run | 2 +- impls/clojure/run | 2 +- impls/coffee/run | 2 +- impls/common-lisp/run | 2 +- impls/cpp/docker.sh | 2 +- impls/cpp/run | 2 +- impls/cs/run | 2 +- impls/d/run | 2 +- impls/dart/run | 2 +- impls/elixir/run | 2 +- impls/erlang/run | 2 +- impls/es6/run | 2 +- impls/factor/run | 2 +- impls/fantom/run | 2 +- impls/fennel/run | 2 +- impls/forth/run | 2 +- impls/fsharp/run | 2 +- impls/gnu-smalltalk/run | 2 +- impls/go/run | 2 +- impls/groovy/run | 2 +- impls/guile/run | 2 +- impls/hare/run | 2 +- impls/haxe/run | 2 +- impls/hy/run | 2 +- impls/io/run | 2 +- impls/java-truffle/run | 2 +- impls/java/run | 2 +- impls/js/run | 2 +- impls/julia/run | 2 +- impls/kotlin/run | 2 +- impls/livescript/run | 2 +- impls/logo/run | 2 +- impls/lua/run | 2 +- impls/make/run | 2 +- impls/make/step0_repl.mk | 2 +- impls/make/step1_read_print.mk | 2 +- impls/make/step2_eval.mk | 2 +- impls/make/step3_env.mk | 2 +- impls/make/step4_if_fn_do.mk | 2 +- impls/make/step6_file.mk | 2 +- impls/make/step7_quote.mk | 2 +- impls/make/step8_macros.mk | 2 +- impls/make/step9_try.mk | 2 +- impls/make/stepA_mal.mk | 2 +- impls/mal/run | 2 +- impls/miniMAL/run | 2 +- impls/nasm/run | 2 +- impls/objc/run | 2 +- impls/objpascal/run | 2 +- impls/perl/run | 2 +- impls/perl6/run | 2 +- impls/php/run | 2 +- impls/picolisp/run | 2 +- impls/pike/run | 2 +- impls/plpgsql/entrypoint.sh | 2 +- impls/plpgsql/run | 2 +- impls/plpgsql/wrap.sh | 2 +- impls/plsql/entrypoint.sh | 2 +- impls/plsql/run | 2 +- impls/plsql/wrap.sh | 2 +- impls/prolog/run | 2 +- impls/ps/run | 2 +- impls/purs/run | 2 +- impls/r/run | 2 +- impls/racket/run | 2 +- impls/rpython/run | 2 +- impls/ruby.2/run | 2 +- impls/ruby/run | 2 +- impls/scala/run | 2 +- impls/scheme/run | 2 +- impls/skew/run | 2 +- impls/sml/run | 2 +- impls/swift3/run | 2 +- impls/swift4/run | 2 +- impls/tcl/run | 2 +- impls/tests/docker-build.sh | 2 +- impls/tests/docker-run.sh | 2 +- impls/tests/run_argv_test.sh | 2 +- impls/tests/travis_trigger.sh | 2 +- impls/ts/run | 2 +- impls/vala/run | 2 +- impls/vb/run | 2 +- impls/vbs/run | 2 +- impls/vhdl/run | 2 +- impls/vhdl/run_vhdl.sh | 2 +- impls/vimscript/run | 2 +- impls/wasm/run | 2 +- impls/wren/run | 2 +- impls/yorick/run | 2 +- process/guide.md | 4 ++-- 96 files changed, 97 insertions(+), 97 deletions(-) diff --git a/impls/ada/run b/impls/ada/run index 8ba68a5484..c66c2b81dc 100755 --- a/impls/ada/run +++ b/impls/ada/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/awk/run b/impls/awk/run index 72be264a5c..9c3061869f 100755 --- a/impls/awk/run +++ b/impls/awk/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec awk -O -f $(dirname $0)/${STEP:-stepA_mal}.awk "${@}" diff --git a/impls/bash/run b/impls/bash/run index 536c542f13..05081b9ce6 100755 --- a/impls/bash/run +++ b/impls/bash/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec bash $(dirname $0)/${STEP:-stepA_mal}.sh "${@}" diff --git a/impls/basic/run b/impls/basic/run index fac3d8c50b..9b394f8c0b 100755 --- a/impls/basic/run +++ b/impls/basic/run @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash cd $(dirname $0) (echo "(def! -*ARGS*- (list $(for a in "${@}"; do echo -n " \"${a}\""; done)))") > .args.mal case ${basic_MODE:-cbm} in diff --git a/impls/bbc-basic/run b/impls/bbc-basic/run index a417467df0..b3e2049a09 100755 --- a/impls/bbc-basic/run +++ b/impls/bbc-basic/run @@ -1,3 +1,3 @@ -#! /bin/bash +#!/usr/bin/env bash exec "${BRANDY:-sbrandy}" -size 1024k \ -path ../bbc-basic -quit $(dirname $0)/${STEP:-stepA_mal}.bas "${@}" diff --git a/impls/c/run b/impls/c/run index 8ba68a5484..c66c2b81dc 100755 --- a/impls/c/run +++ b/impls/c/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/chuck/run b/impls/chuck/run index e0db797bef..99137daea7 100755 --- a/impls/chuck/run +++ b/impls/chuck/run @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash regex_chugin=${REGEX_CHUGIN:-/usr/local/lib/chuck/1.5.2.5/RegEx.chug} if [[ ! -f "$regex_chugin" ]]; then echo "Set \$REGEX_CHUGIN to the absolute path of RegEx.chug"; exit 1 diff --git a/impls/clojure/run b/impls/clojure/run index f74eba6f25..5fe4d92804 100755 --- a/impls/clojure/run +++ b/impls/clojure/run @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash export PATH=$PATH:$(dirname $0)/node_modules/.bin STEP=${STEP:-stepA_mal} if [ "${clojure_MODE}" = "cljs" ]; then diff --git a/impls/coffee/run b/impls/coffee/run index b7841f7779..84deffb471 100755 --- a/impls/coffee/run +++ b/impls/coffee/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec coffee $(dirname $0)/${STEP:-stepA_mal}.coffee "${@}" diff --git a/impls/common-lisp/run b/impls/common-lisp/run index 8ba68a5484..c66c2b81dc 100755 --- a/impls/common-lisp/run +++ b/impls/common-lisp/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/cpp/docker.sh b/impls/cpp/docker.sh index 4fb261dcf6..2714989f4a 100755 --- a/impls/cpp/docker.sh +++ b/impls/cpp/docker.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash IMAGE_NAME=mal-cpp CONTAINER_NAME=mal-cpp-running diff --git a/impls/cpp/run b/impls/cpp/run index 8ba68a5484..c66c2b81dc 100755 --- a/impls/cpp/run +++ b/impls/cpp/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/cs/run b/impls/cs/run index fa517a6ec7..5c5642646f 100755 --- a/impls/cs/run +++ b/impls/cs/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec mono $(dirname $0)/${STEP:-stepA_mal}.exe ${RAW:+--raw} "${@}" diff --git a/impls/d/run b/impls/d/run index 8ba68a5484..c66c2b81dc 100755 --- a/impls/d/run +++ b/impls/d/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/dart/run b/impls/dart/run index fefdb5875d..6f3ab8bca3 100755 --- a/impls/dart/run +++ b/impls/dart/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec dart --checked $(dirname $0)/${STEP:-stepA_mal}.dart "${@}" diff --git a/impls/elixir/run b/impls/elixir/run index db29600d6f..bfd505014b 100755 --- a/impls/elixir/run +++ b/impls/elixir/run @@ -1,3 +1,3 @@ -#!/bin/bash +#!/usr/bin/env bash cd $(dirname $0) exec mix ${STEP:-stepA_mal} "${@}" diff --git a/impls/erlang/run b/impls/erlang/run index 8ba68a5484..c66c2b81dc 100755 --- a/impls/erlang/run +++ b/impls/erlang/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/es6/run b/impls/es6/run index b2e0745933..54e8932d5c 100755 --- a/impls/es6/run +++ b/impls/es6/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec node $(dirname $0)/${STEP:-stepA_mal}.mjs "${@}" diff --git a/impls/factor/run b/impls/factor/run index 4757514db8..bb6d41f1da 100755 --- a/impls/factor/run +++ b/impls/factor/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec factor $(dirname $0)/${STEP:-stepA_mal}/${STEP:-stepA_mal}.factor "${@}" diff --git a/impls/fantom/run b/impls/fantom/run index 3d75d6e53a..b0b70cf57b 100755 --- a/impls/fantom/run +++ b/impls/fantom/run @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash export FAN_ENV=util::PathEnv export FAN_ENV_PATH="$(dirname $0)" exec fan ${STEP:-stepA_mal} "$@" diff --git a/impls/fennel/run b/impls/fennel/run index 2651be8607..5842447c37 100755 --- a/impls/fennel/run +++ b/impls/fennel/run @@ -1,3 +1,3 @@ -#!/bin/bash +#!/usr/bin/env bash exec fennel $(dirname $0)/${STEP:-stepA_mal}.fnl "${@}" diff --git a/impls/forth/run b/impls/forth/run index c7479ea8a9..0a45c57df5 100755 --- a/impls/forth/run +++ b/impls/forth/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec gforth $(dirname $0)/${STEP:-stepA_mal}.fs "${@}" diff --git a/impls/fsharp/run b/impls/fsharp/run index fa517a6ec7..5c5642646f 100755 --- a/impls/fsharp/run +++ b/impls/fsharp/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec mono $(dirname $0)/${STEP:-stepA_mal}.exe ${RAW:+--raw} "${@}" diff --git a/impls/gnu-smalltalk/run b/impls/gnu-smalltalk/run index 4d413ea72d..740459af26 100755 --- a/impls/gnu-smalltalk/run +++ b/impls/gnu-smalltalk/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec gst -f $(dirname $0)/${STEP:-stepA_mal}.st "${@}" diff --git a/impls/go/run b/impls/go/run index 8ba68a5484..c66c2b81dc 100755 --- a/impls/go/run +++ b/impls/go/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/groovy/run b/impls/groovy/run index 80a452e6c8..b64ea39452 100755 --- a/impls/groovy/run +++ b/impls/groovy/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec groovy $(dirname $0)/${STEP:-stepA_mal}.groovy "${@}" diff --git a/impls/guile/run b/impls/guile/run index 26eb986017..6d9e58d123 100755 --- a/impls/guile/run +++ b/impls/guile/run @@ -1,3 +1,3 @@ -#!/bin/bash +#!/usr/bin/env bash # XDG_CACHE_HOME is where guile stores the compiled files XDG_CACHE_HOME=.cache/ exec guile -L $(dirname $0) $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" diff --git a/impls/hare/run b/impls/hare/run index fa9162ab82..373d352735 100755 --- a/impls/hare/run +++ b/impls/hare/run @@ -1,3 +1,3 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" \ No newline at end of file diff --git a/impls/haxe/run b/impls/haxe/run index 6011b6c1e1..3bb679ae65 100755 --- a/impls/haxe/run +++ b/impls/haxe/run @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash case ${haxe_MODE:-neko} in neko) exec neko $(dirname $0)/${STEP:-stepA_mal}.n "${@}" ;; python) exec python3 $(dirname $0)/${STEP:-stepA_mal}.py "${@}" ;; diff --git a/impls/hy/run b/impls/hy/run index 97f856223b..e203849508 100755 --- a/impls/hy/run +++ b/impls/hy/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal}.hy "${@}" diff --git a/impls/io/run b/impls/io/run index d0b76fc8c3..d49d10a227 100755 --- a/impls/io/run +++ b/impls/io/run @@ -1,3 +1,3 @@ -#!/bin/bash +#!/usr/bin/env bash io $(dirname $0)/${STEP:-stepA_mal}.io "$@" diff --git a/impls/java-truffle/run b/impls/java-truffle/run index a8667ac162..c5ca27135f 100755 --- a/impls/java-truffle/run +++ b/impls/java-truffle/run @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash CP=$(gradle -q --console plain printClasspath) diff --git a/impls/java/run b/impls/java/run index 7119297e78..670f45c4db 100755 --- a/impls/java/run +++ b/impls/java/run @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash args="" if [ "$#" -gt 0 ]; then args="-Dexec.args='$1'" diff --git a/impls/js/run b/impls/js/run index 6605303a29..1148122a23 100755 --- a/impls/js/run +++ b/impls/js/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec node $(dirname $0)/${STEP:-stepA_mal}.js "${@}" diff --git a/impls/julia/run b/impls/julia/run index 2acaf1b202..dd149451f1 100755 --- a/impls/julia/run +++ b/impls/julia/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec julia $(dirname $0)/${STEP:-stepA_mal}.jl "${@}" diff --git a/impls/kotlin/run b/impls/kotlin/run index 8840277bd1..c5a1f3c10f 100755 --- a/impls/kotlin/run +++ b/impls/kotlin/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec java -jar $(dirname $0)/${STEP:-stepA_mal}.jar "${@}" diff --git a/impls/livescript/run b/impls/livescript/run index 6605303a29..1148122a23 100755 --- a/impls/livescript/run +++ b/impls/livescript/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec node $(dirname $0)/${STEP:-stepA_mal}.js "${@}" diff --git a/impls/logo/run b/impls/logo/run index b1e12231fc..f4a73d98c1 100755 --- a/impls/logo/run +++ b/impls/logo/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec ucblogo $(dirname $0)/${STEP:-stepA_mal}.lg - "${@}" diff --git a/impls/lua/run b/impls/lua/run index f73e5b6f8f..a53fbc60ba 100755 --- a/impls/lua/run +++ b/impls/lua/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec lua $(dirname $0)/${STEP:-stepA_mal}.lua "${@}" diff --git a/impls/make/run b/impls/make/run index f897b62de8..43b9344665 100755 --- a/impls/make/run +++ b/impls/make/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec make --no-print-directory -f $(dirname $0)/${STEP:-stepA_mal}.mk "${@}" diff --git a/impls/make/step0_repl.mk b/impls/make/step0_repl.mk index 9868ed3983..aecaf10bb5 100644 --- a/impls/make/step0_repl.mk +++ b/impls/make/step0_repl.mk @@ -5,7 +5,7 @@ _TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST))) include $(_TOP_DIR)readline.mk include $(_TOP_DIR)util.mk -SHELL := /bin/bash +SHELL := /usr/bin/env bash define READ $1 diff --git a/impls/make/step1_read_print.mk b/impls/make/step1_read_print.mk index 2c2503aeb3..dee4bb321d 100644 --- a/impls/make/step1_read_print.mk +++ b/impls/make/step1_read_print.mk @@ -7,7 +7,7 @@ include $(_TOP_DIR)util.mk include $(_TOP_DIR)reader.mk include $(_TOP_DIR)printer.mk -SHELL := /bin/bash +SHELL := /usr/bin/env bash # READ: read and parse input define READ diff --git a/impls/make/step2_eval.mk b/impls/make/step2_eval.mk index 1dd8f231b2..4e8a877a50 100644 --- a/impls/make/step2_eval.mk +++ b/impls/make/step2_eval.mk @@ -9,7 +9,7 @@ include $(_TOP_DIR)reader.mk include $(_TOP_DIR)printer.mk include $(_TOP_DIR)core.mk -SHELL := /bin/bash +SHELL := /usr/bin/env bash EVAL_DEBUG ?= # READ: read and parse input diff --git a/impls/make/step3_env.mk b/impls/make/step3_env.mk index 0adc209b3b..384dfebdc7 100644 --- a/impls/make/step3_env.mk +++ b/impls/make/step3_env.mk @@ -10,7 +10,7 @@ include $(_TOP_DIR)printer.mk include $(_TOP_DIR)env.mk include $(_TOP_DIR)core.mk -SHELL := /bin/bash +SHELL := /usr/bin/env bash # READ: read and parse input define READ diff --git a/impls/make/step4_if_fn_do.mk b/impls/make/step4_if_fn_do.mk index 6384f63507..bd6be7dc73 100644 --- a/impls/make/step4_if_fn_do.mk +++ b/impls/make/step4_if_fn_do.mk @@ -10,7 +10,7 @@ include $(_TOP_DIR)printer.mk include $(_TOP_DIR)env.mk include $(_TOP_DIR)core.mk -SHELL := /bin/bash +SHELL := /usr/bin/env bash # READ: read and parse input define READ diff --git a/impls/make/step6_file.mk b/impls/make/step6_file.mk index 265d25f76f..92639ec48e 100644 --- a/impls/make/step6_file.mk +++ b/impls/make/step6_file.mk @@ -10,7 +10,7 @@ include $(_TOP_DIR)printer.mk include $(_TOP_DIR)env.mk include $(_TOP_DIR)core.mk -SHELL := /bin/bash +SHELL := /usr/bin/env bash # READ: read and parse input define READ diff --git a/impls/make/step7_quote.mk b/impls/make/step7_quote.mk index 68665fb325..ec250eec72 100644 --- a/impls/make/step7_quote.mk +++ b/impls/make/step7_quote.mk @@ -10,7 +10,7 @@ include $(_TOP_DIR)printer.mk include $(_TOP_DIR)env.mk include $(_TOP_DIR)core.mk -SHELL := /bin/bash +SHELL := /usr/bin/env bash # READ: read and parse input define READ diff --git a/impls/make/step8_macros.mk b/impls/make/step8_macros.mk index 7815c9d80f..9813fbdeed 100644 --- a/impls/make/step8_macros.mk +++ b/impls/make/step8_macros.mk @@ -10,7 +10,7 @@ include $(_TOP_DIR)printer.mk include $(_TOP_DIR)env.mk include $(_TOP_DIR)core.mk -SHELL := /bin/bash +SHELL := /usr/bin/env bash # READ: read and parse input define READ diff --git a/impls/make/step9_try.mk b/impls/make/step9_try.mk index 4d80fd9810..4dd859eb19 100644 --- a/impls/make/step9_try.mk +++ b/impls/make/step9_try.mk @@ -10,7 +10,7 @@ include $(_TOP_DIR)printer.mk include $(_TOP_DIR)env.mk include $(_TOP_DIR)core.mk -SHELL := /bin/bash +SHELL := /usr/bin/env bash # READ: read and parse input define READ diff --git a/impls/make/stepA_mal.mk b/impls/make/stepA_mal.mk index 5b7788e562..eda4d1f824 100644 --- a/impls/make/stepA_mal.mk +++ b/impls/make/stepA_mal.mk @@ -10,7 +10,7 @@ include $(_TOP_DIR)printer.mk include $(_TOP_DIR)env.mk include $(_TOP_DIR)core.mk -SHELL := /bin/bash +SHELL := /usr/bin/env bash # READ: read and parse input define READ diff --git a/impls/mal/run b/impls/mal/run index 119ef194f2..3bf6b5b09c 100755 --- a/impls/mal/run +++ b/impls/mal/run @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash MAL_FILE=../mal/${STEP:-stepA_mal}.mal export STEP=stepA_mal # force MAL_IMPL to use stepA case ${MAL_IMPL} in diff --git a/impls/miniMAL/run b/impls/miniMAL/run index db4875f09f..5fe9482d6e 100755 --- a/impls/miniMAL/run +++ b/impls/miniMAL/run @@ -1,3 +1,3 @@ -#!/bin/bash +#!/usr/bin/env bash cd $(dirname $0) exec miniMAL ./${STEP:-stepA_mal}.json "${@}" diff --git a/impls/nasm/run b/impls/nasm/run index 0ecd249cae..016cc72d79 100755 --- a/impls/nasm/run +++ b/impls/nasm/run @@ -1,3 +1,3 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/objc/run b/impls/objc/run index 8ba68a5484..c66c2b81dc 100755 --- a/impls/objc/run +++ b/impls/objc/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/objpascal/run b/impls/objpascal/run index 8ba68a5484..c66c2b81dc 100755 --- a/impls/objpascal/run +++ b/impls/objpascal/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/perl/run b/impls/perl/run index 05a286f8af..4f255fe5fe 100755 --- a/impls/perl/run +++ b/impls/perl/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec perl $(dirname $0)/${STEP:-stepA_mal}.pl "${@}" diff --git a/impls/perl6/run b/impls/perl6/run index d22ca7b7cf..80cb92b34b 100755 --- a/impls/perl6/run +++ b/impls/perl6/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec perl6 $(dirname $0)/${STEP:-stepA_mal}.pl "${@}" diff --git a/impls/php/run b/impls/php/run index 1b090b61ba..daf97f93c0 100755 --- a/impls/php/run +++ b/impls/php/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec php $(dirname $0)/${STEP:-stepA_mal}.php "${@}" diff --git a/impls/picolisp/run b/impls/picolisp/run index e759e20f21..a7f77da3fd 100755 --- a/impls/picolisp/run +++ b/impls/picolisp/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec pil $(dirname $0)/${STEP:-stepA_mal}.l - "${@}" diff --git a/impls/pike/run b/impls/pike/run index ede1d3a234..1281d967de 100755 --- a/impls/pike/run +++ b/impls/pike/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec pike $(dirname $0)/${STEP:-stepA_mal}.pike "${@}" diff --git a/impls/plpgsql/entrypoint.sh b/impls/plpgsql/entrypoint.sh index 6eaf5164b7..67ae1373ce 100755 --- a/impls/plpgsql/entrypoint.sh +++ b/impls/plpgsql/entrypoint.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash POSTGRES_SUDO_USER=${POSTGRES_SUDO_USER:-postgres} diff --git a/impls/plpgsql/run b/impls/plpgsql/run index 8613ff915a..a16184e01a 100755 --- a/impls/plpgsql/run +++ b/impls/plpgsql/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/wrap.sh $(dirname $0)/${STEP:-stepA_mal}.sql "${@}" diff --git a/impls/plpgsql/wrap.sh b/impls/plpgsql/wrap.sh index 3e7c921a80..e14c072d6a 100755 --- a/impls/plpgsql/wrap.sh +++ b/impls/plpgsql/wrap.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash RL_HISTORY_FILE=${HOME}/.mal-history SKIP_INIT="${SKIP_INIT:-}" diff --git a/impls/plsql/entrypoint.sh b/impls/plsql/entrypoint.sh index 549e7ce80c..cba55117dc 100755 --- a/impls/plsql/entrypoint.sh +++ b/impls/plsql/entrypoint.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash case ${1} in make*) diff --git a/impls/plsql/run b/impls/plsql/run index 8613ff915a..a16184e01a 100755 --- a/impls/plsql/run +++ b/impls/plsql/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/wrap.sh $(dirname $0)/${STEP:-stepA_mal}.sql "${@}" diff --git a/impls/plsql/wrap.sh b/impls/plsql/wrap.sh index 8822116bdf..c106ecb98b 100755 --- a/impls/plsql/wrap.sh +++ b/impls/plsql/wrap.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash RL_HISTORY_FILE=${HOME}/.mal-history SKIP_INIT="${SKIP_INIT:-}" diff --git a/impls/prolog/run b/impls/prolog/run index c71709be92..6a9b5f1fe4 100755 --- a/impls/prolog/run +++ b/impls/prolog/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec swipl $(dirname $0)/${STEP:-stepA_mal}.pl "${@}" diff --git a/impls/ps/run b/impls/ps/run index e2042e818a..a961d13c7f 100755 --- a/impls/ps/run +++ b/impls/ps/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec gs -q -I$(dirname $0) -dNOSAFER -dNODISPLAY -- $(dirname $0)/${STEP:-stepA_mal}.ps "${@}" diff --git a/impls/purs/run b/impls/purs/run index 6a4a5dd75b..510f226881 100755 --- a/impls/purs/run +++ b/impls/purs/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec node $(dirname $0)/${STEP:-stepA_mal}.js "${@}" \ No newline at end of file diff --git a/impls/r/run b/impls/r/run index 711ef09092..916876f09d 100755 --- a/impls/r/run +++ b/impls/r/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec Rscript $(dirname $0)/${STEP:-stepA_mal}.r "${@}" diff --git a/impls/racket/run b/impls/racket/run index 923de9df0d..fa0719bdce 100755 --- a/impls/racket/run +++ b/impls/racket/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec racket $(dirname $0)/${STEP:-stepA_mal}.rkt "${@}" diff --git a/impls/rpython/run b/impls/rpython/run index 8ba68a5484..c66c2b81dc 100755 --- a/impls/rpython/run +++ b/impls/rpython/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/ruby.2/run b/impls/ruby.2/run index 000320bf5f..980db0d659 100755 --- a/impls/ruby.2/run +++ b/impls/ruby.2/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec ruby $(dirname $0)/${STEP:-stepA_mal}.rb "${@}" diff --git a/impls/ruby/run b/impls/ruby/run index 000320bf5f..980db0d659 100755 --- a/impls/ruby/run +++ b/impls/ruby/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec ruby $(dirname $0)/${STEP:-stepA_mal}.rb "${@}" diff --git a/impls/scala/run b/impls/scala/run index 49c913c26a..eb06292ab5 100755 --- a/impls/scala/run +++ b/impls/scala/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec java -classpath "$(dirname $0)/target/scala-2.11/mal.jar" "${STEP:-stepA_mal}" "$@" diff --git a/impls/scheme/run b/impls/scheme/run index 9614f8baea..8cfd055ae2 100755 --- a/impls/scheme/run +++ b/impls/scheme/run @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash basedir=$(dirname $0) step=${STEP:-stepA_mal} diff --git a/impls/skew/run b/impls/skew/run index 6605303a29..1148122a23 100755 --- a/impls/skew/run +++ b/impls/skew/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec node $(dirname $0)/${STEP:-stepA_mal}.js "${@}" diff --git a/impls/sml/run b/impls/sml/run index 8ba68a5484..c66c2b81dc 100755 --- a/impls/sml/run +++ b/impls/sml/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/swift3/run b/impls/swift3/run index 8ba68a5484..c66c2b81dc 100755 --- a/impls/swift3/run +++ b/impls/swift3/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/swift4/run b/impls/swift4/run index 8ba68a5484..c66c2b81dc 100755 --- a/impls/swift4/run +++ b/impls/swift4/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/tcl/run b/impls/tcl/run index e73c2a63fe..700b5291b0 100755 --- a/impls/tcl/run +++ b/impls/tcl/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec tclsh $(dirname $0)/${STEP:-stepA_mal}.tcl ${RAW:+--raw} "${@}" diff --git a/impls/tests/docker-build.sh b/impls/tests/docker-build.sh index e79c149de5..4df597932e 100755 --- a/impls/tests/docker-build.sh +++ b/impls/tests/docker-build.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash IMAGE_NAME=${IMAGE_NAME:-mal-test-ubuntu-utopic} GIT_TOP=$(git rev-parse --show-toplevel) diff --git a/impls/tests/docker-run.sh b/impls/tests/docker-run.sh index 1666d7d318..0f8be9e1f0 100755 --- a/impls/tests/docker-run.sh +++ b/impls/tests/docker-run.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash IMAGE_NAME=${IMAGE_NAME:-mal-test-ubuntu-utopic} GIT_TOP=$(git rev-parse --show-toplevel) diff --git a/impls/tests/run_argv_test.sh b/impls/tests/run_argv_test.sh index bb29598f03..e3682e4f9f 100755 --- a/impls/tests/run_argv_test.sh +++ b/impls/tests/run_argv_test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # Usage: run_argv_test.sh diff --git a/impls/tests/travis_trigger.sh b/impls/tests/travis_trigger.sh index 424f39508b..7dd96116ec 100755 --- a/impls/tests/travis_trigger.sh +++ b/impls/tests/travis_trigger.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Reference: https://docs.travis-ci.com/user/triggering-builds/ diff --git a/impls/ts/run b/impls/ts/run index 6605303a29..1148122a23 100755 --- a/impls/ts/run +++ b/impls/ts/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec node $(dirname $0)/${STEP:-stepA_mal}.js "${@}" diff --git a/impls/vala/run b/impls/vala/run index 8ba68a5484..c66c2b81dc 100755 --- a/impls/vala/run +++ b/impls/vala/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/vb/run b/impls/vb/run index fa517a6ec7..5c5642646f 100755 --- a/impls/vb/run +++ b/impls/vb/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec mono $(dirname $0)/${STEP:-stepA_mal}.exe ${RAW:+--raw} "${@}" diff --git a/impls/vbs/run b/impls/vbs/run index ba94436374..3c1aed2dd3 100644 --- a/impls/vbs/run +++ b/impls/vbs/run @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash MAL_VBS_IMPL_NO_STDERR=1 MAL_VBS_IMPL_ECHO_STDIN=1 \ WSLENV=MAL_VBS_IMPL_NO_STDERR/w:MAL_VBS_IMPL_ECHO_STDIN/w \ cscript.exe -nologo "`wslpath -w "$(dirname $0)/${STEP:-stepA_mal}.vbs"`" "${@}" \ No newline at end of file diff --git a/impls/vhdl/run b/impls/vhdl/run index 12de079d1f..ca50d3f82b 100755 --- a/impls/vhdl/run +++ b/impls/vhdl/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/run_vhdl.sh $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/vhdl/run_vhdl.sh b/impls/vhdl/run_vhdl.sh index b8e374a222..1cc8e3bf9f 100755 --- a/impls/vhdl/run_vhdl.sh +++ b/impls/vhdl/run_vhdl.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # ghdl doesn't allow passing command-line arguments to the VHDL program. To # circumvent that, we write the command-line arguments as lines in diff --git a/impls/vimscript/run b/impls/vimscript/run index 48e666b057..007df20bdf 100755 --- a/impls/vimscript/run +++ b/impls/vimscript/run @@ -1,3 +1,3 @@ -#!/bin/bash +#!/usr/bin/env bash cd $(dirname $0) exec ./run_vimscript.sh ./${STEP:-stepA_mal}.vim "${@}" diff --git a/impls/wasm/run b/impls/wasm/run index ced38efcd7..7d4fed5185 100755 --- a/impls/wasm/run +++ b/impls/wasm/run @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash STEP=${STEP:-stepA_mal} case "${wasm_MODE}" in wasmtime) diff --git a/impls/wren/run b/impls/wren/run index 9a2c7e10b4..8c269bf55a 100755 --- a/impls/wren/run +++ b/impls/wren/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash exec wren $(dirname $0)/${STEP:-stepA_mal}.wren "${@}" diff --git a/impls/yorick/run b/impls/yorick/run index c54589bb04..78b865fca2 100755 --- a/impls/yorick/run +++ b/impls/yorick/run @@ -1,3 +1,3 @@ -#!/bin/bash +#!/usr/bin/env bash export YORICK_MAL_PATH="$(dirname $0)" exec yorick -batch "$YORICK_MAL_PATH/${STEP:-stepA_mal}.i" "${@}" diff --git a/process/guide.md b/process/guide.md index 3ce25676ef..bda640206d 100644 --- a/process/guide.md +++ b/process/guide.md @@ -118,12 +118,12 @@ quux_STEP_TO_PROG = impls/quux/$($(1)).qx the interpreter is named "quux"): ``` -#!/bin/bash +#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}" ``` ``` -#!/bin/bash +#!/usr/bin/env bash exec quux $(dirname $0)/${STEP:-stepA_mal}.qx "${@}" ``` From 9386e0726e8b57f64351c179ac2f8d34e85d43c0 Mon Sep 17 00:00:00 2001 From: Donda Date: Sun, 19 Oct 2025 23:30:05 +0200 Subject: [PATCH 02/20] gleam: step0 --- Makefile.impls | 3 ++- impls/gleam/.github/workflows/test.yml | 23 ++++++++++++++++++ impls/gleam/.gitignore | 4 ++++ impls/gleam/Makefile | 18 ++++++++++++++ impls/gleam/README.md | 24 +++++++++++++++++++ impls/gleam/gleam.toml | 10 ++++++++ impls/gleam/manifest.toml | 15 ++++++++++++ impls/gleam/run | 3 +++ impls/gleam/src/readline.gleam | 5 ++++ impls/gleam/src/step0_repl.gleam | 33 ++++++++++++++++++++++++++ impls/gleam/test/mal_test.gleam | 13 ++++++++++ 11 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 impls/gleam/.github/workflows/test.yml create mode 100644 impls/gleam/.gitignore create mode 100644 impls/gleam/Makefile create mode 100644 impls/gleam/README.md create mode 100644 impls/gleam/gleam.toml create mode 100644 impls/gleam/manifest.toml create mode 100755 impls/gleam/run create mode 100644 impls/gleam/src/readline.gleam create mode 100644 impls/gleam/src/step0_repl.gleam create mode 100644 impls/gleam/test/mal_test.gleam diff --git a/Makefile.impls b/Makefile.impls index 2c3517d7ff..c6a51d5075 100644 --- a/Makefile.impls +++ b/Makefile.impls @@ -33,7 +33,7 @@ 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 \ + elisp elixir elm erlang es6 factor fantom fennel forth fsharp gleam go groovy gnu-smalltalk \ guile hare 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 python2 python3 r racket rexx rpython ruby ruby.2 rust scala scheme skew sml \ @@ -133,6 +133,7 @@ fantom_STEP_TO_PROG = impls/fantom/lib/fan/$($(1)).pod fennel_STEP_TO_PROG = impls/fennel/$($(1)).fnl forth_STEP_TO_PROG = impls/forth/$($(1)).fs fsharp_STEP_TO_PROG = impls/fsharp/$($(1)).exe +gleam_STEP_TO_PROG = impls/gleam/$($(1)) go_STEP_TO_PROG = impls/go/$($(1)) groovy_STEP_TO_PROG = impls/groovy/$($(1)).groovy gnu-smalltalk_STEP_TO_PROG = impls/gnu-smalltalk/$($(1)).st diff --git a/impls/gleam/.github/workflows/test.yml b/impls/gleam/.github/workflows/test.yml new file mode 100644 index 0000000000..4a7fe22d29 --- /dev/null +++ b/impls/gleam/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: test + +on: + push: + branches: + - master + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + with: + otp-version: "27.1.2" + gleam-version: "1.12.0" + rebar3-version: "3" + # elixir-version: "1" + - run: gleam deps download + - run: gleam test + - run: gleam format --check src test diff --git a/impls/gleam/.gitignore b/impls/gleam/.gitignore new file mode 100644 index 0000000000..599be4eb92 --- /dev/null +++ b/impls/gleam/.gitignore @@ -0,0 +1,4 @@ +*.beam +*.ez +/build +erl_crash.dump diff --git a/impls/gleam/Makefile b/impls/gleam/Makefile new file mode 100644 index 0000000000..93260cfbe8 --- /dev/null +++ b/impls/gleam/Makefile @@ -0,0 +1,18 @@ +STEPS = \ +step0_repl \ +step1_read_print \ +step2_eval \ +step3_env \ +step4_if_fn_do \ +step5_tco \ +step6_file \ +step7_quote \ +step8_macros \ +step9_try \ +stepA_mal + +$(STEPS): + gleam build + +clean: + gleam clean diff --git a/impls/gleam/README.md b/impls/gleam/README.md new file mode 100644 index 0000000000..6fc8195c24 --- /dev/null +++ b/impls/gleam/README.md @@ -0,0 +1,24 @@ +# mal + +[![Package Version](https://img.shields.io/hexpm/v/mal)](https://hex.pm/packages/mal) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/mal/) + +```sh +gleam add mal@1 +``` +```gleam +import mal + +pub fn main() -> Nil { + // TODO: An example of the project in use +} +``` + +Further documentation can be found at . + +## Development + +```sh +gleam run # Run the project +gleam test # Run the tests +``` diff --git a/impls/gleam/gleam.toml b/impls/gleam/gleam.toml new file mode 100644 index 0000000000..225d645cf4 --- /dev/null +++ b/impls/gleam/gleam.toml @@ -0,0 +1,10 @@ +name = "mal" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 0.44.0 and < 2.0.0" +argv = ">= 1.0.2 and < 2.0.0" +input = ">= 1.0.1 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/impls/gleam/manifest.toml b/impls/gleam/manifest.toml new file mode 100644 index 0000000000..844dd0bacc --- /dev/null +++ b/impls/gleam/manifest.toml @@ -0,0 +1,15 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, + { name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" }, + { name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" }, + { name = "input", version = "1.0.1", build_tools = ["gleam"], requirements = [], otp_app = "input", source = "hex", outer_checksum = "FE84CDADC78A1367E4AFD561A529825A8FEC88D165CBDF511FD3226CABCDEE6F" }, +] + +[requirements] +argv = { version = ">= 1.0.2 and < 2.0.0" } +gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } +gleeunit = { version = ">= 1.0.0 and < 2.0.0" } +input = { version = ">= 1.0.1 and < 2.0.0" } diff --git a/impls/gleam/run b/impls/gleam/run new file mode 100755 index 0000000000..1cdbc8a315 --- /dev/null +++ b/impls/gleam/run @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +cd $(dirname $0) +exec gleam run --module ${STEP:-stepA_mal} --no-print-progress -- "${@}" diff --git a/impls/gleam/src/readline.gleam b/impls/gleam/src/readline.gleam new file mode 100644 index 0000000000..5ab4eacf71 --- /dev/null +++ b/impls/gleam/src/readline.gleam @@ -0,0 +1,5 @@ +import input.{input} + +pub fn readline(prn: String) { + input(prn) +} diff --git a/impls/gleam/src/step0_repl.gleam b/impls/gleam/src/step0_repl.gleam new file mode 100644 index 0000000000..3aebcfb016 --- /dev/null +++ b/impls/gleam/src/step0_repl.gleam @@ -0,0 +1,33 @@ +import gleam/io +import gleam/result +import readline.{readline} + +pub fn main() -> Nil { + let _ = loop() + Nil +} + +fn loop() { + use input <- result.try(readline("user> ")) + + let _res = input |> rep() + + loop() +} + +fn read(str) { + str +} + +fn eval(ast) { + ast +} + +fn print(ast) { + io.println(ast) + ast +} + +fn rep(str) { + read(str) |> eval() |> print() +} diff --git a/impls/gleam/test/mal_test.gleam b/impls/gleam/test/mal_test.gleam new file mode 100644 index 0000000000..fba3c8872b --- /dev/null +++ b/impls/gleam/test/mal_test.gleam @@ -0,0 +1,13 @@ +import gleeunit + +pub fn main() -> Nil { + gleeunit.main() +} + +// gleeunit test functions end in `_test` +pub fn hello_world_test() { + let name = "Joe" + let greeting = "Hello, " <> name <> "!" + + assert greeting == "Hello, Joe!" +} From bd034818cad3c1f4aafa754f5a48a57fd0e62f1b Mon Sep 17 00:00:00 2001 From: Donda Date: Tue, 21 Oct 2025 22:14:20 +0200 Subject: [PATCH 03/20] gleam: step1 --- impls/gleam/gleam.toml | 1 + impls/gleam/manifest.toml | 2 + impls/gleam/src/printer.gleam | 55 +++++++++ impls/gleam/src/reader.gleam | 154 +++++++++++++++++++++++++ impls/gleam/src/step1_read_print.gleam | 45 ++++++++ impls/gleam/src/types.gleam | 19 +++ impls/gleam/test/mal_test.gleam | 8 -- 7 files changed, 276 insertions(+), 8 deletions(-) create mode 100644 impls/gleam/src/printer.gleam create mode 100644 impls/gleam/src/reader.gleam create mode 100644 impls/gleam/src/step1_read_print.gleam create mode 100644 impls/gleam/src/types.gleam diff --git a/impls/gleam/gleam.toml b/impls/gleam/gleam.toml index 225d645cf4..ea5eea3329 100644 --- a/impls/gleam/gleam.toml +++ b/impls/gleam/gleam.toml @@ -5,6 +5,7 @@ version = "1.0.0" gleam_stdlib = ">= 0.44.0 and < 2.0.0" argv = ">= 1.0.2 and < 2.0.0" input = ">= 1.0.1 and < 2.0.0" +gleam_regexp = ">= 1.1.1 and < 2.0.0" [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/impls/gleam/manifest.toml b/impls/gleam/manifest.toml index 844dd0bacc..9686608af3 100644 --- a/impls/gleam/manifest.toml +++ b/impls/gleam/manifest.toml @@ -3,6 +3,7 @@ packages = [ { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, + { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, { name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" }, { name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" }, { name = "input", version = "1.0.1", build_tools = ["gleam"], requirements = [], otp_app = "input", source = "hex", outer_checksum = "FE84CDADC78A1367E4AFD561A529825A8FEC88D165CBDF511FD3226CABCDEE6F" }, @@ -10,6 +11,7 @@ packages = [ [requirements] argv = { version = ">= 1.0.2 and < 2.0.0" } +gleam_regexp = { version = ">= 1.1.1 and < 2.0.0" } gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } input = { version = ">= 1.0.1 and < 2.0.0" } diff --git a/impls/gleam/src/printer.gleam b/impls/gleam/src/printer.gleam new file mode 100644 index 0000000000..5ec5285d83 --- /dev/null +++ b/impls/gleam/src/printer.gleam @@ -0,0 +1,55 @@ +import gleam/dict +import gleam/int +import gleam/list +import gleam/string +import types.{ + Array, Bool, HashMap, Int, Keyword, List, Nil, ReaderEmpyForm, ReaderEof, + ReaderInvalidHashMap, String, Symbol, +} + +pub fn prn_err(err: types.Error) { + case err { + ReaderEof(expected) -> "expected '" <> expected <> "', found EOF" + ReaderInvalidHashMap -> + "failed to construct hashmap: item count not divisible by 2" + ReaderEmpyForm -> "read_form called with empty list" + } +} + +pub fn pr_str(ast: types.MalType, print_readability: Bool) -> String { + case ast { + Nil -> "nil" + Int(int) -> int.to_string(int) + String(str) -> "\"" <> str <> "\"" + Symbol(sym) -> sym + Bool(bool) -> + case bool { + True -> "true" + False -> "false" + } + Keyword(kwd) -> ":" <> kwd + List(list, _) -> + "(" + <> list.map(list, fn(x) { pr_str(x, print_readability) }) + |> string.join(" ") + <> ")" + Array(arr, _) -> + "[" + <> list.map(arr, fn(x) { pr_str(x, print_readability) }) + |> string.join(" ") + <> "]" + HashMap(hm, _) -> { + let d = + dict.fold(hm, [], fn(acc, key, val) { + [ + pr_str(key, print_readability), + pr_str(val, print_readability), + ..acc + ] + }) + |> string.join(" ") + + "{" <> d <> "}" + } + } +} diff --git a/impls/gleam/src/reader.gleam b/impls/gleam/src/reader.gleam new file mode 100644 index 0000000000..bc89440616 --- /dev/null +++ b/impls/gleam/src/reader.gleam @@ -0,0 +1,154 @@ +import gleam/dict +import gleam/int +import gleam/list +import gleam/option.{None, Some} +import gleam/regexp +import gleam/result +import gleam/string +import types.{ + type Error, type MalType, Bool, HashMap, Int, Keyword, List, ReaderEmpyForm, + ReaderEof, ReaderInvalidHashMap, String, Symbol, +} + +pub fn read_str(str: String) { + let res = + tokenize(str) + |> read_form() + + case res { + Error(err) -> Error(err) + Ok(ok) -> Ok(ok.0) + } +} + +fn tokenize(str: String) { + let assert Ok(re) = + regexp.from_string( + "[\\s,]*" + <> "(" + <> "~@" + <> "|[\\[\\]{}()'`~^@]" + <> "|\"(?:\\\\.|[^\\\"])*\"?" + <> "|;.*" + <> "|[^\\s\\[\\]{}('\"`,;)]*" + <> ")", + ) + + // TODO remove the need for this filter + // something to do with the regex or regexp.split + regexp.split(re, str) + |> list.filter(fn(x) { x != "" }) +} + +fn read_form(x: List(String)) { + let expand = fn(rest: List(String), sym: String) { + use #(f, new_rest) <- result.try(read_form(rest)) + + let res = List([Symbol(sym), f], types.Nil) + Ok(#(res, new_rest)) + } + + case x { + ["(", ..rest] -> read_seq(rest, [], ")") + ["[", ..rest] -> read_seq(rest, [], "]") + ["{", ..rest] -> read_seq(rest, [], "}") + ["'", ..rest] -> expand(rest, "quote") + ["`", ..rest] -> expand(rest, "quasiquote") + ["~", ..rest] -> expand(rest, "unquote") + ["~@", ..rest] -> expand(rest, "splice-unquote") + ["@", ..rest] -> expand(rest, "deref") + + ["^", ..rest] -> { + use #(meta, r) <- result.try(read_form(rest)) + use #(data, new_rest) <- result.try(read_form(r)) + + let res = List([Symbol("with-meta"), data, meta], types.Nil) + Ok(#(res, new_rest)) + } + + [a, ..rest] -> { + let res = read_atom(a) + + case res { + Ok(o) -> Ok(#(o, rest)) + Error(e) -> Error(e) + } + } + _ -> Error(ReaderEmpyForm) + } +} + +fn list_to_pairs( + l: List(MalType), + acc: List(#(MalType, MalType)), +) -> option.Option(List(#(MalType, MalType))) { + case l { + [a, b, ..rest] -> list_to_pairs(rest, [#(a, b), ..acc]) + [_] -> None + [] -> Some(acc) + } +} + +fn read_seq(input: List(String), acc: List(MalType), closing: String) { + case input { + [] -> Error(ReaderEof(closing)) + [x, ..rest] if x == closing -> { + case closing { + ")" -> Ok(#(List(acc, types.Nil), rest)) + "]" -> Ok(#(types.Array(acc, types.Nil), rest)) + "}" -> { + let tuples = list_to_pairs(acc, []) + + case tuples { + None -> Error(ReaderInvalidHashMap) + Some(t) -> { + let hm = dict.from_list(t) + + Ok(#(HashMap(hm, types.Nil), rest)) + } + } + } + _ -> panic as "read_seq called with unknown closing symbol" + } + } + _ -> { + use #(atom, rest) <- result.try(read_form(input)) + read_seq(rest, list.append(acc, [atom]), closing) + } + } +} + +fn use_parse_int(input: String, with: fn(Nil) -> Result(MalType, Error)) { + let res = int.parse(input) + case res { + Error(_) -> with(Nil) + Ok(int) -> Ok(Int(int)) + } +} + +fn use_parse_str(input: String, with: fn(Nil) -> Result(MalType, Error)) { + let assert Ok(str_re) = regexp.from_string("\"(?:\\\\.|[^\\\\\"])*\"") + let is_string = regexp.check(str_re, input) + + case is_string { + False -> with(Nil) + True -> Ok(String(input |> string.drop_end(1) |> string.drop_start(1))) + } +} + +fn read_atom(input: String) -> Result(MalType, Error) { + case input { + "true" -> Ok(Bool(True)) + "false" -> Ok(Bool(False)) + _ -> { + use _ <- use_parse_int(input) + use _ <- use_parse_str(input) + + case input { + ":" <> rest -> Ok(Keyword(rest)) + "\"" <> _ -> Error(ReaderEof("\"")) + _ -> Ok(Symbol(input)) + } + } + } +} diff --git a/impls/gleam/src/step1_read_print.gleam b/impls/gleam/src/step1_read_print.gleam new file mode 100644 index 0000000000..66c1d67928 --- /dev/null +++ b/impls/gleam/src/step1_read_print.gleam @@ -0,0 +1,45 @@ +import gleam/io +import gleam/result +import printer +import reader +import readline.{readline} + +pub fn main() -> Nil { + let _ = loop() + Nil +} + +fn loop() { + use input <- result.try(readline("user> ")) + + let prn = + input + |> rep() + case prn { + Error(err) -> printer.prn_err(err) |> io.println_error() + Ok(str) -> io.println(str) + } + + loop() +} + +fn read(str) { + reader.read_str(str) +} + +fn eval(ast) { + ast +} + +fn print(ast) { + printer.pr_str(ast, True) +} + +fn rep(str) { + use re <- result.try( + read(str) + |> eval(), + ) + + Ok(print(re)) +} diff --git a/impls/gleam/src/types.gleam b/impls/gleam/src/types.gleam new file mode 100644 index 0000000000..674cc4efea --- /dev/null +++ b/impls/gleam/src/types.gleam @@ -0,0 +1,19 @@ +import gleam/dict + +pub type MalType { + Nil + Int(Int) + Bool(Bool) + String(String) + Symbol(String) + Keyword(String) + List(List(MalType), MalType) + Array(List(MalType), MalType) + HashMap(dict.Dict(MalType, MalType), MalType) +} + +pub type Error { + ReaderEof(expected: String) + ReaderInvalidHashMap + ReaderEmpyForm +} diff --git a/impls/gleam/test/mal_test.gleam b/impls/gleam/test/mal_test.gleam index fba3c8872b..902c4da7e0 100644 --- a/impls/gleam/test/mal_test.gleam +++ b/impls/gleam/test/mal_test.gleam @@ -3,11 +3,3 @@ import gleeunit pub fn main() -> Nil { gleeunit.main() } - -// gleeunit test functions end in `_test` -pub fn hello_world_test() { - let name = "Joe" - let greeting = "Hello, " <> name <> "!" - - assert greeting == "Hello, Joe!" -} From c8b0b221eee17f0b36140207aa47f36653ed6eb5 Mon Sep 17 00:00:00 2001 From: Donda Date: Wed, 22 Oct 2025 21:26:16 +0200 Subject: [PATCH 04/20] gleam: step2 --- impls/gleam/src/printer.gleam | 15 +++- impls/gleam/src/reader.gleam | 4 +- impls/gleam/src/step2_eval.gleam | 119 +++++++++++++++++++++++++++++++ impls/gleam/src/types.gleam | 11 ++- 4 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 impls/gleam/src/step2_eval.gleam diff --git a/impls/gleam/src/printer.gleam b/impls/gleam/src/printer.gleam index 5ec5285d83..ec65b832f2 100644 --- a/impls/gleam/src/printer.gleam +++ b/impls/gleam/src/printer.gleam @@ -3,8 +3,8 @@ import gleam/int import gleam/list import gleam/string import types.{ - Array, Bool, HashMap, Int, Keyword, List, Nil, ReaderEmpyForm, ReaderEof, - ReaderInvalidHashMap, String, Symbol, + Bool, HashMap, Int, Keyword, List, Nil, ReaderEmpyForm, ReaderEof, + ReaderInvalidHashMap, String, Symbol, Vector, } pub fn prn_err(err: types.Error) { @@ -13,6 +13,14 @@ pub fn prn_err(err: types.Error) { ReaderInvalidHashMap -> "failed to construct hashmap: item count not divisible by 2" ReaderEmpyForm -> "read_form called with empty list" + types.EvalWrongArgLen(expected, provided) -> + "expected " + <> int.to_string(expected) + <> " arguments, got " + <> int.to_string(provided) + types.EvalWrongType(_expected, _provided) -> "todo" + types.EvalDivideByZero -> "can't divide by zero" + types.EvalApplyType(x) -> "can't apply '" <> pr_str(x, True) <> "'" } } @@ -33,7 +41,7 @@ pub fn pr_str(ast: types.MalType, print_readability: Bool) -> String { <> list.map(list, fn(x) { pr_str(x, print_readability) }) |> string.join(" ") <> ")" - Array(arr, _) -> + Vector(arr, _) -> "[" <> list.map(arr, fn(x) { pr_str(x, print_readability) }) |> string.join(" ") @@ -51,5 +59,6 @@ pub fn pr_str(ast: types.MalType, print_readability: Bool) -> String { "{" <> d <> "}" } + types.Builtin(_) -> "builtin" } } diff --git a/impls/gleam/src/reader.gleam b/impls/gleam/src/reader.gleam index bc89440616..31563c39b4 100644 --- a/impls/gleam/src/reader.gleam +++ b/impls/gleam/src/reader.gleam @@ -7,7 +7,7 @@ import gleam/result import gleam/string import types.{ type Error, type MalType, Bool, HashMap, Int, Keyword, List, ReaderEmpyForm, - ReaderEof, ReaderInvalidHashMap, String, Symbol, + ReaderEof, ReaderInvalidHashMap, String, Symbol, Vector, } pub fn read_str(str: String) { @@ -95,7 +95,7 @@ fn read_seq(input: List(String), acc: List(MalType), closing: String) { [x, ..rest] if x == closing -> { case closing { ")" -> Ok(#(List(acc, types.Nil), rest)) - "]" -> Ok(#(types.Array(acc, types.Nil), rest)) + "]" -> Ok(#(Vector(acc, types.Nil), rest)) "}" -> { let tuples = list_to_pairs(acc, []) diff --git a/impls/gleam/src/step2_eval.gleam b/impls/gleam/src/step2_eval.gleam new file mode 100644 index 0000000000..232ec31902 --- /dev/null +++ b/impls/gleam/src/step2_eval.gleam @@ -0,0 +1,119 @@ +import gleam/dict +import gleam/io +import gleam/list +import gleam/result +import printer +import reader +import readline.{readline} +import types.{ + type MalRet, type MalType, Builtin, HashMap, Int, List, Symbol, Vector, +} + +pub fn main() -> Nil { + let int_op = fn(with: fn(Int, Int) -> Result(Int, types.Error)) { + Builtin(fn(l: List(MalType)) { + case l { + [a, b] -> { + case a, b { + Int(x), Int(y) -> with(x, y) |> result.map(fn(z) { Int(z) }) + // TODO + // wrong type constructor function + _, _ -> Error(types.EvalWrongType("", "")) + } + } + _ -> Error(types.EvalWrongArgLen(2, list.length(l))) + } + }) + } + + let env = + [ + #("+", int_op(fn(a, b) { Ok(a + b) })), + #("-", int_op(fn(a, b) { Ok(a - b) })), + #("*", int_op(fn(a, b) { Ok(a * b) })), + #( + "/", + int_op(fn(a, b) { + case b == 0 { + False -> Ok(a / b) + True -> Error(types.EvalDivideByZero) + } + }), + ), + ] + |> dict.from_list() + + let _ = loop(env) + + Nil +} + +fn loop(env) { + use input <- result.try(readline("user> ")) + + let prn = + input + |> rep(env) + case prn { + Error(err) -> printer.prn_err(err) |> io.println_error() + Ok(str) -> io.println(str) + } + + loop(env) +} + +fn apply(x: MalType, args: List(MalType)) -> MalRet { + case x { + Builtin(f) -> f(args) + _ -> Error(types.EvalApplyType(x)) + } +} + +fn eval(ast: MalType, env: dict.Dict(String, MalType)) -> types.MalRet { + case ast { + Symbol(sym) -> { + case dict.get(env, sym) { + Error(_) -> Ok(ast) + Ok(val) -> Ok(val) + } + } + List([l, ..rest], _) -> { + use first <- result.try(eval(l, env)) + use args <- result.try(list.try_map(rest, fn(x) { eval(x, env) })) + + apply(first, args) + } + Vector(vec, _) -> + vec + |> list.try_map(fn(x) { eval(x, env) }) + |> result.map(fn(o) { Vector(o, types.Nil) }) + HashMap(hm, _) -> { + use res <- result.try( + hm + |> dict.to_list() + |> list.try_map(fn(x) { + use e <- result.try(eval(x.1, env)) + Ok(#(x.0, e)) + }), + ) + + Ok(HashMap(dict.from_list(res), types.Nil)) + } + _ -> Ok(ast) + } +} + +fn read(str) { + reader.read_str(str) +} + +fn print(ast) { + printer.pr_str(ast, True) +} + +fn rep(str, env) { + use r <- result.try(read(str)) + use e <- result.try(eval(r, env)) + + Ok(print(e)) +} diff --git a/impls/gleam/src/types.gleam b/impls/gleam/src/types.gleam index 674cc4efea..56b8aa4691 100644 --- a/impls/gleam/src/types.gleam +++ b/impls/gleam/src/types.gleam @@ -1,5 +1,8 @@ import gleam/dict +pub type MalRet = + Result(MalType, Error) + pub type MalType { Nil Int(Int) @@ -8,12 +11,18 @@ pub type MalType { Symbol(String) Keyword(String) List(List(MalType), MalType) - Array(List(MalType), MalType) + Vector(List(MalType), MalType) HashMap(dict.Dict(MalType, MalType), MalType) + + Builtin(fn(List(MalType)) -> MalRet) } pub type Error { ReaderEof(expected: String) ReaderInvalidHashMap ReaderEmpyForm + EvalWrongArgLen(expected: Int, provided: Int) + EvalWrongType(expected: String, provided: String) + EvalDivideByZero + EvalApplyType(provided: MalType) } From 75b8ae02adf9e0d0873a1ae87e5f06d289d7f687 Mon Sep 17 00:00:00 2001 From: Donda Date: Fri, 24 Oct 2025 17:55:14 +0200 Subject: [PATCH 05/20] gleam: step3 --- impls/gleam/src/env.gleam | 54 +++++++ impls/gleam/src/printer.gleam | 16 +- impls/gleam/src/step1_read_print.gleam | 2 +- impls/gleam/src/step2_eval.gleam | 2 +- impls/gleam/src/step3_env.gleam | 212 +++++++++++++++++++++++++ impls/gleam/src/types.gleam | 4 + 6 files changed, 280 insertions(+), 10 deletions(-) create mode 100644 impls/gleam/src/env.gleam create mode 100644 impls/gleam/src/step3_env.gleam diff --git a/impls/gleam/src/env.gleam b/impls/gleam/src/env.gleam new file mode 100644 index 0000000000..afc23b40ba --- /dev/null +++ b/impls/gleam/src/env.gleam @@ -0,0 +1,54 @@ +import gleam/dict.{type Dict} +import gleam/option.{type Option, None, Some} +import types.{type Error, type MalType} + +pub opaque type Env { + Env(outer: Option(Env), data: Dict(String, MalType)) +} + +pub fn new() -> Env { + Env(None, dict.new()) +} + +pub fn get(env: Env, key: String) -> Option(MalType) { + case env.outer { + Some(e) -> get(e, key) + None -> { + env.data + |> dict.get(key) + |> option.from_result() + } + } +} + +pub fn set(env: Env, key: String, val: MalType) -> Env { + env.data + |> dict.insert(key, val) + |> Env(env.outer, _) +} + +pub fn from_list(input: List(#(String, MalType)), outer: Option(Env)) -> Env { + input + |> dict.from_list() + |> Env(outer, _) +} + +pub fn from_dict(input: Dict(String, MalType), outer: Option(Env)) -> Env { + input + |> Env(outer, _) +} + +pub fn into_outer(outer: Env) -> Env { + Env(Some(outer), dict.new()) +} + +pub fn ast_to_key(ast: MalType) -> Result(String, Error) { + case ast { + types.Symbol(sym) -> Ok(sym) + _ -> Error(types.EnvToKey(ast)) + } +} + +pub fn key_to_ast(str: String) { + types.Symbol(str) +} diff --git a/impls/gleam/src/printer.gleam b/impls/gleam/src/printer.gleam index ec65b832f2..721b9cf25a 100644 --- a/impls/gleam/src/printer.gleam +++ b/impls/gleam/src/printer.gleam @@ -2,17 +2,15 @@ import gleam/dict import gleam/int import gleam/list import gleam/string -import types.{ - Bool, HashMap, Int, Keyword, List, Nil, ReaderEmpyForm, ReaderEof, - ReaderInvalidHashMap, String, Symbol, Vector, -} +import types.{Bool, HashMap, Int, Keyword, List, Nil, String, Symbol, Vector} -pub fn prn_err(err: types.Error) { +pub fn pr_err(err: types.Error) { case err { - ReaderEof(expected) -> "expected '" <> expected <> "', found EOF" - ReaderInvalidHashMap -> + types.ReaderEof(expected) -> "expected '" <> expected <> "', found EOF" + types.ReaderInvalidHashMap -> "failed to construct hashmap: item count not divisible by 2" - ReaderEmpyForm -> "read_form called with empty list" + types.ReaderEmpyForm -> "read_form called with empty list" + types.EvalWrongArgLen(expected, provided) -> "expected " <> int.to_string(expected) @@ -21,6 +19,8 @@ pub fn prn_err(err: types.Error) { types.EvalWrongType(_expected, _provided) -> "todo" types.EvalDivideByZero -> "can't divide by zero" types.EvalApplyType(x) -> "can't apply '" <> pr_str(x, True) <> "'" + types.EvalSymbolNotFound(sym) -> "'" <> sym <> "' not found" + types.EnvToKey(ast) -> "can't bind value to '" <> pr_str(ast, True) <> "'" } } diff --git a/impls/gleam/src/step1_read_print.gleam b/impls/gleam/src/step1_read_print.gleam index 66c1d67928..5fe8362fb3 100644 --- a/impls/gleam/src/step1_read_print.gleam +++ b/impls/gleam/src/step1_read_print.gleam @@ -16,7 +16,7 @@ fn loop() { input |> rep() case prn { - Error(err) -> printer.prn_err(err) |> io.println_error() + Error(err) -> printer.pr_err(err) |> io.println_error() Ok(str) -> io.println(str) } diff --git a/impls/gleam/src/step2_eval.gleam b/impls/gleam/src/step2_eval.gleam index 232ec31902..16dbf8b3ff 100644 --- a/impls/gleam/src/step2_eval.gleam +++ b/impls/gleam/src/step2_eval.gleam @@ -55,7 +55,7 @@ fn loop(env) { input |> rep(env) case prn { - Error(err) -> printer.prn_err(err) |> io.println_error() + Error(err) -> printer.pr_err(err) |> io.println_error() Ok(str) -> io.println(str) } diff --git a/impls/gleam/src/step3_env.gleam b/impls/gleam/src/step3_env.gleam new file mode 100644 index 0000000000..7d3918ff33 --- /dev/null +++ b/impls/gleam/src/step3_env.gleam @@ -0,0 +1,212 @@ +import env +import gleam/dict +import gleam/io +import gleam/list +import gleam/option.{None, Some} +import gleam/result +import printer +import reader +import readline.{readline} +import types.{ + type MalRet, type MalType, Builtin, HashMap, Int, List, Symbol, Vector, +} + +pub fn main() -> Nil { + let int_op = fn(with: fn(Int, Int) -> Result(Int, types.Error)) { + Builtin(fn(l: List(MalType)) { + case l { + [a, b] -> { + case a, b { + Int(x), Int(y) -> with(x, y) |> result.map(fn(z) { Int(z) }) + // TODO + // wrong type constructor function + _, _ -> Error(types.EvalWrongType("", "")) + } + } + _ -> Error(types.EvalWrongArgLen(2, list.length(l))) + } + }) + } + + let env = + env.from_list( + [ + #("+", int_op(fn(a, b) { Ok(a + b) })), + #("-", int_op(fn(a, b) { Ok(a - b) })), + #("*", int_op(fn(a, b) { Ok(a * b) })), + #( + "/", + int_op(fn(a, b) { + case b == 0 { + False -> Ok(a / b) + True -> Error(types.EvalDivideByZero) + } + }), + ), + ], + None, + ) + + let _ = loop(env) + + Nil +} + +fn loop(env) { + use input <- result.try(readline("user> ")) + + let prn = + input + |> rep(env) + case prn { + Error(err) -> { + printer.pr_err(err) |> io.println_error() + loop(env) + } + Ok(#(str, env)) -> { + io.println(str) + + loop(env) + } + } +} + +fn apply(x: MalType, args: List(MalType)) -> MalRet { + case x { + Builtin(f) -> f(args) + _ -> Error(types.EvalApplyType(x)) + } +} + +fn eval(ast: MalType, env: env.Env) -> Result(#(MalType, env.Env), types.Error) { + case env.get(env, "DEBUG-EVAL") { + None -> Nil + Some(_) -> { + io.println("EVAL: " <> printer.pr_str(ast, True)) + } + } + + case ast { + Symbol(sym) -> { + case env.get(env, sym) { + None -> Ok(#(ast, env)) + Some(val) -> Ok(#(val, env)) + } + } + List([l, ..rest], _) -> { + let eval_args = fn() { + list.try_fold(rest, #([], env), fn(acc, x) { + use #(res, env) <- result.try(eval(x, acc.1)) + + Ok(#([res, ..acc.0], env)) + }) + |> result.map(fn(o) { #(list.reverse(o.0), o.1) }) + } + + case l { + Symbol("def!") -> + case rest { + [key, val] -> { + use #(res, _) <- result.try(eval(val, env)) + use k <- result.try(env.ast_to_key(key)) + + let env = env.set(env, k, res) + + Ok(#(res, env)) + } + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } + Symbol("let*") -> { + case rest { + [pairs, closure] -> { + case pairs { + List(l, _) | Vector(l, _) -> { + use e <- result.try(let_special(l, [], env)) + let new_env = env.into_outer(e) + + use #(res, _) <- result.try(eval(closure, new_env)) + Ok(#(res, env)) + } + _ -> Error(types.EvalWrongType("list | vector", "")) + } + } + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } + } + + Symbol(sym) -> { + case env.get(env, sym) { + None -> Error(types.EvalSymbolNotFound(sym)) + Some(a) -> { + use #(args, env) <- result.try(eval_args()) + apply(a, args) |> result.map(fn(o) { #(o, env) }) + } + } + } + _ -> { + use #(args, env) <- result.try(eval_args()) + apply(l, args) |> result.map(fn(o) { #(o, env) }) + } + } + } + Vector(vec, _) -> { + use #(res, env) <- result.try( + list.try_fold(vec, #([], env), fn(acc, x) { + use #(res, env) <- result.try(eval(x, acc.1)) + + Ok(#([res, ..acc.0], env)) + }) + |> result.map(fn(o) { #(list.reverse(o.0), o.1) }), + ) + + Ok(#(Vector(res, types.Nil), env)) + } + HashMap(hm, _) -> { + use #(res, env) <- result.try( + hm + |> dict.to_list() + |> list.try_fold(#([], env), fn(acc, x) { + use #(res, env) <- result.try(eval(x.1, acc.1)) + + let pair = #(x.0, res) + Ok(#([pair, ..acc.0], env)) + }), + ) + + let ret = HashMap(dict.from_list(res), types.Nil) + Ok(#(ret, env)) + } + _ -> Ok(#(ast, env)) + } +} + +fn let_special(pairs, acc, env) { + case pairs { + [key, val, ..rest] -> { + use #(res, env) <- result.try(eval(val, env)) + use k <- result.try(env.ast_to_key(key)) + + let env = env.set(env, k, res) + + let_special(rest, [res, ..acc], env) + } + [] -> Ok(env) + // TODO different err + _ -> Error(types.EvalWrongArgLen(2, list.length(pairs))) + } +} + +fn read(str) { + reader.read_str(str) +} + +fn print(ast) { + printer.pr_str(ast, True) +} + +fn rep(str, env) { + use r <- result.try(read(str)) + use #(e, env) <- result.try(eval(r, env)) + + Ok(#(print(e), env)) +} diff --git a/impls/gleam/src/types.gleam b/impls/gleam/src/types.gleam index 56b8aa4691..9bf897321d 100644 --- a/impls/gleam/src/types.gleam +++ b/impls/gleam/src/types.gleam @@ -21,8 +21,12 @@ pub type Error { ReaderEof(expected: String) ReaderInvalidHashMap ReaderEmpyForm + EvalWrongArgLen(expected: Int, provided: Int) EvalWrongType(expected: String, provided: String) EvalDivideByZero EvalApplyType(provided: MalType) + EvalSymbolNotFound(symbol: String) + + EnvToKey(ast: MalType) } From d48fc5939254a83cd4a33a0b74d6bcae5672631f Mon Sep 17 00:00:00 2001 From: Donda Date: Wed, 29 Oct 2025 00:16:04 +0100 Subject: [PATCH 06/20] gleam: step4 --- impls/gleam/src/core.gleam | 123 ++++++++++++ impls/gleam/src/env.gleam | 35 ++-- impls/gleam/src/printer.gleam | 50 ++++- impls/gleam/src/reader.gleam | 21 +- impls/gleam/src/step2_eval.gleam | 7 +- impls/gleam/src/step3_env.gleam | 27 +-- impls/gleam/src/step4_if_fn_do.gleam | 278 +++++++++++++++++++++++++++ impls/gleam/src/types.gleam | 69 ++++++- 8 files changed, 563 insertions(+), 47 deletions(-) create mode 100644 impls/gleam/src/core.gleam create mode 100644 impls/gleam/src/step4_if_fn_do.gleam diff --git a/impls/gleam/src/core.gleam b/impls/gleam/src/core.gleam new file mode 100644 index 0000000000..eecbba9c69 --- /dev/null +++ b/impls/gleam/src/core.gleam @@ -0,0 +1,123 @@ +import gleam/io +import gleam/list +import gleam/result +import gleam/string +import printer +import types.{type Error, type MalType, Bool, Builtin, Int, List, String, Vector} + +pub fn ns() { + let int_op = fn(with: fn(Int, Int) -> Result(MalType, Error)) { + Builtin(fn(l: List(MalType)) { + case l { + [a, b] -> { + case a, b { + Int(x), Int(y) -> with(x, y) |> result.map(fn(z) { z }) + _, _ -> types.wrong_type_err("int, int", [a, b]) + } + } + _ -> Error(types.EvalWrongArgLen(2, list.length(l))) + } + }) + } + + [ + // Int + #("+", int_op(fn(a, b) { Ok(Int(a + b)) })), + #("-", int_op(fn(a, b) { Ok(Int(a - b)) })), + #("*", int_op(fn(a, b) { Ok(Int(a * b)) })), + #( + "/", + int_op(fn(a, b) { + case b == 0 { + False -> Ok(Int(a / b)) + True -> Error(types.EvalDivideByZero) + } + }), + ), + #(">", int_op(fn(a, b) { Ok(Bool(a > b)) })), + #(">=", int_op(fn(a, b) { Ok(Bool(a >= b)) })), + #("<", int_op(fn(a, b) { Ok(Bool(a < b)) })), + #("<=", int_op(fn(a, b) { Ok(Bool(a <= b)) })), + // Bool + #( + "=", + Builtin(fn(args) { + case args { + [a, b, ..] -> Ok(Bool(types.eq(a, b))) + _ -> Ok(Bool(False)) + } + }), + ), + #( + "list?", + Builtin(fn(args) { + case args { + [List(..), ..] -> Ok(Bool(True)) + _ -> Ok(Bool(False)) + } + }), + ), + #( + "empty?", + Builtin(fn(args) { + case args { + [List([], _), ..] | [Vector([], _), ..] -> Ok(Bool(True)) + [List(_, _), ..] | [Vector(_, _), ..] -> Ok(Bool(False)) + _ -> Error(types.EvalWrongArgLenGreaterThan(1, 0)) + } + }), + ), + // String + #( + "pr-str", + Builtin(fn(args) { + Ok( + list.map(args, fn(x) { printer.pr_str(x, True) }) + |> string.join(" ") + |> String, + ) + }), + ), + #( + "str", + Builtin(fn(args) { + Ok( + list.map(args, fn(x) { printer.pr_str(x, False) }) + |> string.join("") + |> String, + ) + }), + ), + // IO + #( + "prn", + Builtin(fn(args) { + list.map(args, fn(x) { printer.pr_str(x, True) }) + |> string.join(" ") + |> io.println() + Ok(types.Nil) + }), + ), + #( + "println", + Builtin(fn(args) { + list.map(args, fn(x) { printer.pr_str(x, False) }) + |> string.join(" ") + |> io.println() + Ok(types.Nil) + }), + ), + // Declare + #("list", Builtin(fn(args) { Ok(List(args, types.Nil)) })), + // Other + #( + "count", + Builtin(fn(args) { + case args { + [List(l, _), ..] | [Vector(l, _), ..] -> Ok(Int(list.length(l))) + _ -> Ok(Int(0)) + } + }), + ), + ] +} diff --git a/impls/gleam/src/env.gleam b/impls/gleam/src/env.gleam index afc23b40ba..9ec48ba7f7 100644 --- a/impls/gleam/src/env.gleam +++ b/impls/gleam/src/env.gleam @@ -11,13 +11,14 @@ pub fn new() -> Env { } pub fn get(env: Env, key: String) -> Option(MalType) { - case env.outer { - Some(e) -> get(e, key) - None -> { - env.data - |> dict.get(key) - |> option.from_result() + case dict.get(env.data, key) { + Error(_) -> { + case env.outer { + None -> None + Some(e) -> get(e, key) + } } + Ok(data) -> Some(data) } } @@ -27,6 +28,22 @@ pub fn set(env: Env, key: String, val: MalType) -> Env { |> Env(env.outer, _) } +pub fn bind(env: Env, from: List(String), to: List(MalType)) { + case from { + ["&", sym] -> Ok(set(env, sym, types.List(to, types.Nil))) + [sym, ..from_rest] -> + case to { + [t, ..rest] -> { + let env = set(env, sym, t) + bind(env, from_rest, rest) + } + [] -> Error(types.StrErr("can't bind env: not enough args provided")) + } + [] if to == [] -> Ok(env) + _ -> Error(types.StrErr("can't bind env: too many args provided")) + } +} + pub fn from_list(input: List(#(String, MalType)), outer: Option(Env)) -> Env { input |> dict.from_list() @@ -42,13 +59,9 @@ pub fn into_outer(outer: Env) -> Env { Env(Some(outer), dict.new()) } -pub fn ast_to_key(ast: MalType) -> Result(String, Error) { +pub fn try_key(ast: MalType) -> Result(String, Error) { case ast { types.Symbol(sym) -> Ok(sym) _ -> Error(types.EnvToKey(ast)) } } - -pub fn key_to_ast(str: String) { - types.Symbol(str) -} diff --git a/impls/gleam/src/printer.gleam b/impls/gleam/src/printer.gleam index 721b9cf25a..cff9c064ab 100644 --- a/impls/gleam/src/printer.gleam +++ b/impls/gleam/src/printer.gleam @@ -4,23 +4,55 @@ import gleam/list import gleam/string import types.{Bool, HashMap, Int, Keyword, List, Nil, String, Symbol, Vector} -pub fn pr_err(err: types.Error) { +pub fn pr_err(err: types.Error) -> String { case err { types.ReaderEof(expected) -> "expected '" <> expected <> "', found EOF" types.ReaderInvalidHashMap -> "failed to construct hashmap: item count not divisible by 2" types.ReaderEmpyForm -> "read_form called with empty list" - types.EvalWrongArgLen(expected, provided) -> + types.EvalWrongArgLen(expected, got) -> "expected " <> int.to_string(expected) <> " arguments, got " - <> int.to_string(provided) - types.EvalWrongType(_expected, _provided) -> "todo" + <> int.to_string(got) + types.EvalWrongArgLenGreaterThan(expected, got) -> + "expected at least " + <> int.to_string(expected) + <> " arguments, got " + <> int.to_string(got) + types.EvalWrongArgLenRange(expected, got) -> + "expected " + <> int.to_string(expected.0) + <> " - " + <> int.to_string(expected.1) + <> " arguments, got " + <> int.to_string(got) + + types.EvalWrongType(expected, got) -> + "expected '" <> expected <> "', got '" <> got <> "'" + types.EvalDivideByZero -> "can't divide by zero" types.EvalApplyType(x) -> "can't apply '" <> pr_str(x, True) <> "'" types.EvalSymbolNotFound(sym) -> "'" <> sym <> "' not found" types.EnvToKey(ast) -> "can't bind value to '" <> pr_str(ast, True) <> "'" + types.EvalFuncParamNotSymbol -> "function parameter not of type symbol" + types.StrErr(str) -> str + } +} + +fn escape_str(str, acc) { + case str { + "\n" <> rest -> escape_str(rest, acc <> "\\n") + "\"" <> rest -> escape_str(rest, acc <> "\\\"") + "\\" <> rest -> escape_str(rest, acc <> "\\\\") + "" -> acc + _ -> { + case string.pop_grapheme(str) { + Error(_) -> acc + Ok(#(g, rest)) -> escape_str(rest, acc <> g) + } + } } } @@ -28,7 +60,11 @@ pub fn pr_str(ast: types.MalType, print_readability: Bool) -> String { case ast { Nil -> "nil" Int(int) -> int.to_string(int) - String(str) -> "\"" <> str <> "\"" + String(str) -> + case print_readability { + False -> str + True -> "\"" <> escape_str(str, "") <> "\"" + } Symbol(sym) -> sym Bool(bool) -> case bool { @@ -59,6 +95,8 @@ pub fn pr_str(ast: types.MalType, print_readability: Bool) -> String { "{" <> d <> "}" } - types.Builtin(_) -> "builtin" + types.Builtin(..) -> "#" + types.Func(..) -> "#" + types.LazyFunc(_) -> "#" } } diff --git a/impls/gleam/src/reader.gleam b/impls/gleam/src/reader.gleam index 31563c39b4..48059fc591 100644 --- a/impls/gleam/src/reader.gleam +++ b/impls/gleam/src/reader.gleam @@ -126,18 +126,37 @@ fn use_parse_int(input: String, with: fn(Nil) -> Result(MalType, Error)) { } } +fn unescape_str(str, acc) { + case str { + "\\n" <> rest -> unescape_str(rest, acc <> "\n") + "\\\"" <> rest -> unescape_str(rest, acc <> "\"") + "\\\\" <> rest -> unescape_str(rest, acc <> "\\") + "" -> acc + _ -> { + case string.pop_grapheme(str) { + Error(_) -> acc + Ok(#(g, rest)) -> unescape_str(rest, acc <> g) + } + } + } +} + fn use_parse_str(input: String, with: fn(Nil) -> Result(MalType, Error)) { let assert Ok(str_re) = regexp.from_string("\"(?:\\\\.|[^\\\\\"])*\"") let is_string = regexp.check(str_re, input) case is_string { False -> with(Nil) - True -> Ok(String(input |> string.drop_end(1) |> string.drop_start(1))) + True -> + Ok(String( + input |> string.drop_end(1) |> string.drop_start(1) |> unescape_str(""), + )) } } fn read_atom(input: String) -> Result(MalType, Error) { case input { + "nil" -> Ok(types.Nil) "true" -> Ok(Bool(True)) "false" -> Ok(Bool(False)) _ -> { diff --git a/impls/gleam/src/step2_eval.gleam b/impls/gleam/src/step2_eval.gleam index 16dbf8b3ff..e6e11506d5 100644 --- a/impls/gleam/src/step2_eval.gleam +++ b/impls/gleam/src/step2_eval.gleam @@ -16,16 +16,13 @@ pub fn main() -> Nil { [a, b] -> { case a, b { Int(x), Int(y) -> with(x, y) |> result.map(fn(z) { Int(z) }) - // TODO - // wrong type constructor function - _, _ -> Error(types.EvalWrongType("", "")) + _, _ -> types.wrong_type_err("int, int", [a, b]) } } _ -> Error(types.EvalWrongArgLen(2, list.length(l))) } }) } - let env = [ #("+", int_op(fn(a, b) { Ok(a + b) })), @@ -73,7 +70,7 @@ fn eval(ast: MalType, env: dict.Dict(String, MalType)) -> types.MalRet { case ast { Symbol(sym) -> { case dict.get(env, sym) { - Error(_) -> Ok(ast) + Error(_) -> Error(types.EvalSymbolNotFound(sym)) Ok(val) -> Ok(val) } } diff --git a/impls/gleam/src/step3_env.gleam b/impls/gleam/src/step3_env.gleam index 7d3918ff33..a517956529 100644 --- a/impls/gleam/src/step3_env.gleam +++ b/impls/gleam/src/step3_env.gleam @@ -18,9 +18,7 @@ pub fn main() -> Nil { [a, b] -> { case a, b { Int(x), Int(y) -> with(x, y) |> result.map(fn(z) { Int(z) }) - // TODO - // wrong type constructor function - _, _ -> Error(types.EvalWrongType("", "")) + _, _ -> types.wrong_type_err("int, int", [a, b]) } } _ -> Error(types.EvalWrongArgLen(2, list.length(l))) @@ -89,12 +87,12 @@ fn eval(ast: MalType, env: env.Env) -> Result(#(MalType, env.Env), types.Error) case ast { Symbol(sym) -> { case env.get(env, sym) { - None -> Ok(#(ast, env)) + None -> Error(types.EvalSymbolNotFound(sym)) Some(val) -> Ok(#(val, env)) } } List([l, ..rest], _) -> { - let eval_args = fn() { + let eval_args = fn(env) { list.try_fold(rest, #([], env), fn(acc, x) { use #(res, env) <- result.try(eval(x, acc.1)) @@ -108,7 +106,7 @@ fn eval(ast: MalType, env: env.Env) -> Result(#(MalType, env.Env), types.Error) case rest { [key, val] -> { use #(res, _) <- result.try(eval(val, env)) - use k <- result.try(env.ast_to_key(key)) + use k <- result.try(env.try_key(key)) let env = env.set(env, k, res) @@ -134,18 +132,11 @@ fn eval(ast: MalType, env: env.Env) -> Result(#(MalType, env.Env), types.Error) } } - Symbol(sym) -> { - case env.get(env, sym) { - None -> Error(types.EvalSymbolNotFound(sym)) - Some(a) -> { - use #(args, env) <- result.try(eval_args()) - apply(a, args) |> result.map(fn(o) { #(o, env) }) - } - } - } _ -> { - use #(args, env) <- result.try(eval_args()) - apply(l, args) |> result.map(fn(o) { #(o, env) }) + use #(f, env) <- result.try(eval(l, env)) + use #(args, env) <- result.try(eval_args(env)) + + apply(f, args) |> result.map(fn(o) { #(o, env) }) } } } @@ -184,7 +175,7 @@ fn let_special(pairs, acc, env) { case pairs { [key, val, ..rest] -> { use #(res, env) <- result.try(eval(val, env)) - use k <- result.try(env.ast_to_key(key)) + use k <- result.try(env.try_key(key)) let env = env.set(env, k, res) diff --git a/impls/gleam/src/step4_if_fn_do.gleam b/impls/gleam/src/step4_if_fn_do.gleam new file mode 100644 index 0000000000..5105d7ef78 --- /dev/null +++ b/impls/gleam/src/step4_if_fn_do.gleam @@ -0,0 +1,278 @@ +import core +import env +import gleam/dict +import gleam/io +import gleam/list +import gleam/option.{None, Some} +import gleam/result +import printer +import reader +import readline.{readline} +import types.{ + type MalRet, type MalType, Bool, Builtin, Func, HashMap, LazyFunc, List, + Symbol, Vector, +} + +pub fn main() -> Nil { + let env = env.from_list(core.ns(), None) + + let assert Ok(#(_, env)) = rep("(def! not (fn* (a) (if a false true)))", env) + + let _ = loop(env) + + Nil +} + +fn loop(env) { + use input <- result.try(readline("user> ")) + + let prn = + input + |> rep(env) + case prn { + Error(err) -> { + printer.pr_err(err) |> io.println_error() + loop(env) + } + Ok(#(str, env)) -> { + io.println(str) + + loop(env) + } + } +} + +fn apply(x: MalType, args: List(MalType)) -> MalRet { + case x { + Builtin(f) -> f(args) + Func(f, _) -> f(args) + LazyFunc(f) -> { + use res <- result.try(f()) + apply(res, args) + } + _ -> Error(types.EvalApplyType(x)) + } +} + +fn eval(ast: MalType, env: env.Env) -> Result(#(MalType, env.Env), types.Error) { + case env.get(env, "DEBUG-EVAL") { + None -> Nil + Some(_) -> { + io.println("EVAL: " <> printer.pr_str(ast, True)) + } + } + + case ast { + Symbol(sym) -> + case env.get(env, sym) { + None -> Error(types.EvalSymbolNotFound(sym)) + Some(val) -> + case val { + LazyFunc(f) -> result.map(f(), fn(o) { #(o, env) }) + _ -> Ok(#(val, env)) + } + } + List([first, ..rest], _) -> { + eval_list(first, rest, env) + } + Vector(vec, _) -> { + use #(res, env) <- result.try( + list.try_fold(vec, #([], env), fn(acc, x) { + use #(res, env) <- result.try(eval(x, acc.1)) + + Ok(#([res, ..acc.0], env)) + }) + |> result.map(fn(o) { #(list.reverse(o.0), o.1) }), + ) + + Ok(#(Vector(res, types.Nil), env)) + } + HashMap(hm, _) -> { + use #(res, env) <- result.try( + hm + |> dict.to_list() + |> list.try_fold(#([], env), fn(acc, x) { + use #(res, env) <- result.try(eval(x.1, acc.1)) + + let pair = #(x.0, res) + Ok(#([pair, ..acc.0], env)) + }), + ) + + let ret = HashMap(dict.from_list(res), types.Nil) + Ok(#(ret, env)) + } + _ -> Ok(#(ast, env)) + } +} + +fn eval_list(first, rest, env) { + let eval_args = fn(env) { + list.try_fold(rest, #([], env), fn(acc, x) { + use #(res, env) <- result.try(eval(x, acc.1)) + + Ok(#([res, ..acc.0], env)) + }) + |> result.map(fn(o) { #(list.reverse(o.0), o.1) }) + } + + case first { + Symbol("def!") -> + case rest { + [key, val] -> def_special(key, val, env) + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } + Symbol("let*") -> + case rest { + [pairs, body] -> + case pairs { + List(l, _) | Vector(l, _) -> { + let_special(l, body, env) + } + _ -> types.wrong_type_err("list | vector", [pairs]) + } + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } + Symbol("if") -> + case rest { + [p, branch_true, ..branch_false] -> { + use #(predicate, env) <- result.try(eval(p, env)) + + use f <- result.try(case branch_false { + [] -> Ok(types.Nil) + [f] -> Ok(f) + _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) + }) + + case predicate { + Bool(False) | types.Nil -> { + eval(f, env) + } + _ -> { + eval(branch_true, env) + } + } + } + _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) + } + Symbol("do") -> { + use #(res, env) <- result.try(eval_args(env)) + + case list.last(res) { + Error(_) -> Error(types.EvalWrongArgLenGreaterThan(1, 0)) + Ok(last) -> Ok(#(last, env)) + } + } + Symbol("fn*") -> + case rest { + [List(params, _), body] | [Vector(params, _), body] -> { + use param_names <- result.try( + list.try_map(params, fn(x) { + case x { + Symbol(sym) -> Ok(sym) + _ -> Error(types.EvalFuncParamNotSymbol) + } + }), + ) + + let func = fn(a) { + use env <- result.try( + env.into_outer(env) + |> env.bind(param_names, a), + ) + + eval(body, env) |> result.map(fn(o) { o.0 }) + } + + Ok(#(Func(func, types.Nil), env)) + } + [first, second] -> types.wrong_type_err("list, any", [first, second]) + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } + _ -> { + use #(f, env) <- result.try(eval(first, env)) + use #(args, env) <- result.try(eval_args(env)) + + apply(f, args) |> result.map(fn(o) { #(o, env) }) + } + } +} + +fn fix(f) { + fn() { f(fix(f)) } +} + +fn fix_with_args(f) { + fn(x) { f(fix_with_args(f), x) } +} + +fn def_special(key, val, env) { + use k <- result.try(env.try_key(key)) + + // Needed for recursion + let gen_value: fn() -> Result(MalType, types.Error) = + fix(fn(gen_value) { + let env = env.set(env, k, LazyFunc(gen_value)) + eval(val, env) |> result.map(fn(o) { o.0 }) + }) + + use v <- result.try(gen_value()) + Ok(#(v, env.set(env, k, v))) +} + +fn get_pairs(list, acc) { + case list { + [a, b, ..rest] -> { + get_pairs(rest, [#(a, b), ..acc]) + } + [] -> Ok(list.reverse(acc)) + _ -> Error(types.StrErr("can't get bind pairs: count not divisible by 2")) + } +} + +fn let_special(pairs, body, original_env) { + use p <- result.try(get_pairs(pairs, [])) + use pairs <- result.try( + list.try_map(p, fn(x) { + use k <- result.try(env.try_key(x.0)) + Ok(#(k, x.1)) + }), + ) + + let env = env.into_outer(original_env) + + let gen_value = + fix_with_args(fn(gen_value, v) { + let env = + list.fold(pairs, env, fn(acc, p) { + env.set(acc, p.0, LazyFunc(fn() { gen_value(p.1) })) + }) + eval(v, env) |> result.map(fn(o) { o.0 }) + }) + + use let_env <- result.try( + list.try_fold(pairs, env, fn(acc, p) { + use v <- result.try(gen_value(p.1)) + + Ok(env.set(acc, p.0, v)) + }), + ) + + use #(res, _) <- result.try(eval(body, let_env)) + Ok(#(res, original_env)) +} + +fn read(str) { + reader.read_str(str) +} + +fn print(ast) { + printer.pr_str(ast, True) +} + +fn rep(str, env) { + use r <- result.try(read(str)) + use #(e, env) <- result.try(eval(r, env)) + + Ok(#(print(e), env)) +} diff --git a/impls/gleam/src/types.gleam b/impls/gleam/src/types.gleam index 9bf897321d..c1838ac5d8 100644 --- a/impls/gleam/src/types.gleam +++ b/impls/gleam/src/types.gleam @@ -1,4 +1,6 @@ import gleam/dict +import gleam/list +import gleam/string pub type MalRet = Result(MalType, Error) @@ -10,11 +12,60 @@ pub type MalType { String(String) Symbol(String) Keyword(String) - List(List(MalType), MalType) - Vector(List(MalType), MalType) - HashMap(dict.Dict(MalType, MalType), MalType) + List(List(MalType), meta: MalType) + Vector(List(MalType), meta: MalType) + HashMap(dict.Dict(MalType, MalType), meta: MalType) Builtin(fn(List(MalType)) -> MalRet) + Func(fn(List(MalType)) -> MalRet, meta: MalType) + LazyFunc(fn() -> MalRet) +} + +pub fn eq(a: MalType, b: MalType) -> Bool { + case a, b { + List(l, _), Vector(v, _) + | Vector(l, _), List(v, _) + | List(l, _), List(v, _) + | Vector(l, _), Vector(v, _) + -> seq_eq(l, v) + _, _ -> a == b + } +} + +fn seq_eq(a: List(MalType), b: List(MalType)) { + case a, b { + [x, ..rest], [y, ..rest2] -> + case eq(x, y) { + False -> False + True -> seq_eq(rest, rest2) + } + [], [] -> True + _, _ -> False + } +} + +pub fn wrong_type_err(expected: String, got: List(MalType)) { + let t = + got + |> list.map(fn(x) { + case x { + Nil -> "nil" + Int(_) -> "int" + Bool(_) -> "bool" + String(_) -> "string" + Symbol(_) -> "symbol" + Keyword(_) -> "keyword" + List(_, _) -> "list" + Vector(_, _) -> "vector" + HashMap(_, _) -> "hashmap" + Builtin(_) -> "builtin" + Func(_, _) -> "func" + LazyFunc(_) -> "lazy" + } + }) + |> string.join(", ") + + Error(EvalWrongType(expected, t)) } pub type Error { @@ -22,11 +73,17 @@ pub type Error { ReaderInvalidHashMap ReaderEmpyForm - EvalWrongArgLen(expected: Int, provided: Int) - EvalWrongType(expected: String, provided: String) + EvalWrongArgLen(expected: Int, got: Int) + EvalWrongArgLenRange(expected: #(Int, Int), got: Int) + EvalWrongArgLenGreaterThan(expected: Int, got: Int) + EvalWrongType(expected: String, got: String) EvalDivideByZero - EvalApplyType(provided: MalType) + EvalApplyType(got: MalType) EvalSymbolNotFound(symbol: String) + EvalFuncParamNotSymbol EnvToKey(ast: MalType) + + /// Used for one off errors + StrErr(String) } From 295edf7ae3299ff530b0c08449fe6480c3b8cb25 Mon Sep 17 00:00:00 2001 From: Donda Date: Sun, 24 May 2026 21:30:23 +0200 Subject: [PATCH 07/20] gleam: step5 --- impls/gleam/gleam.toml | 2 + impls/gleam/manifest.toml | 4 + impls/gleam/src/env.gleam | 34 +++-- impls/gleam/src/mut_cell.gleam | 50 +++++++ impls/gleam/src/printer.gleam | 1 - impls/gleam/src/step3_env.gleam | 65 ++++---- impls/gleam/src/step4_if_fn_do.gleam | 176 +++++++--------------- impls/gleam/src/step5_tco.gleam | 212 +++++++++++++++++++++++++++ impls/gleam/src/types.gleam | 2 - 9 files changed, 371 insertions(+), 175 deletions(-) create mode 100644 impls/gleam/src/mut_cell.gleam create mode 100644 impls/gleam/src/step5_tco.gleam diff --git a/impls/gleam/gleam.toml b/impls/gleam/gleam.toml index ea5eea3329..91394aa50a 100644 --- a/impls/gleam/gleam.toml +++ b/impls/gleam/gleam.toml @@ -6,6 +6,8 @@ gleam_stdlib = ">= 0.44.0 and < 2.0.0" argv = ">= 1.0.2 and < 2.0.0" input = ">= 1.0.1 and < 2.0.0" gleam_regexp = ">= 1.1.1 and < 2.0.0" +gleam_erlang = ">= 1.3.0 and < 2.0.0" +gleam_otp = ">= 1.2.0 and < 2.0.0" [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/impls/gleam/manifest.toml b/impls/gleam/manifest.toml index 9686608af3..7e5b8da573 100644 --- a/impls/gleam/manifest.toml +++ b/impls/gleam/manifest.toml @@ -3,6 +3,8 @@ packages = [ { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, + { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, { name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" }, { name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" }, @@ -11,6 +13,8 @@ packages = [ [requirements] argv = { version = ">= 1.0.2 and < 2.0.0" } +gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" } +gleam_otp = { version = ">= 1.2.0 and < 2.0.0" } gleam_regexp = { version = ">= 1.1.1 and < 2.0.0" } gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/impls/gleam/src/env.gleam b/impls/gleam/src/env.gleam index 9ec48ba7f7..75e0d3db30 100644 --- a/impls/gleam/src/env.gleam +++ b/impls/gleam/src/env.gleam @@ -1,17 +1,20 @@ import gleam/dict.{type Dict} import gleam/option.{type Option, None, Some} +import mut_cell.{type MutCell} import types.{type Error, type MalType} pub opaque type Env { - Env(outer: Option(Env), data: Dict(String, MalType)) + Env(outer: Option(Env), data: MutCell(Dict(String, MalType))) } -pub fn new() -> Env { - Env(None, dict.new()) +pub fn new(outer, data) -> Env { + Env(outer, mut_cell.new(data)) } pub fn get(env: Env, key: String) -> Option(MalType) { - case dict.get(env.data, key) { + let hm = mut_cell.get(env.data) + + case dict.get(hm, key) { Error(_) -> { case env.outer { None -> None @@ -22,10 +25,11 @@ pub fn get(env: Env, key: String) -> Option(MalType) { } } -pub fn set(env: Env, key: String, val: MalType) -> Env { - env.data - |> dict.insert(key, val) - |> Env(env.outer, _) +pub fn set(env: Env, key: String, val: MalType) { + mut_cell.update(env.data, fn(data) { + data + |> dict.insert(key, val) + }) } pub fn bind(env: Env, from: List(String), to: List(MalType)) { @@ -34,29 +38,27 @@ pub fn bind(env: Env, from: List(String), to: List(MalType)) { [sym, ..from_rest] -> case to { [t, ..rest] -> { - let env = set(env, sym, t) + set(env, sym, t) bind(env, from_rest, rest) } [] -> Error(types.StrErr("can't bind env: not enough args provided")) } - [] if to == [] -> Ok(env) + [] if to == [] -> Ok(Nil) _ -> Error(types.StrErr("can't bind env: too many args provided")) } } pub fn from_list(input: List(#(String, MalType)), outer: Option(Env)) -> Env { - input - |> dict.from_list() - |> Env(outer, _) + let data = dict.from_list(input) + Env(outer, mut_cell.new(data)) } pub fn from_dict(input: Dict(String, MalType), outer: Option(Env)) -> Env { - input - |> Env(outer, _) + Env(outer, mut_cell.new(input)) } pub fn into_outer(outer: Env) -> Env { - Env(Some(outer), dict.new()) + Env(Some(outer), mut_cell.new(dict.new())) } pub fn try_key(ast: MalType) -> Result(String, Error) { diff --git a/impls/gleam/src/mut_cell.gleam b/impls/gleam/src/mut_cell.gleam new file mode 100644 index 0000000000..52eaba0aad --- /dev/null +++ b/impls/gleam/src/mut_cell.gleam @@ -0,0 +1,50 @@ +import gleam/erlang/process.{type Subject} +import gleam/otp/actor + +pub fn new(value: a) { + let handle_message = fn(state: a, message: Message(a)) -> actor.Next( + a, + Message(a), + ) { + case message { + Get(reply) -> { + actor.send(reply, state) + actor.continue(state) + } + Set(i) -> { + actor.continue(i) + } + Update(f) -> { + actor.continue(f(state)) + } + } + } + + let assert Ok(actor) = + actor.new(value) + |> actor.on_message(handle_message) + |> actor.start + actor +} + +pub type Message(a) { + Get(Subject(a)) + Set(a) + Update(fn(a) -> a) +} + +pub type MutCell(a) = + actor.Started(process.Subject(Message(a))) + +pub fn get(cell: MutCell(a)) { + actor.call(cell.data, waiting: 10, sending: Get) +} + +pub fn set(cell: MutCell(a), new: a) { + actor.send(cell.data, Set(new)) + actor.continue(cell) +} + +pub fn update(cell: MutCell(a), f: fn(a) -> a) { + actor.send(cell.data, Update(f)) +} diff --git a/impls/gleam/src/printer.gleam b/impls/gleam/src/printer.gleam index cff9c064ab..efb5d67512 100644 --- a/impls/gleam/src/printer.gleam +++ b/impls/gleam/src/printer.gleam @@ -97,6 +97,5 @@ pub fn pr_str(ast: types.MalType, print_readability: Bool) -> String { } types.Builtin(..) -> "#" types.Func(..) -> "#" - types.LazyFunc(_) -> "#" } } diff --git a/impls/gleam/src/step3_env.gleam b/impls/gleam/src/step3_env.gleam index a517956529..38fe4c7257 100644 --- a/impls/gleam/src/step3_env.gleam +++ b/impls/gleam/src/step3_env.gleam @@ -76,7 +76,7 @@ fn apply(x: MalType, args: List(MalType)) -> MalRet { } } -fn eval(ast: MalType, env: env.Env) -> Result(#(MalType, env.Env), types.Error) { +fn eval(ast: MalType, env: env.Env) -> MalRet { case env.get(env, "DEBUG-EVAL") { None -> Nil Some(_) -> { @@ -88,29 +88,22 @@ fn eval(ast: MalType, env: env.Env) -> Result(#(MalType, env.Env), types.Error) Symbol(sym) -> { case env.get(env, sym) { None -> Error(types.EvalSymbolNotFound(sym)) - Some(val) -> Ok(#(val, env)) + Some(val) -> Ok(val) } } List([l, ..rest], _) -> { - let eval_args = fn(env) { - list.try_fold(rest, #([], env), fn(acc, x) { - use #(res, env) <- result.try(eval(x, acc.1)) - - Ok(#([res, ..acc.0], env)) - }) - |> result.map(fn(o) { #(list.reverse(o.0), o.1) }) - } + let eval_args = fn(env) { list.try_map(rest, fn(x) { eval(x, env) }) } case l { Symbol("def!") -> case rest { [key, val] -> { - use #(res, _) <- result.try(eval(val, env)) + use res <- result.try(eval(val, env)) use k <- result.try(env.try_key(key)) - let env = env.set(env, k, res) + env.set(env, k, res) - Ok(#(res, env)) + Ok(res) } _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) } @@ -119,11 +112,11 @@ fn eval(ast: MalType, env: env.Env) -> Result(#(MalType, env.Env), types.Error) [pairs, closure] -> { case pairs { List(l, _) | Vector(l, _) -> { - use e <- result.try(let_special(l, [], env)) - let new_env = env.into_outer(e) + let new_env = env.into_outer(env) + use e <- result.try(let_special(l, [], new_env)) - use #(res, _) <- result.try(eval(closure, new_env)) - Ok(#(res, env)) + use res <- result.try(eval(closure, e)) + Ok(res) } _ -> Error(types.EvalWrongType("list | vector", "")) } @@ -133,51 +126,53 @@ fn eval(ast: MalType, env: env.Env) -> Result(#(MalType, env.Env), types.Error) } _ -> { - use #(f, env) <- result.try(eval(l, env)) - use #(args, env) <- result.try(eval_args(env)) + use f <- result.try(eval(l, env)) + use args <- result.try(eval_args(env)) + + echo #(f, args) - apply(f, args) |> result.map(fn(o) { #(o, env) }) + apply(f, args) } } } Vector(vec, _) -> { - use #(res, env) <- result.try( - list.try_fold(vec, #([], env), fn(acc, x) { - use #(res, env) <- result.try(eval(x, acc.1)) + use res <- result.try( + list.try_fold(vec, [], fn(acc, x) { + use res <- result.try(eval(x, env)) - Ok(#([res, ..acc.0], env)) + Ok([res, ..acc]) }) - |> result.map(fn(o) { #(list.reverse(o.0), o.1) }), + |> result.map(fn(o) { list.reverse(o) }), ) - Ok(#(Vector(res, types.Nil), env)) + Ok(Vector(res, types.Nil)) } HashMap(hm, _) -> { - use #(res, env) <- result.try( + use res <- result.try( hm |> dict.to_list() - |> list.try_fold(#([], env), fn(acc, x) { - use #(res, env) <- result.try(eval(x.1, acc.1)) + |> list.try_fold([], fn(acc, x) { + use res <- result.try(eval(x.1, env)) let pair = #(x.0, res) - Ok(#([pair, ..acc.0], env)) + Ok([pair, ..acc]) }), ) let ret = HashMap(dict.from_list(res), types.Nil) - Ok(#(ret, env)) + Ok(ret) } - _ -> Ok(#(ast, env)) + _ -> Ok(ast) } } fn let_special(pairs, acc, env) { case pairs { [key, val, ..rest] -> { - use #(res, env) <- result.try(eval(val, env)) + use res <- result.try(eval(val, env)) use k <- result.try(env.try_key(key)) - let env = env.set(env, k, res) + env.set(env, k, res) let_special(rest, [res, ..acc], env) } @@ -197,7 +192,7 @@ fn print(ast) { fn rep(str, env) { use r <- result.try(read(str)) - use #(e, env) <- result.try(eval(r, env)) + use e <- result.try(eval(r, env)) Ok(#(print(e), env)) } diff --git a/impls/gleam/src/step4_if_fn_do.gleam b/impls/gleam/src/step4_if_fn_do.gleam index 5105d7ef78..b5c8ee1876 100644 --- a/impls/gleam/src/step4_if_fn_do.gleam +++ b/impls/gleam/src/step4_if_fn_do.gleam @@ -9,14 +9,13 @@ import printer import reader import readline.{readline} import types.{ - type MalRet, type MalType, Bool, Builtin, Func, HashMap, LazyFunc, List, - Symbol, Vector, + type MalRet, type MalType, Bool, Builtin, Func, HashMap, List, Symbol, Vector, } pub fn main() -> Nil { let env = env.from_list(core.ns(), None) - let assert Ok(#(_, env)) = rep("(def! not (fn* (a) (if a false true)))", env) + let assert Ok(_) = rep("(def! not (fn* (a) (if a false true)))", env) let _ = loop(env) @@ -34,7 +33,7 @@ fn loop(env) { printer.pr_err(err) |> io.println_error() loop(env) } - Ok(#(str, env)) -> { + Ok(str) -> { io.println(str) loop(env) @@ -46,76 +45,42 @@ fn apply(x: MalType, args: List(MalType)) -> MalRet { case x { Builtin(f) -> f(args) Func(f, _) -> f(args) - LazyFunc(f) -> { - use res <- result.try(f()) - apply(res, args) - } _ -> Error(types.EvalApplyType(x)) } } -fn eval(ast: MalType, env: env.Env) -> Result(#(MalType, env.Env), types.Error) { +fn eval(ast: MalType, env: env.Env) -> MalRet { case env.get(env, "DEBUG-EVAL") { - None -> Nil - Some(_) -> { + None | Some(Bool(False)) | Some(types.Nil) -> Nil + _ -> { io.println("EVAL: " <> printer.pr_str(ast, True)) } } case ast { Symbol(sym) -> - case env.get(env, sym) { - None -> Error(types.EvalSymbolNotFound(sym)) - Some(val) -> - case val { - LazyFunc(f) -> result.map(f(), fn(o) { #(o, env) }) - _ -> Ok(#(val, env)) - } - } - List([first, ..rest], _) -> { - eval_list(first, rest, env) - } - Vector(vec, _) -> { - use #(res, env) <- result.try( - list.try_fold(vec, #([], env), fn(acc, x) { - use #(res, env) <- result.try(eval(x, acc.1)) - - Ok(#([res, ..acc.0], env)) - }) - |> result.map(fn(o) { #(list.reverse(o.0), o.1) }), - ) - - Ok(#(Vector(res, types.Nil), env)) - } + env.get(env, sym) + |> option.to_result(types.EvalSymbolNotFound(sym)) + List([first, ..rest], _) -> eval_list(first, rest, env) + Vector(vec, _) -> + list.try_map(vec, fn(x) { eval(x, env) }) + |> result.map(fn(x) { Vector(x, types.Nil) }) HashMap(hm, _) -> { - use #(res, env) <- result.try( - hm - |> dict.to_list() - |> list.try_fold(#([], env), fn(acc, x) { - use #(res, env) <- result.try(eval(x.1, acc.1)) - - let pair = #(x.0, res) - Ok(#([pair, ..acc.0], env)) - }), - ) - - let ret = HashMap(dict.from_list(res), types.Nil) - Ok(#(ret, env)) + dict.to_list(hm) + |> list.try_map(fn(x) { + let key = x.0 + let val = x.1 + + eval(val, env) + |> result.map(fn(e) { #(key, e) }) + }) + |> result.map(fn(res) { HashMap(dict.from_list(res), types.Nil) }) } - _ -> Ok(#(ast, env)) + _ -> Ok(ast) } } -fn eval_list(first, rest, env) { - let eval_args = fn(env) { - list.try_fold(rest, #([], env), fn(acc, x) { - use #(res, env) <- result.try(eval(x, acc.1)) - - Ok(#([res, ..acc.0], env)) - }) - |> result.map(fn(o) { #(list.reverse(o.0), o.1) }) - } - +fn eval_list(first, rest, env) -> Result(MalType, types.Error) { case first { Symbol("def!") -> case rest { @@ -135,10 +100,10 @@ fn eval_list(first, rest, env) { } Symbol("if") -> case rest { - [p, branch_true, ..branch_false] -> { - use #(predicate, env) <- result.try(eval(p, env)) + [predicate, branch_true, ..branch_false] -> { + use predicate <- result.try(eval(predicate, env)) - use f <- result.try(case branch_false { + use false_ast <- result.try(case branch_false { [] -> Ok(types.Nil) [f] -> Ok(f) _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) @@ -146,7 +111,7 @@ fn eval_list(first, rest, env) { case predicate { Bool(False) | types.Nil -> { - eval(f, env) + eval(false_ast, env) } _ -> { eval(branch_true, env) @@ -156,11 +121,12 @@ fn eval_list(first, rest, env) { _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) } Symbol("do") -> { - use #(res, env) <- result.try(eval_args(env)) + let do = list.take(rest, list.length(rest) - 1) + use _ <- result.try(list.try_map(do, fn(x) { eval(x, env) })) - case list.last(res) { + case list.last(rest) { Error(_) -> Error(types.EvalWrongArgLenGreaterThan(1, 0)) - Ok(last) -> Ok(#(last, env)) + Ok(last) -> eval(last, env) } } Symbol("fn*") -> @@ -175,49 +141,32 @@ fn eval_list(first, rest, env) { }), ) - let func = fn(a) { - use env <- result.try( - env.into_outer(env) - |> env.bind(param_names, a), - ) + let func = fn(args) { + let fn_env = env.into_outer(env) + use _ <- result.try(env.bind(fn_env, param_names, args)) - eval(body, env) |> result.map(fn(o) { o.0 }) + eval(body, fn_env) } - Ok(#(Func(func, types.Nil), env)) + Ok(Func(func, types.Nil)) } - [first, second] -> types.wrong_type_err("list, any", [first, second]) + [_, _] as l -> types.wrong_type_err("list, any", l) _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) } _ -> { - use #(f, env) <- result.try(eval(first, env)) - use #(args, env) <- result.try(eval_args(env)) + use f <- result.try(eval(first, env)) + use args <- result.try(list.try_map(rest, fn(x) { eval(x, env) })) - apply(f, args) |> result.map(fn(o) { #(o, env) }) + apply(f, args) } } } -fn fix(f) { - fn() { f(fix(f)) } -} - -fn fix_with_args(f) { - fn(x) { f(fix_with_args(f), x) } -} - fn def_special(key, val, env) { use k <- result.try(env.try_key(key)) - - // Needed for recursion - let gen_value: fn() -> Result(MalType, types.Error) = - fix(fn(gen_value) { - let env = env.set(env, k, LazyFunc(gen_value)) - eval(val, env) |> result.map(fn(o) { o.0 }) - }) - - use v <- result.try(gen_value()) - Ok(#(v, env.set(env, k, v))) + use v <- result.try(eval(val, env)) + env.set(env, k, v) + Ok(v) } fn get_pairs(list, acc) { @@ -230,36 +179,21 @@ fn get_pairs(list, acc) { } } -fn let_special(pairs, body, original_env) { - use p <- result.try(get_pairs(pairs, [])) - use pairs <- result.try( - list.try_map(p, fn(x) { - use k <- result.try(env.try_key(x.0)) - Ok(#(k, x.1)) - }), - ) - - let env = env.into_outer(original_env) - - let gen_value = - fix_with_args(fn(gen_value, v) { - let env = - list.fold(pairs, env, fn(acc, p) { - env.set(acc, p.0, LazyFunc(fn() { gen_value(p.1) })) - }) - eval(v, env) |> result.map(fn(o) { o.0 }) - }) +fn let_special(pairs, body, env) { + use pairs <- result.try(get_pairs(pairs, [])) + let let_env = env.into_outer(env) - use let_env <- result.try( - list.try_fold(pairs, env, fn(acc, p) { - use v <- result.try(gen_value(p.1)) + use _ <- result.try( + list.try_map(pairs, fn(x) { + use key <- result.try(env.try_key(x.0)) + use val <- result.try(eval(x.1, let_env)) + env.set(let_env, key, val) - Ok(env.set(acc, p.0, v)) + Ok(Nil) }), ) - use #(res, _) <- result.try(eval(body, let_env)) - Ok(#(res, original_env)) + eval(body, let_env) } fn read(str) { @@ -272,7 +206,7 @@ fn print(ast) { fn rep(str, env) { use r <- result.try(read(str)) - use #(e, env) <- result.try(eval(r, env)) + use e <- result.try(eval(r, env)) - Ok(#(print(e), env)) + Ok(print(e)) } diff --git a/impls/gleam/src/step5_tco.gleam b/impls/gleam/src/step5_tco.gleam new file mode 100644 index 0000000000..b5c8ee1876 --- /dev/null +++ b/impls/gleam/src/step5_tco.gleam @@ -0,0 +1,212 @@ +import core +import env +import gleam/dict +import gleam/io +import gleam/list +import gleam/option.{None, Some} +import gleam/result +import printer +import reader +import readline.{readline} +import types.{ + type MalRet, type MalType, Bool, Builtin, Func, HashMap, List, Symbol, Vector, +} + +pub fn main() -> Nil { + let env = env.from_list(core.ns(), None) + + let assert Ok(_) = rep("(def! not (fn* (a) (if a false true)))", env) + + let _ = loop(env) + + Nil +} + +fn loop(env) { + use input <- result.try(readline("user> ")) + + let prn = + input + |> rep(env) + case prn { + Error(err) -> { + printer.pr_err(err) |> io.println_error() + loop(env) + } + Ok(str) -> { + io.println(str) + + loop(env) + } + } +} + +fn apply(x: MalType, args: List(MalType)) -> MalRet { + case x { + Builtin(f) -> f(args) + Func(f, _) -> f(args) + _ -> Error(types.EvalApplyType(x)) + } +} + +fn eval(ast: MalType, env: env.Env) -> MalRet { + case env.get(env, "DEBUG-EVAL") { + None | Some(Bool(False)) | Some(types.Nil) -> Nil + _ -> { + io.println("EVAL: " <> printer.pr_str(ast, True)) + } + } + + case ast { + Symbol(sym) -> + env.get(env, sym) + |> option.to_result(types.EvalSymbolNotFound(sym)) + List([first, ..rest], _) -> eval_list(first, rest, env) + Vector(vec, _) -> + list.try_map(vec, fn(x) { eval(x, env) }) + |> result.map(fn(x) { Vector(x, types.Nil) }) + HashMap(hm, _) -> { + dict.to_list(hm) + |> list.try_map(fn(x) { + let key = x.0 + let val = x.1 + + eval(val, env) + |> result.map(fn(e) { #(key, e) }) + }) + |> result.map(fn(res) { HashMap(dict.from_list(res), types.Nil) }) + } + _ -> Ok(ast) + } +} + +fn eval_list(first, rest, env) -> Result(MalType, types.Error) { + case first { + Symbol("def!") -> + case rest { + [key, val] -> def_special(key, val, env) + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } + Symbol("let*") -> + case rest { + [pairs, body] -> + case pairs { + List(l, _) | Vector(l, _) -> { + let_special(l, body, env) + } + _ -> types.wrong_type_err("list | vector", [pairs]) + } + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } + Symbol("if") -> + case rest { + [predicate, branch_true, ..branch_false] -> { + use predicate <- result.try(eval(predicate, env)) + + use false_ast <- result.try(case branch_false { + [] -> Ok(types.Nil) + [f] -> Ok(f) + _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) + }) + + case predicate { + Bool(False) | types.Nil -> { + eval(false_ast, env) + } + _ -> { + eval(branch_true, env) + } + } + } + _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) + } + Symbol("do") -> { + let do = list.take(rest, list.length(rest) - 1) + use _ <- result.try(list.try_map(do, fn(x) { eval(x, env) })) + + case list.last(rest) { + Error(_) -> Error(types.EvalWrongArgLenGreaterThan(1, 0)) + Ok(last) -> eval(last, env) + } + } + Symbol("fn*") -> + case rest { + [List(params, _), body] | [Vector(params, _), body] -> { + use param_names <- result.try( + list.try_map(params, fn(x) { + case x { + Symbol(sym) -> Ok(sym) + _ -> Error(types.EvalFuncParamNotSymbol) + } + }), + ) + + let func = fn(args) { + let fn_env = env.into_outer(env) + use _ <- result.try(env.bind(fn_env, param_names, args)) + + eval(body, fn_env) + } + + Ok(Func(func, types.Nil)) + } + [_, _] as l -> types.wrong_type_err("list, any", l) + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } + _ -> { + use f <- result.try(eval(first, env)) + use args <- result.try(list.try_map(rest, fn(x) { eval(x, env) })) + + apply(f, args) + } + } +} + +fn def_special(key, val, env) { + use k <- result.try(env.try_key(key)) + use v <- result.try(eval(val, env)) + env.set(env, k, v) + Ok(v) +} + +fn get_pairs(list, acc) { + case list { + [a, b, ..rest] -> { + get_pairs(rest, [#(a, b), ..acc]) + } + [] -> Ok(list.reverse(acc)) + _ -> Error(types.StrErr("can't get bind pairs: count not divisible by 2")) + } +} + +fn let_special(pairs, body, env) { + use pairs <- result.try(get_pairs(pairs, [])) + let let_env = env.into_outer(env) + + use _ <- result.try( + list.try_map(pairs, fn(x) { + use key <- result.try(env.try_key(x.0)) + use val <- result.try(eval(x.1, let_env)) + env.set(let_env, key, val) + + Ok(Nil) + }), + ) + + eval(body, let_env) +} + +fn read(str) { + reader.read_str(str) +} + +fn print(ast) { + printer.pr_str(ast, True) +} + +fn rep(str, env) { + use r <- result.try(read(str)) + use e <- result.try(eval(r, env)) + + Ok(print(e)) +} diff --git a/impls/gleam/src/types.gleam b/impls/gleam/src/types.gleam index c1838ac5d8..c481a6566d 100644 --- a/impls/gleam/src/types.gleam +++ b/impls/gleam/src/types.gleam @@ -18,7 +18,6 @@ pub type MalType { Builtin(fn(List(MalType)) -> MalRet) Func(fn(List(MalType)) -> MalRet, meta: MalType) - LazyFunc(fn() -> MalRet) } pub fn eq(a: MalType, b: MalType) -> Bool { @@ -60,7 +59,6 @@ pub fn wrong_type_err(expected: String, got: List(MalType)) { HashMap(_, _) -> "hashmap" Builtin(_) -> "builtin" Func(_, _) -> "func" - LazyFunc(_) -> "lazy" } }) |> string.join(", ") From 9ddee39465fba3fd62c6185b5cf584ad7e6e7345 Mon Sep 17 00:00:00 2001 From: Donda Date: Tue, 26 May 2026 00:39:33 +0200 Subject: [PATCH 08/20] gleam: step6 --- impls/gleam/gleam.toml | 1 + impls/gleam/manifest.toml | 3 + impls/gleam/src/core.gleam | 111 +++++++++++++- impls/gleam/src/mut_cell.gleam | 10 +- impls/gleam/src/printer.gleam | 3 + impls/gleam/src/reader.gleam | 3 +- impls/gleam/src/step3_env.gleam | 2 - impls/gleam/src/step6_file.gleam | 240 +++++++++++++++++++++++++++++++ impls/gleam/src/types.gleam | 3 + 9 files changed, 365 insertions(+), 11 deletions(-) create mode 100644 impls/gleam/src/step6_file.gleam diff --git a/impls/gleam/gleam.toml b/impls/gleam/gleam.toml index 91394aa50a..622f5421fe 100644 --- a/impls/gleam/gleam.toml +++ b/impls/gleam/gleam.toml @@ -8,6 +8,7 @@ input = ">= 1.0.1 and < 2.0.0" gleam_regexp = ">= 1.1.1 and < 2.0.0" gleam_erlang = ">= 1.3.0 and < 2.0.0" gleam_otp = ">= 1.2.0 and < 2.0.0" +simplifile = ">= 2.4.0 and < 3.0.0" [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/impls/gleam/manifest.toml b/impls/gleam/manifest.toml index 7e5b8da573..e1e5378705 100644 --- a/impls/gleam/manifest.toml +++ b/impls/gleam/manifest.toml @@ -3,12 +3,14 @@ packages = [ { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, { name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" }, { name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" }, { name = "input", version = "1.0.1", build_tools = ["gleam"], requirements = [], otp_app = "input", source = "hex", outer_checksum = "FE84CDADC78A1367E4AFD561A529825A8FEC88D165CBDF511FD3226CABCDEE6F" }, + { name = "simplifile", version = "2.4.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "7C18AFA4FED0B4CE1FA5B0B4BAC1FA1744427054EA993565F6F3F82E5453170D" }, ] [requirements] @@ -19,3 +21,4 @@ gleam_regexp = { version = ">= 1.1.1 and < 2.0.0" } gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } input = { version = ">= 1.0.1 and < 2.0.0" } +simplifile = { version = ">= 2.4.0 and < 3.0.0" } diff --git a/impls/gleam/src/core.gleam b/impls/gleam/src/core.gleam index eecbba9c69..77242e0a88 100644 --- a/impls/gleam/src/core.gleam +++ b/impls/gleam/src/core.gleam @@ -1,9 +1,15 @@ +import argv import gleam/io import gleam/list import gleam/result import gleam/string +import mut_cell import printer -import types.{type Error, type MalType, Bool, Builtin, Int, List, String, Vector} +import reader +import simplifile +import types.{ + type Error, type MalType, Atom, Bool, Builtin, Func, Int, List, String, Vector, +} pub fn ns() { let int_op = fn(with: fn(Int, Int) -> Result(MalType, Error)) { @@ -34,11 +40,12 @@ pub fn ns() { } }), ), + + // Bool #(">", int_op(fn(a, b) { Ok(Bool(a > b)) })), #(">=", int_op(fn(a, b) { Ok(Bool(a >= b)) })), #("<", int_op(fn(a, b) { Ok(Bool(a < b)) })), #("<=", int_op(fn(a, b) { Ok(Bool(a <= b)) })), - // Bool #( "=", Builtin(fn(args) { @@ -61,12 +68,22 @@ pub fn ns() { "empty?", Builtin(fn(args) { case args { - [List([], _), ..] | [Vector([], _), ..] -> Ok(Bool(True)) - [List(_, _), ..] | [Vector(_, _), ..] -> Ok(Bool(False)) - _ -> Error(types.EvalWrongArgLenGreaterThan(1, 0)) + [List([], _)] | [Vector([], _)] -> Ok(Bool(True)) + [List(_, _)] | [Vector(_, _)] -> Ok(Bool(False)) + _ -> types.wrong_type_err("list | vector", args) } }), ), + #( + "atom?", + Builtin(fn(args) { + case args { + [Atom(_)] -> Ok(Bool(True)) + _ -> Ok(Bool(False)) + } + }), + ), + // String #( "pr-str", @@ -88,6 +105,7 @@ pub fn ns() { ) }), ), + // IO #( "prn", @@ -107,17 +125,98 @@ pub fn ns() { Ok(types.Nil) }), ), + #( + "slurp", + Builtin(fn(args) { + case args { + [String(path)] -> { + case simplifile.read(path) { + Ok(str) -> Ok(String(str)) + Error(err) -> + Error(types.StrErr( + "failed to open file: " <> string.inspect(err), + )) + } + } + _ -> types.wrong_type_err("string", args) + } + }), + ), + // Declare #("list", Builtin(fn(args) { Ok(List(args, types.Nil)) })), + #( + "atom", + Builtin(fn(args) { + case args { + [data] -> Ok(Atom(mut_cell.new(data))) + _ -> types.wrong_type_err("any", args) + } + }), + ), + + // Atom + #( + "deref", + Builtin(fn(args) { + case args { + [Atom(ref)] -> Ok(mut_cell.get(ref)) + _ -> types.wrong_type_err("atom", args) + } + }), + ), + #( + "reset!", + Builtin(fn(args) { + case args { + [Atom(ref), data] -> { + mut_cell.set(ref, data) + Ok(data) + } + _ -> types.wrong_type_err("atom", args) + } + }), + ), + #( + "swap!", + Builtin(fn(args) { + case args { + [Atom(ref), Func(f, _), ..rest] | [Atom(ref), Builtin(f), ..rest] -> { + mut_cell.try_update(ref, fn(data) { f([data, ..rest]) }) + } + _ -> types.wrong_type_err("atom, func", args) + } + }), + ), + // Other #( "count", Builtin(fn(args) { case args { - [List(l, _), ..] | [Vector(l, _), ..] -> Ok(Int(list.length(l))) + [List(l, _)] | [Vector(l, _)] -> Ok(Int(list.length(l))) _ -> Ok(Int(0)) } }), ), + #( + "read-string", + Builtin(fn(args) { + case args { + [String(str)] -> reader.read_str(str) + _ -> types.wrong_type_err("string", args) + } + }), + ), + #( + "*ARGV*", + List( + argv.load().arguments + // Ignore file path arg + |> list.drop(1) + |> list.map(fn(arg) { String(arg) }), + types.Nil, + ), + ), ] } diff --git a/impls/gleam/src/mut_cell.gleam b/impls/gleam/src/mut_cell.gleam index 52eaba0aad..48de0d97cf 100644 --- a/impls/gleam/src/mut_cell.gleam +++ b/impls/gleam/src/mut_cell.gleam @@ -1,5 +1,6 @@ import gleam/erlang/process.{type Subject} import gleam/otp/actor +import gleam/result pub fn new(value: a) { let handle_message = fn(state: a, message: Message(a)) -> actor.Next( @@ -42,9 +43,16 @@ pub fn get(cell: MutCell(a)) { pub fn set(cell: MutCell(a), new: a) { actor.send(cell.data, Set(new)) - actor.continue(cell) + Nil } pub fn update(cell: MutCell(a), f: fn(a) -> a) { actor.send(cell.data, Update(f)) } + +pub fn try_update(cell: MutCell(a), f: fn(a) -> Result(a, e)) { + let data = get(cell) + use res <- result.try(f(data)) + set(cell, res) + Ok(res) +} diff --git a/impls/gleam/src/printer.gleam b/impls/gleam/src/printer.gleam index efb5d67512..2d01235fe5 100644 --- a/impls/gleam/src/printer.gleam +++ b/impls/gleam/src/printer.gleam @@ -2,6 +2,7 @@ import gleam/dict import gleam/int import gleam/list import gleam/string +import mut_cell import types.{Bool, HashMap, Int, Keyword, List, Nil, String, Symbol, Vector} pub fn pr_err(err: types.Error) -> String { @@ -97,5 +98,7 @@ pub fn pr_str(ast: types.MalType, print_readability: Bool) -> String { } types.Builtin(..) -> "#" types.Func(..) -> "#" + types.Atom(ref) -> + "(atom " <> pr_str(mut_cell.get(ref), print_readability) <> ")" } } diff --git a/impls/gleam/src/reader.gleam b/impls/gleam/src/reader.gleam index 48059fc591..07fe4cd623 100644 --- a/impls/gleam/src/reader.gleam +++ b/impls/gleam/src/reader.gleam @@ -34,10 +34,9 @@ fn tokenize(str: String) { <> ")", ) - // TODO remove the need for this filter - // something to do with the regex or regexp.split regexp.split(re, str) |> list.filter(fn(x) { x != "" }) + |> list.filter(fn(x) { !string.starts_with(x, ";") }) } fn read_form(x: List(String)) { diff --git a/impls/gleam/src/step3_env.gleam b/impls/gleam/src/step3_env.gleam index 38fe4c7257..ba9adee736 100644 --- a/impls/gleam/src/step3_env.gleam +++ b/impls/gleam/src/step3_env.gleam @@ -129,8 +129,6 @@ fn eval(ast: MalType, env: env.Env) -> MalRet { use f <- result.try(eval(l, env)) use args <- result.try(eval_args(env)) - echo #(f, args) - apply(f, args) } } diff --git a/impls/gleam/src/step6_file.gleam b/impls/gleam/src/step6_file.gleam new file mode 100644 index 0000000000..2e14185a02 --- /dev/null +++ b/impls/gleam/src/step6_file.gleam @@ -0,0 +1,240 @@ +import argv +import core +import env +import gleam/dict +import gleam/io +import gleam/list +import gleam/option.{None, Some} +import gleam/result +import printer +import reader +import readline.{readline} +import types.{ + type MalRet, type MalType, Bool, Builtin, Func, HashMap, List, Symbol, Vector, +} + +pub fn main() -> Nil { + let env = env.from_list(core.ns(), None) + + let assert Ok(_) = + [ + "(def! not (fn* (a) (if a false true)))", + "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", + ] + |> list.try_map(fn(x) { rep(x, env) }) + + env.set( + env, + "eval", + Builtin(fn(args) { + case args { + [ast] -> eval(ast, env) + _ -> types.wrong_type_err("any", args) + } + }), + ) + + case argv.load().arguments { + [file, ..] -> { + let _ = case rep("(load-file \"" <> file <> "\")", env) { + Error(err) -> printer.pr_err(err) |> io.println_error() + _ -> Nil + } + } + _ -> { + let _ = loop(env) + Nil + } + } +} + +fn loop(env) { + use input <- result.try(readline("user> ")) + + input + |> rep(env) + |> print_res() + + loop(env) +} + +fn print_res(res) { + case res { + Ok(str) -> { + io.println(str) + } + Error(err) -> { + printer.pr_err(err) |> io.println_error() + } + } +} + +fn apply(x: MalType, args: List(MalType)) -> MalRet { + case x { + Builtin(f) -> f(args) + Func(f, _) -> f(args) + _ -> Error(types.EvalApplyType(x)) + } +} + +fn eval(ast: MalType, env: env.Env) -> MalRet { + case env.get(env, "DEBUG-EVAL") { + None | Some(Bool(False)) | Some(types.Nil) -> Nil + _ -> { + io.println("EVAL: " <> printer.pr_str(ast, True)) + } + } + + case ast { + Symbol(sym) -> + env.get(env, sym) + |> option.to_result(types.EvalSymbolNotFound(sym)) + List([first, ..rest], _) -> eval_list(first, rest, env) + Vector(vec, _) -> + list.try_map(vec, fn(x) { eval(x, env) }) + |> result.map(fn(x) { Vector(x, types.Nil) }) + HashMap(hm, _) -> { + dict.to_list(hm) + |> list.try_map(fn(x) { + let key = x.0 + let val = x.1 + + eval(val, env) + |> result.map(fn(e) { #(key, e) }) + }) + |> result.map(fn(res) { HashMap(dict.from_list(res), types.Nil) }) + } + _ -> Ok(ast) + } +} + +fn eval_list(first, rest, env) -> Result(MalType, types.Error) { + case first { + Symbol("def!") -> + case rest { + [key, val] -> def_special(key, val, env) + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } + Symbol("let*") -> + case rest { + [pairs, body] -> + case pairs { + List(l, _) | Vector(l, _) -> { + let_special(l, body, env) + } + _ -> types.wrong_type_err("list | vector", [pairs]) + } + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } + Symbol("if") -> + case rest { + [predicate, branch_true, ..branch_false] -> { + use predicate <- result.try(eval(predicate, env)) + + use false_ast <- result.try(case branch_false { + [] -> Ok(types.Nil) + [f] -> Ok(f) + _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) + }) + + case predicate { + Bool(False) | types.Nil -> { + eval(false_ast, env) + } + _ -> { + eval(branch_true, env) + } + } + } + _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) + } + Symbol("do") -> { + let do = list.take(rest, list.length(rest) - 1) + use _ <- result.try(list.try_map(do, fn(x) { eval(x, env) })) + + case list.last(rest) { + Error(_) -> Error(types.EvalWrongArgLenGreaterThan(1, 0)) + Ok(last) -> eval(last, env) + } + } + Symbol("fn*") -> + case rest { + [List(params, _), body] | [Vector(params, _), body] -> { + use param_names <- result.try( + list.try_map(params, fn(x) { + case x { + Symbol(sym) -> Ok(sym) + _ -> Error(types.EvalFuncParamNotSymbol) + } + }), + ) + + let func = fn(args) { + let fn_env = env.into_outer(env) + use _ <- result.try(env.bind(fn_env, param_names, args)) + + eval(body, fn_env) + } + + Ok(Func(func, types.Nil)) + } + [_, _] as l -> types.wrong_type_err("list, any", l) + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } + _ -> { + use f <- result.try(eval(first, env)) + use args <- result.try(list.try_map(rest, fn(x) { eval(x, env) })) + + apply(f, args) + } + } +} + +fn def_special(key, val, env) { + use k <- result.try(env.try_key(key)) + use v <- result.try(eval(val, env)) + env.set(env, k, v) + Ok(v) +} + +fn get_pairs(list, acc) { + case list { + [a, b, ..rest] -> { + get_pairs(rest, [#(a, b), ..acc]) + } + [] -> Ok(list.reverse(acc)) + _ -> Error(types.StrErr("can't get bind pairs: count not divisible by 2")) + } +} + +fn let_special(pairs, body, env) { + use pairs <- result.try(get_pairs(pairs, [])) + let let_env = env.into_outer(env) + + use _ <- result.try( + list.try_map(pairs, fn(x) { + use key <- result.try(env.try_key(x.0)) + use val <- result.try(eval(x.1, let_env)) + env.set(let_env, key, val) + + Ok(Nil) + }), + ) + + eval(body, let_env) +} + +fn read(str) { + reader.read_str(str) +} + +fn print(ast) { + printer.pr_str(ast, True) +} + +fn rep(str, env) { + use r <- result.try(read(str)) + use e <- result.try(eval(r, env)) + + Ok(print(e)) +} diff --git a/impls/gleam/src/types.gleam b/impls/gleam/src/types.gleam index c481a6566d..d46d28ea25 100644 --- a/impls/gleam/src/types.gleam +++ b/impls/gleam/src/types.gleam @@ -1,6 +1,7 @@ import gleam/dict import gleam/list import gleam/string +import mut_cell.{type MutCell} pub type MalRet = Result(MalType, Error) @@ -15,6 +16,7 @@ pub type MalType { List(List(MalType), meta: MalType) Vector(List(MalType), meta: MalType) HashMap(dict.Dict(MalType, MalType), meta: MalType) + Atom(MutCell(MalType)) Builtin(fn(List(MalType)) -> MalRet) Func(fn(List(MalType)) -> MalRet, meta: MalType) @@ -59,6 +61,7 @@ pub fn wrong_type_err(expected: String, got: List(MalType)) { HashMap(_, _) -> "hashmap" Builtin(_) -> "builtin" Func(_, _) -> "func" + Atom(_) -> "atom" } }) |> string.join(", ") From 900309511bb5aa67ba645064e7f23b1b47e367b3 Mon Sep 17 00:00:00 2001 From: Donda Date: Sat, 30 May 2026 13:59:09 +0200 Subject: [PATCH 09/20] gleam: step7 --- impls/gleam/src/core.gleam | 33 ++++ impls/gleam/src/step7_quote.gleam | 294 ++++++++++++++++++++++++++++++ 2 files changed, 327 insertions(+) create mode 100644 impls/gleam/src/step7_quote.gleam diff --git a/impls/gleam/src/core.gleam b/impls/gleam/src/core.gleam index 77242e0a88..291dfbed40 100644 --- a/impls/gleam/src/core.gleam +++ b/impls/gleam/src/core.gleam @@ -106,6 +106,30 @@ pub fn ns() { }), ), + // Seq + #( + "cons", + Builtin(fn(args) { + case args { + [head, List(tail, _)] | [head, Vector(tail, _)] -> + Ok(List([head, ..tail], types.Nil)) + _ -> types.wrong_type_err("any, list | vector", args) + } + }), + ), + #( + "concat", + Builtin(fn(args) { + list.try_fold(args, [], fn(acc, x) { + case x { + List(l, _) | Vector(l, _) -> Ok(list.append(acc, l)) + _ -> types.wrong_type_err("...(list | vector)", args) + } + }) + |> result.map(fn(res) { List(res, types.Nil) }) + }), + ), + // IO #( "prn", @@ -145,6 +169,15 @@ pub fn ns() { // Declare #("list", Builtin(fn(args) { Ok(List(args, types.Nil)) })), + #( + "vec", + Builtin(fn(args) { + case args { + [List(l, _)] | [Vector(l, _)] -> Ok(Vector(l, types.Nil)) + _ -> types.wrong_type_err("list | vector", args) + } + }), + ), #( "atom", Builtin(fn(args) { diff --git a/impls/gleam/src/step7_quote.gleam b/impls/gleam/src/step7_quote.gleam new file mode 100644 index 0000000000..5b5e65be95 --- /dev/null +++ b/impls/gleam/src/step7_quote.gleam @@ -0,0 +1,294 @@ +import argv +import core +import env +import gleam/dict +import gleam/io +import gleam/list +import gleam/option.{None, Some} +import gleam/result +import printer +import reader +import readline.{readline} +import types.{ + type MalRet, type MalType, Bool, Builtin, Func, HashMap, List, Symbol, Vector, +} + +pub fn main() -> Nil { + let env = env.from_list(core.ns(), None) + + let assert Ok(_) = + [ + "(def! not (fn* (a) (if a false true)))", + "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", + ] + |> list.try_map(fn(x) { rep(x, env) }) + + env.set( + env, + "eval", + Builtin(fn(args) { + case args { + [ast] -> eval(ast, env) + _ -> types.wrong_type_err("any", args) + } + }), + ) + + case argv.load().arguments { + [file, ..] -> { + let _ = case rep("(load-file \"" <> file <> "\")", env) { + Error(err) -> printer.pr_err(err) |> io.println_error() + _ -> Nil + } + } + _ -> { + let _ = loop(env) + Nil + } + } +} + +fn loop(env) { + use input <- result.try(readline("user> ")) + + input + |> rep(env) + |> print_res() + + loop(env) +} + +fn print_res(res) { + case res { + Ok(str) -> { + io.println(str) + } + Error(err) -> { + printer.pr_err(err) |> io.println_error() + } + } +} + +fn apply(x: MalType, args: List(MalType)) -> MalRet { + case x { + Builtin(f) -> f(args) + Func(f, _) -> f(args) + _ -> Error(types.EvalApplyType(x)) + } +} + +fn eval(ast: MalType, env: env.Env) -> MalRet { + case env.get(env, "DEBUG-EVAL") { + None | Some(Bool(False)) | Some(types.Nil) -> Nil + _ -> { + io.println("EVAL: " <> printer.pr_str(ast, True)) + } + } + + case ast { + Symbol(sym) -> + env.get(env, sym) + |> option.to_result(types.EvalSymbolNotFound(sym)) + List([first, ..rest], _) -> eval_list(first, rest, env) + Vector(vec, _) -> + list.try_map(vec, fn(x) { eval(x, env) }) + |> result.map(fn(x) { Vector(x, types.Nil) }) + HashMap(hm, _) -> { + dict.to_list(hm) + |> list.try_map(fn(x) { + let key = x.0 + let val = x.1 + + eval(val, env) + |> result.map(fn(e) { #(key, e) }) + }) + |> result.map(fn(res) { HashMap(dict.from_list(res), types.Nil) }) + } + _ -> Ok(ast) + } +} + +fn eval_list(first, rest, env) -> Result(MalType, types.Error) { + case first { + Symbol("def!") -> def_special(rest, env) + Symbol("let*") -> let_special(rest, env) + Symbol("if") -> if_special(rest, env) + Symbol("do") -> do_special(rest, env) + Symbol("fn*") -> fn_special(rest, env) + Symbol("quote") -> + case rest { + [a] -> Ok(a) + _ -> types.wrong_type_err("any", rest) + } + Symbol("quasiquote") -> + case rest { + [a] -> quasiquote(a) |> eval(env) + _ -> types.wrong_type_err("any", rest) + } + _ -> { + use f <- result.try(eval(first, env)) + use args <- result.try(list.try_map(rest, fn(x) { eval(x, env) })) + + apply(f, args) + } + } +} + +fn def_special(input, env) { + case input { + [key, val] -> { + use k <- result.try(env.try_key(key)) + use v <- result.try(eval(val, env)) + env.set(env, k, v) + Ok(v) + } + _ -> Error(types.EvalWrongArgLen(2, list.length(input))) + } +} + +fn let_special(rest, env) { + case rest { + [pairs, body] -> + case pairs { + List(l, _) | Vector(l, _) -> { + use pairs <- result.try(get_pairs(l, [])) + let let_env = env.into_outer(env) + + use _ <- result.try( + list.try_map(pairs, fn(x) { + use key <- result.try(env.try_key(x.0)) + use val <- result.try(eval(x.1, let_env)) + env.set(let_env, key, val) + + Ok(Nil) + }), + ) + + eval(body, let_env) + } + _ -> types.wrong_type_err("list | vector", [pairs]) + } + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } +} + +fn do_special( + rest: List(MalType), + env: env.Env, +) -> Result(MalType, types.Error) { + let do = list.take(rest, list.length(rest) - 1) + use _ <- result.try(list.try_map(do, fn(x) { eval(x, env) })) + + case list.last(rest) { + Error(_) -> Error(types.EvalWrongArgLenGreaterThan(1, 0)) + Ok(last) -> eval(last, env) + } +} + +fn if_special( + rest: List(MalType), + env: env.Env, +) -> Result(MalType, types.Error) { + case rest { + [predicate, branch_true, ..branch_false] -> { + use predicate <- result.try(eval(predicate, env)) + + use false_ast <- result.try(case branch_false { + [] -> Ok(types.Nil) + [f] -> Ok(f) + _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) + }) + + case predicate { + Bool(False) | types.Nil -> { + eval(false_ast, env) + } + _ -> { + eval(branch_true, env) + } + } + } + _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) + } +} + +fn fn_special( + rest: List(MalType), + env: env.Env, +) -> Result(MalType, types.Error) { + case rest { + [List(params, _), body] | [Vector(params, _), body] -> { + use param_names <- result.try( + list.try_map(params, fn(x) { + case x { + Symbol(sym) -> Ok(sym) + _ -> Error(types.EvalFuncParamNotSymbol) + } + }), + ) + + let func = fn(args) { + let fn_env = env.into_outer(env) + use _ <- result.try(env.bind(fn_env, param_names, args)) + + eval(body, fn_env) + } + + Ok(Func(func, types.Nil)) + } + [_, _] as l -> types.wrong_type_err("list, any", l) + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } +} + +fn make_call(sym, rest) { + List([Symbol(sym), ..rest], types.Nil) +} + +fn quasiquote(ast: MalType) -> MalType { + case ast { + List([Symbol("unquote"), x], _) -> x + List(l, _) -> qq_fold(l) + Vector(v, _) -> make_call("vec", [qq_fold(v)]) + HashMap(..) -> make_call("quote", [ast]) + Symbol(_) -> make_call("quote", [ast]) + _ -> ast + } +} + +fn qq_fold(l) { + list.fold_right(l, List([], types.Nil), qq_loop) +} + +fn qq_loop(acc, elt) { + case elt { + List([Symbol("splice-unquote"), splice], _) -> + make_call("concat", [splice, acc]) + _ -> make_call("cons", [quasiquote(elt), acc]) + } +} + +fn get_pairs(list, acc) { + case list { + [a, b, ..rest] -> { + get_pairs(rest, [#(a, b), ..acc]) + } + [] -> Ok(list.reverse(acc)) + _ -> Error(types.StrErr("can't get bind pairs: count not divisible by 2")) + } +} + +fn read(str) { + reader.read_str(str) +} + +fn print(ast) { + printer.pr_str(ast, True) +} + +fn rep(str, env) { + use r <- result.try(read(str)) + use e <- result.try(eval(r, env)) + + Ok(print(e)) +} From 1902d0831cb08a66fa199b5ab265f138ed2fd70d Mon Sep 17 00:00:00 2001 From: Donda Date: Sat, 30 May 2026 17:55:17 +0200 Subject: [PATCH 10/20] gleam: step8 --- impls/gleam/src/core.gleam | 108 ++++++--- impls/gleam/src/printer.gleam | 1 - impls/gleam/src/step2_eval.gleam | 6 +- impls/gleam/src/step3_env.gleam | 6 +- impls/gleam/src/step4_if_fn_do.gleam | 9 +- impls/gleam/src/step5_tco.gleam | 9 +- impls/gleam/src/step6_file.gleam | 11 +- impls/gleam/src/step7_quote.gleam | 17 +- impls/gleam/src/step8_macros.gleam | 320 +++++++++++++++++++++++++++ impls/gleam/src/types.gleam | 16 +- 10 files changed, 438 insertions(+), 65 deletions(-) create mode 100644 impls/gleam/src/step8_macros.gleam diff --git a/impls/gleam/src/core.gleam b/impls/gleam/src/core.gleam index 291dfbed40..0ce56c18ff 100644 --- a/impls/gleam/src/core.gleam +++ b/impls/gleam/src/core.gleam @@ -1,6 +1,7 @@ import argv import gleam/io import gleam/list +import gleam/option.{type Option, None, Some} import gleam/result import gleam/string import mut_cell @@ -8,12 +9,28 @@ import printer import reader import simplifile import types.{ - type Error, type MalType, Atom, Bool, Builtin, Func, Int, List, String, Vector, + type Error, type MalType, Atom, Bool, Func, Int, List, StrErr, String, Vector, + func, +} + +pub fn nth(list: List(a), n: Int) -> Option(a) { + case n < 0 { + True -> None + False -> + case list { + [] -> None + [x, ..rest] -> + case n == 0 { + True -> Some(x) + False -> nth(rest, n - 1) + } + } + } } pub fn ns() { let int_op = fn(with: fn(Int, Int) -> Result(MalType, Error)) { - Builtin(fn(l: List(MalType)) { + func(fn(l: List(MalType)) { case l { [a, b] -> { case a, b { @@ -48,7 +65,7 @@ pub fn ns() { #("<=", int_op(fn(a, b) { Ok(Bool(a <= b)) })), #( "=", - Builtin(fn(args) { + func(fn(args) { case args { [a, b, ..] -> Ok(Bool(types.eq(a, b))) _ -> Ok(Bool(False)) @@ -57,7 +74,7 @@ pub fn ns() { ), #( "list?", - Builtin(fn(args) { + func(fn(args) { case args { [List(..), ..] -> Ok(Bool(True)) _ -> Ok(Bool(False)) @@ -66,7 +83,7 @@ pub fn ns() { ), #( "empty?", - Builtin(fn(args) { + func(fn(args) { case args { [List([], _)] | [Vector([], _)] -> Ok(Bool(True)) [List(_, _)] | [Vector(_, _)] -> Ok(Bool(False)) @@ -76,18 +93,28 @@ pub fn ns() { ), #( "atom?", - Builtin(fn(args) { + func(fn(args) { case args { [Atom(_)] -> Ok(Bool(True)) _ -> Ok(Bool(False)) } }), ), + #( + "macro?", + func(fn(args) { + case args { + [Func(is_macro: True, ..)] -> Ok(Bool(True)) + [_] -> Ok(Bool(False)) + _ -> types.wrong_type_err("any", args) + } + }), + ), // String #( "pr-str", - Builtin(fn(args) { + func(fn(args) { Ok( list.map(args, fn(x) { printer.pr_str(x, True) }) |> string.join(" ") @@ -97,7 +124,7 @@ pub fn ns() { ), #( "str", - Builtin(fn(args) { + func(fn(args) { Ok( list.map(args, fn(x) { printer.pr_str(x, False) }) |> string.join("") @@ -109,7 +136,7 @@ pub fn ns() { // Seq #( "cons", - Builtin(fn(args) { + func(fn(args) { case args { [head, List(tail, _)] | [head, Vector(tail, _)] -> Ok(List([head, ..tail], types.Nil)) @@ -119,7 +146,7 @@ pub fn ns() { ), #( "concat", - Builtin(fn(args) { + func(fn(args) { list.try_fold(args, [], fn(acc, x) { case x { List(l, _) | Vector(l, _) -> Ok(list.append(acc, l)) @@ -129,11 +156,43 @@ pub fn ns() { |> result.map(fn(res) { List(res, types.Nil) }) }), ), + #( + "first", + func(fn(args) { + case args { + [List([first, ..], _)] | [Vector([first, ..], _)] -> Ok(first) + [types.Nil] | [List(_, _)] | [Vector(_, _)] -> Ok(types.Nil) + _ -> types.wrong_type_err("list | vector", args) + } + }), + ), + #( + "rest", + func(fn(args) { + case args { + [List([_, ..rest], _)] | [Vector([_, ..rest], _)] -> + Ok(List(rest, types.Nil)) + [types.Nil] | [List(_, _)] | [Vector(_, _)] -> Ok(List([], types.Nil)) + _ -> types.wrong_type_err("list | vector", args) + } + }), + ), + #( + "nth", + func(fn(args) { + case args { + [List(data, _), Int(n)] | [Vector(data, _), Int(n)] -> + nth(data, n) + |> option.to_result(StrErr("index out of bounds")) + _ -> types.wrong_type_err("list | vector, int", args) + } + }), + ), // IO #( "prn", - Builtin(fn(args) { + func(fn(args) { list.map(args, fn(x) { printer.pr_str(x, True) }) |> string.join(" ") |> io.println() @@ -142,7 +201,7 @@ pub fn ns() { ), #( "println", - Builtin(fn(args) { + func(fn(args) { list.map(args, fn(x) { printer.pr_str(x, False) }) |> string.join(" ") |> io.println() @@ -151,15 +210,13 @@ pub fn ns() { ), #( "slurp", - Builtin(fn(args) { + func(fn(args) { case args { [String(path)] -> { case simplifile.read(path) { Ok(str) -> Ok(String(str)) Error(err) -> - Error(types.StrErr( - "failed to open file: " <> string.inspect(err), - )) + Error(StrErr("failed to open file: " <> string.inspect(err))) } } _ -> types.wrong_type_err("string", args) @@ -168,10 +225,10 @@ pub fn ns() { ), // Declare - #("list", Builtin(fn(args) { Ok(List(args, types.Nil)) })), + #("list", func(fn(args) { Ok(List(args, types.Nil)) })), #( "vec", - Builtin(fn(args) { + func(fn(args) { case args { [List(l, _)] | [Vector(l, _)] -> Ok(Vector(l, types.Nil)) _ -> types.wrong_type_err("list | vector", args) @@ -180,7 +237,7 @@ pub fn ns() { ), #( "atom", - Builtin(fn(args) { + func(fn(args) { case args { [data] -> Ok(Atom(mut_cell.new(data))) _ -> types.wrong_type_err("any", args) @@ -191,7 +248,7 @@ pub fn ns() { // Atom #( "deref", - Builtin(fn(args) { + func(fn(args) { case args { [Atom(ref)] -> Ok(mut_cell.get(ref)) _ -> types.wrong_type_err("atom", args) @@ -200,7 +257,7 @@ pub fn ns() { ), #( "reset!", - Builtin(fn(args) { + func(fn(args) { case args { [Atom(ref), data] -> { mut_cell.set(ref, data) @@ -212,11 +269,10 @@ pub fn ns() { ), #( "swap!", - Builtin(fn(args) { + func(fn(args) { case args { - [Atom(ref), Func(f, _), ..rest] | [Atom(ref), Builtin(f), ..rest] -> { + [Atom(ref), Func(f, ..), ..rest] -> mut_cell.try_update(ref, fn(data) { f([data, ..rest]) }) - } _ -> types.wrong_type_err("atom, func", args) } }), @@ -225,7 +281,7 @@ pub fn ns() { // Other #( "count", - Builtin(fn(args) { + func(fn(args) { case args { [List(l, _)] | [Vector(l, _)] -> Ok(Int(list.length(l))) _ -> Ok(Int(0)) @@ -234,7 +290,7 @@ pub fn ns() { ), #( "read-string", - Builtin(fn(args) { + func(fn(args) { case args { [String(str)] -> reader.read_str(str) _ -> types.wrong_type_err("string", args) diff --git a/impls/gleam/src/printer.gleam b/impls/gleam/src/printer.gleam index 2d01235fe5..4f6b8f88b4 100644 --- a/impls/gleam/src/printer.gleam +++ b/impls/gleam/src/printer.gleam @@ -96,7 +96,6 @@ pub fn pr_str(ast: types.MalType, print_readability: Bool) -> String { "{" <> d <> "}" } - types.Builtin(..) -> "#" types.Func(..) -> "#" types.Atom(ref) -> "(atom " <> pr_str(mut_cell.get(ref), print_readability) <> ")" diff --git a/impls/gleam/src/step2_eval.gleam b/impls/gleam/src/step2_eval.gleam index e6e11506d5..d53fda6234 100644 --- a/impls/gleam/src/step2_eval.gleam +++ b/impls/gleam/src/step2_eval.gleam @@ -6,12 +6,12 @@ import printer import reader import readline.{readline} import types.{ - type MalRet, type MalType, Builtin, HashMap, Int, List, Symbol, Vector, + type MalRet, type MalType, Func, HashMap, Int, List, Symbol, Vector, } pub fn main() -> Nil { let int_op = fn(with: fn(Int, Int) -> Result(Int, types.Error)) { - Builtin(fn(l: List(MalType)) { + types.func(fn(l: List(MalType)) { case l { [a, b] -> { case a, b { @@ -61,7 +61,7 @@ fn loop(env) { fn apply(x: MalType, args: List(MalType)) -> MalRet { case x { - Builtin(f) -> f(args) + Func(f, ..) -> f(args) _ -> Error(types.EvalApplyType(x)) } } diff --git a/impls/gleam/src/step3_env.gleam b/impls/gleam/src/step3_env.gleam index ba9adee736..67d09d06b9 100644 --- a/impls/gleam/src/step3_env.gleam +++ b/impls/gleam/src/step3_env.gleam @@ -8,12 +8,12 @@ import printer import reader import readline.{readline} import types.{ - type MalRet, type MalType, Builtin, HashMap, Int, List, Symbol, Vector, + type MalRet, type MalType, Func, HashMap, Int, List, Symbol, Vector, } pub fn main() -> Nil { let int_op = fn(with: fn(Int, Int) -> Result(Int, types.Error)) { - Builtin(fn(l: List(MalType)) { + types.func(fn(l: List(MalType)) { case l { [a, b] -> { case a, b { @@ -71,7 +71,7 @@ fn loop(env) { fn apply(x: MalType, args: List(MalType)) -> MalRet { case x { - Builtin(f) -> f(args) + Func(f, ..) -> f(args) _ -> Error(types.EvalApplyType(x)) } } diff --git a/impls/gleam/src/step4_if_fn_do.gleam b/impls/gleam/src/step4_if_fn_do.gleam index b5c8ee1876..18dde02b5c 100644 --- a/impls/gleam/src/step4_if_fn_do.gleam +++ b/impls/gleam/src/step4_if_fn_do.gleam @@ -9,7 +9,7 @@ import printer import reader import readline.{readline} import types.{ - type MalRet, type MalType, Bool, Builtin, Func, HashMap, List, Symbol, Vector, + type MalRet, type MalType, Bool, Func, HashMap, List, Symbol, Vector, } pub fn main() -> Nil { @@ -43,8 +43,7 @@ fn loop(env) { fn apply(x: MalType, args: List(MalType)) -> MalRet { case x { - Builtin(f) -> f(args) - Func(f, _) -> f(args) + Func(f, ..) -> f(args) _ -> Error(types.EvalApplyType(x)) } } @@ -141,14 +140,14 @@ fn eval_list(first, rest, env) -> Result(MalType, types.Error) { }), ) - let func = fn(args) { + let closure = fn(args) { let fn_env = env.into_outer(env) use _ <- result.try(env.bind(fn_env, param_names, args)) eval(body, fn_env) } - Ok(Func(func, types.Nil)) + Ok(types.func(closure)) } [_, _] as l -> types.wrong_type_err("list, any", l) _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) diff --git a/impls/gleam/src/step5_tco.gleam b/impls/gleam/src/step5_tco.gleam index b5c8ee1876..18dde02b5c 100644 --- a/impls/gleam/src/step5_tco.gleam +++ b/impls/gleam/src/step5_tco.gleam @@ -9,7 +9,7 @@ import printer import reader import readline.{readline} import types.{ - type MalRet, type MalType, Bool, Builtin, Func, HashMap, List, Symbol, Vector, + type MalRet, type MalType, Bool, Func, HashMap, List, Symbol, Vector, } pub fn main() -> Nil { @@ -43,8 +43,7 @@ fn loop(env) { fn apply(x: MalType, args: List(MalType)) -> MalRet { case x { - Builtin(f) -> f(args) - Func(f, _) -> f(args) + Func(f, ..) -> f(args) _ -> Error(types.EvalApplyType(x)) } } @@ -141,14 +140,14 @@ fn eval_list(first, rest, env) -> Result(MalType, types.Error) { }), ) - let func = fn(args) { + let closure = fn(args) { let fn_env = env.into_outer(env) use _ <- result.try(env.bind(fn_env, param_names, args)) eval(body, fn_env) } - Ok(Func(func, types.Nil)) + Ok(types.func(closure)) } [_, _] as l -> types.wrong_type_err("list, any", l) _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) diff --git a/impls/gleam/src/step6_file.gleam b/impls/gleam/src/step6_file.gleam index 2e14185a02..1b220135d6 100644 --- a/impls/gleam/src/step6_file.gleam +++ b/impls/gleam/src/step6_file.gleam @@ -10,7 +10,7 @@ import printer import reader import readline.{readline} import types.{ - type MalRet, type MalType, Bool, Builtin, Func, HashMap, List, Symbol, Vector, + type MalRet, type MalType, Bool, Func, HashMap, List, Symbol, Vector, } pub fn main() -> Nil { @@ -26,7 +26,7 @@ pub fn main() -> Nil { env.set( env, "eval", - Builtin(fn(args) { + types.func(fn(args) { case args { [ast] -> eval(ast, env) _ -> types.wrong_type_err("any", args) @@ -71,8 +71,7 @@ fn print_res(res) { fn apply(x: MalType, args: List(MalType)) -> MalRet { case x { - Builtin(f) -> f(args) - Func(f, _) -> f(args) + Func(f, ..) -> f(args) _ -> Error(types.EvalApplyType(x)) } } @@ -169,14 +168,14 @@ fn eval_list(first, rest, env) -> Result(MalType, types.Error) { }), ) - let func = fn(args) { + let closure = fn(args) { let fn_env = env.into_outer(env) use _ <- result.try(env.bind(fn_env, param_names, args)) eval(body, fn_env) } - Ok(Func(func, types.Nil)) + Ok(types.func(closure)) } [_, _] as l -> types.wrong_type_err("list, any", l) _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) diff --git a/impls/gleam/src/step7_quote.gleam b/impls/gleam/src/step7_quote.gleam index 5b5e65be95..f2ed3cf7c6 100644 --- a/impls/gleam/src/step7_quote.gleam +++ b/impls/gleam/src/step7_quote.gleam @@ -10,7 +10,7 @@ import printer import reader import readline.{readline} import types.{ - type MalRet, type MalType, Bool, Builtin, Func, HashMap, List, Symbol, Vector, + type MalRet, type MalType, Bool, Func, HashMap, List, Symbol, Vector, } pub fn main() -> Nil { @@ -26,7 +26,7 @@ pub fn main() -> Nil { env.set( env, "eval", - Builtin(fn(args) { + types.func(fn(args) { case args { [ast] -> eval(ast, env) _ -> types.wrong_type_err("any", args) @@ -71,8 +71,7 @@ fn print_res(res) { fn apply(x: MalType, args: List(MalType)) -> MalRet { case x { - Builtin(f) -> f(args) - Func(f, _) -> f(args) + Func(f, ..) -> f(args) _ -> Error(types.EvalApplyType(x)) } } @@ -134,15 +133,15 @@ fn eval_list(first, rest, env) -> Result(MalType, types.Error) { } } -fn def_special(input, env) { - case input { +fn def_special(rest, env) { + case rest { [key, val] -> { use k <- result.try(env.try_key(key)) use v <- result.try(eval(val, env)) env.set(env, k, v) Ok(v) } - _ -> Error(types.EvalWrongArgLen(2, list.length(input))) + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) } } @@ -227,14 +226,14 @@ fn fn_special( }), ) - let func = fn(args) { + let closure = fn(args) { let fn_env = env.into_outer(env) use _ <- result.try(env.bind(fn_env, param_names, args)) eval(body, fn_env) } - Ok(Func(func, types.Nil)) + Ok(types.func(closure)) } [_, _] as l -> types.wrong_type_err("list, any", l) _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) diff --git a/impls/gleam/src/step8_macros.gleam b/impls/gleam/src/step8_macros.gleam new file mode 100644 index 0000000000..3ba4f3aa3c --- /dev/null +++ b/impls/gleam/src/step8_macros.gleam @@ -0,0 +1,320 @@ +import argv +import core +import env +import gleam/dict +import gleam/io +import gleam/list +import gleam/option.{None, Some} +import gleam/result +import printer +import reader +import readline.{readline} +import types.{ + type MalRet, type MalType, Bool, Func, HashMap, List, Symbol, Vector, +} + +pub fn main() -> Nil { + let env = env.from_list(core.ns(), None) + + let assert Ok(_) = + [ + "(def! not (fn* (a) (if a false true)))", + "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", + "(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)))))))", + ] + |> list.try_map(fn(x) { rep(x, env) }) + + env.set( + env, + "eval", + types.func(fn(args) { + case args { + [ast] -> eval(ast, env) + _ -> types.wrong_type_err("any", args) + } + }), + ) + + case argv.load().arguments { + [file, ..] -> { + let _ = case rep("(load-file \"" <> file <> "\")", env) { + Error(err) -> printer.pr_err(err) |> io.println_error() + _ -> Nil + } + } + _ -> { + let _ = loop(env) + Nil + } + } +} + +fn loop(env) { + use input <- result.try(readline("user> ")) + + input + |> rep(env) + |> print_res() + + loop(env) +} + +fn print_res(res) { + case res { + Ok(str) -> { + io.println(str) + } + Error(err) -> { + printer.pr_err(err) |> io.println_error() + } + } +} + +fn apply(x: MalType, args: List(MalType)) -> MalRet { + case x { + Func(f, ..) -> f(args) + _ -> Error(types.EvalApplyType(x)) + } +} + +fn eval(ast: MalType, env: env.Env) -> MalRet { + case env.get(env, "DEBUG-EVAL") { + None | Some(Bool(False)) | Some(types.Nil) -> Nil + _ -> { + io.println("EVAL: " <> printer.pr_str(ast, True)) + } + } + + case ast { + Symbol(sym) -> + env.get(env, sym) + |> option.to_result(types.EvalSymbolNotFound(sym)) + List([first, ..rest], _) -> eval_list(first, rest, env) + Vector(vec, _) -> + list.try_map(vec, fn(x) { eval(x, env) }) + |> result.map(fn(x) { Vector(x, types.Nil) }) + HashMap(hm, _) -> { + dict.to_list(hm) + |> list.try_map(fn(x) { + let key = x.0 + let val = x.1 + + eval(val, env) + |> result.map(fn(e) { #(key, e) }) + }) + |> result.map(fn(res) { HashMap(dict.from_list(res), types.Nil) }) + } + _ -> Ok(ast) + } +} + +fn eval_list(first, rest, env) -> Result(MalType, types.Error) { + case first { + Symbol("def!") -> def_special(rest, env) + Symbol("defmacro!") -> defmacro_special(rest, env) + Symbol("let*") -> let_special(rest, env) + Symbol("fn*") -> fn_special(rest, env) + Symbol("if") -> if_special(rest, env) + Symbol("do") -> do_special(rest, env) + Symbol("quote") -> + case rest { + [a] -> Ok(a) + _ -> types.wrong_type_err("any", rest) + } + Symbol("quasiquote") -> + case rest { + [a] -> quasiquote(a) |> eval(env) + _ -> types.wrong_type_err("any", rest) + } + _ -> { + use f <- result.try(eval(first, env)) + + case f { + Func(f, is_macro: True, ..) -> + f(rest) |> result.try(fn(res) { eval(res, env) }) + _ -> { + use args <- result.try(list.try_map(rest, fn(x) { eval(x, env) })) + + apply(f, args) + } + } + } + } +} + +fn def_special(rest, env) { + case rest { + [key, val] -> { + use k <- result.try(env.try_key(key)) + use v <- result.try(eval(val, env)) + env.set(env, k, v) + Ok(v) + } + _ -> types.wrong_type_err("symbol, any", rest) + } +} + +fn defmacro_special(rest, env) { + case rest { + [key, f] -> { + use k <- result.try(env.try_key(key)) + use f <- result.try(eval(f, env)) + case f { + Func(fun, ..) -> { + let v = Func(fun, is_macro: True, meta: types.Nil) + env.set(env, k, v) + Ok(v) + } + _ -> types.wrong_type_err("symbol, func", rest) + } + } + _ -> types.wrong_type_err("symbol, func", rest) + } +} + +fn let_special(rest, env) { + case rest { + [pairs, body] -> + case pairs { + List(l, _) | Vector(l, _) -> { + use pairs <- result.try(get_pairs(l, [])) + let let_env = env.into_outer(env) + + use _ <- result.try( + list.try_map(pairs, fn(x) { + use key <- result.try(env.try_key(x.0)) + use val <- result.try(eval(x.1, let_env)) + env.set(let_env, key, val) + + Ok(Nil) + }), + ) + + eval(body, let_env) + } + _ -> types.wrong_type_err("list | vector", [pairs]) + } + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } +} + +fn fn_special( + rest: List(MalType), + env: env.Env, +) -> Result(MalType, types.Error) { + case rest { + [List(params, _), body] | [Vector(params, _), body] -> { + use param_names <- result.try( + list.try_map(params, fn(x) { + case x { + Symbol(sym) -> Ok(sym) + _ -> Error(types.EvalFuncParamNotSymbol) + } + }), + ) + + let func = fn(args) { + let fn_env = env.into_outer(env) + use _ <- result.try(env.bind(fn_env, param_names, args)) + + eval(body, fn_env) + } + + Ok(Func(func, False, types.Nil)) + } + [_, _] as l -> types.wrong_type_err("list, any", l) + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } +} + +fn do_special( + rest: List(MalType), + env: env.Env, +) -> Result(MalType, types.Error) { + let do = list.take(rest, list.length(rest) - 1) + use _ <- result.try(list.try_map(do, fn(x) { eval(x, env) })) + + case list.last(rest) { + Error(_) -> Error(types.EvalWrongArgLenGreaterThan(1, 0)) + Ok(last) -> eval(last, env) + } +} + +fn if_special( + rest: List(MalType), + env: env.Env, +) -> Result(MalType, types.Error) { + case rest { + [predicate, branch_true, ..branch_false] -> { + use predicate <- result.try(eval(predicate, env)) + + use false_ast <- result.try(case branch_false { + [] -> Ok(types.Nil) + [f] -> Ok(f) + _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) + }) + + case predicate { + Bool(False) | types.Nil -> { + eval(false_ast, env) + } + _ -> { + eval(branch_true, env) + } + } + } + _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) + } +} + +fn make_call(sym, rest) { + List([Symbol(sym), ..rest], types.Nil) +} + +fn quasiquote(ast: MalType) -> MalType { + case ast { + List([Symbol("unquote"), x], _) -> x + List(l, _) -> qq_fold(l) + Vector(v, _) -> make_call("vec", [qq_fold(v)]) + HashMap(..) -> make_call("quote", [ast]) + Symbol(_) -> make_call("quote", [ast]) + _ -> ast + } +} + +fn qq_fold(l) { + list.fold_right(l, List([], types.Nil), qq_loop) +} + +fn qq_loop(acc, elt) { + case elt { + List([Symbol("splice-unquote"), splice], _) -> + make_call("concat", [splice, acc]) + _ -> make_call("cons", [quasiquote(elt), acc]) + } +} + +fn get_pairs(list, acc) { + case list { + [a, b, ..rest] -> { + get_pairs(rest, [#(a, b), ..acc]) + } + [] -> Ok(list.reverse(acc)) + _ -> Error(types.StrErr("can't get bind pairs: count not divisible by 2")) + } +} + +fn read(str) { + reader.read_str(str) +} + +fn print(ast) { + printer.pr_str(ast, True) +} + +fn rep(str, env) { + use r <- result.try(read(str)) + use e <- result.try(eval(r, env)) + + Ok(print(e)) +} diff --git a/impls/gleam/src/types.gleam b/impls/gleam/src/types.gleam index d46d28ea25..ced1bfc20a 100644 --- a/impls/gleam/src/types.gleam +++ b/impls/gleam/src/types.gleam @@ -18,8 +18,11 @@ pub type MalType { HashMap(dict.Dict(MalType, MalType), meta: MalType) Atom(MutCell(MalType)) - Builtin(fn(List(MalType)) -> MalRet) - Func(fn(List(MalType)) -> MalRet, meta: MalType) + Func(fn(List(MalType)) -> MalRet, is_macro: Bool, meta: MalType) +} + +pub fn func(f: fn(List(MalType)) -> MalRet) { + Func(f, False, Nil) } pub fn eq(a: MalType, b: MalType) -> Bool { @@ -56,11 +59,10 @@ pub fn wrong_type_err(expected: String, got: List(MalType)) { String(_) -> "string" Symbol(_) -> "symbol" Keyword(_) -> "keyword" - List(_, _) -> "list" - Vector(_, _) -> "vector" - HashMap(_, _) -> "hashmap" - Builtin(_) -> "builtin" - Func(_, _) -> "func" + List(..) -> "list" + Vector(..) -> "vector" + HashMap(..) -> "hashmap" + Func(..) -> "func" Atom(_) -> "atom" } }) From 0b3b3af7be0bea69ba59f3fd2eeeb8107e8ce8a6 Mon Sep 17 00:00:00 2001 From: Donda Date: Sun, 31 May 2026 00:55:09 +0200 Subject: [PATCH 11/20] gleam: step9 --- impls/gleam/src/core.gleam | 588 +++++++++++++++++++------------- impls/gleam/src/printer.gleam | 5 +- impls/gleam/src/reader.gleam | 29 +- impls/gleam/src/step9_try.gleam | 343 +++++++++++++++++++ impls/gleam/src/types.gleam | 46 ++- 5 files changed, 737 insertions(+), 274 deletions(-) create mode 100644 impls/gleam/src/step9_try.gleam diff --git a/impls/gleam/src/core.gleam b/impls/gleam/src/core.gleam index 0ce56c18ff..f1a728d968 100644 --- a/impls/gleam/src/core.gleam +++ b/impls/gleam/src/core.gleam @@ -1,4 +1,5 @@ import argv +import gleam/dict import gleam/io import gleam/list import gleam/option.{type Option, None, Some} @@ -9,11 +10,37 @@ import printer import reader import simplifile import types.{ - type Error, type MalType, Atom, Bool, Func, Int, List, StrErr, String, Vector, - func, + type Error, type MalType, Atom, Bool, Func, HashMap, Int, Keyword, List, + StrErr, String, Symbol, Vector, func, } -pub fn nth(list: List(a), n: Int) -> Option(a) { +fn bool(b) { + Ok(Bool(b)) +} + +fn f(name: String, fun) { + #(name, func(fun)) +} + +// fn list_op() +fn int_op(name, with: fn(Int, Int) -> Result(MalType, Error)) { + #( + name, + func(fn(l: List(MalType)) { + case l { + [a, b] -> { + case a, b { + Int(x), Int(y) -> with(x, y) |> result.map(fn(z) { z }) + _, _ -> types.wrong_type_err("int, int", [a, b]) + } + } + _ -> Error(types.EvalWrongArgLen(2, list.length(l))) + } + }), + ) +} + +fn nth(list: List(a), n: Int) -> Option(a) { case n < 0 { True -> None False -> @@ -29,274 +56,343 @@ pub fn nth(list: List(a), n: Int) -> Option(a) { } pub fn ns() { - let int_op = fn(with: fn(Int, Int) -> Result(MalType, Error)) { - func(fn(l: List(MalType)) { - case l { - [a, b] -> { - case a, b { - Int(x), Int(y) -> with(x, y) |> result.map(fn(z) { z }) - _, _ -> types.wrong_type_err("int, int", [a, b]) - } - } - _ -> Error(types.EvalWrongArgLen(2, list.length(l))) - } - }) - } - [ // Int - #("+", int_op(fn(a, b) { Ok(Int(a + b)) })), - #("-", int_op(fn(a, b) { Ok(Int(a - b)) })), - #("*", int_op(fn(a, b) { Ok(Int(a * b)) })), - #( - "/", - int_op(fn(a, b) { - case b == 0 { - False -> Ok(Int(a / b)) - True -> Error(types.EvalDivideByZero) - } - }), - ), + int_op("+", fn(a, b) { Ok(Int(a + b)) }), + int_op("-", fn(a, b) { Ok(Int(a - b)) }), + int_op("*", fn(a, b) { Ok(Int(a * b)) }), + int_op("/", fn(a, b) { + case b == 0 { + False -> Ok(Int(a / b)) + True -> Error(types.EvalDivideByZero) + } + }), // Bool - #(">", int_op(fn(a, b) { Ok(Bool(a > b)) })), - #(">=", int_op(fn(a, b) { Ok(Bool(a >= b)) })), - #("<", int_op(fn(a, b) { Ok(Bool(a < b)) })), - #("<=", int_op(fn(a, b) { Ok(Bool(a <= b)) })), - #( - "=", - func(fn(args) { - case args { - [a, b, ..] -> Ok(Bool(types.eq(a, b))) - _ -> Ok(Bool(False)) - } - }), - ), - #( - "list?", - func(fn(args) { - case args { - [List(..), ..] -> Ok(Bool(True)) - _ -> Ok(Bool(False)) - } - }), - ), - #( - "empty?", - func(fn(args) { - case args { - [List([], _)] | [Vector([], _)] -> Ok(Bool(True)) - [List(_, _)] | [Vector(_, _)] -> Ok(Bool(False)) - _ -> types.wrong_type_err("list | vector", args) - } - }), - ), - #( - "atom?", - func(fn(args) { - case args { - [Atom(_)] -> Ok(Bool(True)) - _ -> Ok(Bool(False)) - } - }), - ), - #( - "macro?", - func(fn(args) { - case args { - [Func(is_macro: True, ..)] -> Ok(Bool(True)) - [_] -> Ok(Bool(False)) - _ -> types.wrong_type_err("any", args) - } - }), - ), + int_op(">", fn(a, b) { bool(a > b) }), + int_op(">=", fn(a, b) { bool(a >= b) }), + int_op("<", fn(a, b) { bool(a < b) }), + int_op("<=", fn(a, b) { bool(a <= b) }), + f("=", fn(args) { + case args { + [a, b, ..] -> bool(types.eq(a, b)) + _ -> bool(False) + } + }), + f("list?", fn(args) { + case args { + [List(..), ..] -> bool(True) + _ -> bool(False) + } + }), + f("vector?", fn(args) { + case args { + [Vector(..), ..] -> bool(True) + _ -> bool(False) + } + }), + f("sequential?", fn(args) { + case args { + [List(..), ..] | [Vector(..), ..] -> bool(True) + _ -> bool(False) + } + }), + f("empty?", fn(args) { + case args { + [List([], _)] | [Vector([], _)] -> bool(True) + [List(_, _)] | [Vector(_, _)] -> bool(False) + _ -> types.wrong_type_err("list | vector", args) + } + }), + f("atom?", fn(args) { + case args { + [Atom(_)] -> bool(True) + _ -> bool(False) + } + }), + f("macro?", fn(args) { + case args { + [Func(is_macro: True, ..)] -> bool(True) + [_] -> bool(False) + _ -> types.wrong_type_err("any", args) + } + }), + f("symbol?", fn(args) { + case args { + [Symbol(_)] -> bool(True) + _ -> bool(False) + } + }), + f("nil?", fn(args) { + case args { + [types.Nil] -> bool(True) + _ -> bool(False) + } + }), + f("true?", fn(args) { + case args { + [Bool(True)] -> bool(True) + _ -> bool(False) + } + }), + f("false?", fn(args) { + case args { + [Bool(False)] -> bool(True) + _ -> bool(False) + } + }), + f("keyword?", fn(args) { + case args { + [Keyword(_)] -> bool(True) + _ -> bool(False) + } + }), + f("map?", fn(args) { + case args { + [HashMap(..)] -> bool(True) + _ -> bool(False) + } + }), // String - #( - "pr-str", - func(fn(args) { - Ok( - list.map(args, fn(x) { printer.pr_str(x, True) }) - |> string.join(" ") - |> String, - ) - }), - ), - #( - "str", - func(fn(args) { - Ok( - list.map(args, fn(x) { printer.pr_str(x, False) }) - |> string.join("") - |> String, - ) - }), - ), + f("pr-str", fn(args) { + Ok( + list.map(args, fn(x) { printer.pr_str(x, True) }) + |> string.join(" ") + |> String, + ) + }), + f("str", fn(args) { + Ok( + list.map(args, fn(x) { printer.pr_str(x, False) }) + |> string.join("") + |> String, + ) + }), // Seq - #( - "cons", - func(fn(args) { - case args { - [head, List(tail, _)] | [head, Vector(tail, _)] -> - Ok(List([head, ..tail], types.Nil)) - _ -> types.wrong_type_err("any, list | vector", args) + f("count", fn(args) { + case args { + [List(l, _)] | [Vector(l, _)] -> Ok(Int(list.length(l))) + _ -> Ok(Int(0)) + } + }), + f("cons", fn(args) { + case args { + [head, List(tail, _)] | [head, Vector(tail, _)] -> + Ok(List([head, ..tail], types.Nil)) + _ -> types.wrong_type_err("any, list | vector", args) + } + }), + f("concat", fn(args) { + list.try_fold(args, [], fn(acc, x) { + case x { + List(l, _) | Vector(l, _) -> Ok(list.append(acc, l)) + _ -> types.wrong_type_err("...(list | vector)", args) } - }), - ), - #( - "concat", - func(fn(args) { - list.try_fold(args, [], fn(acc, x) { - case x { - List(l, _) | Vector(l, _) -> Ok(list.append(acc, l)) - _ -> types.wrong_type_err("...(list | vector)", args) + }) + |> result.map(fn(res) { List(res, types.Nil) }) + }), + f("first", fn(args) { + case args { + [List([first, ..], _)] | [Vector([first, ..], _)] -> Ok(first) + [types.Nil] | [List(_, _)] | [Vector(_, _)] -> Ok(types.Nil) + _ -> types.wrong_type_err("list | vector", args) + } + }), + f("rest", fn(args) { + case args { + [List([_, ..rest], _)] | [Vector([_, ..rest], _)] -> + Ok(List(rest, types.Nil)) + [types.Nil] | [List(_, _)] | [Vector(_, _)] -> Ok(List([], types.Nil)) + _ -> types.wrong_type_err("list | vector", args) + } + }), + f("nth", fn(args) { + case args { + [List(data, _), Int(n)] | [Vector(data, _), Int(n)] -> + nth(data, n) + |> option.to_result(StrErr("index out of bounds")) + _ -> types.wrong_type_err("list | vector, int", args) + } + }), + f("map", fn(args) { + case args { + [Func(f, ..), List(data, _)] | [Func(f, ..), Vector(data, _)] -> + list.try_map(data, fn(x) { f([x]) }) + |> result.map(fn(res) { List(res, types.Nil) }) + _ -> types.wrong_type_err("list | vector, func", args) + } + }), + + // HashMap + f("assoc", fn(args) { + case args { + [HashMap(hm, _), ..rest] -> { + types.assoc(rest) + |> result.map(fn(res) { HashMap(dict.merge(hm, res), types.Nil) }) + } + _ -> types.wrong_type_err("hashmap, ...any", args) + } + }), + f("dissoc", fn(args) { + case args { + [HashMap(hm, _), ..keys] -> { + Ok(HashMap(dict.drop(hm, keys), types.Nil)) + } + [types.Nil, String(_)] | [types.Nil, Keyword(_)] -> Ok(types.Nil) + _ -> types.wrong_type_err("hashmap, string | keyword", args) + } + }), + f("get", fn(args) { + case args { + [HashMap(hm, _), String(_) as key] + | [HashMap(hm, _), Keyword(_) as key] -> { + case dict.get(hm, key) { + Ok(ok) -> Ok(ok) + _ -> Ok(types.Nil) } - }) - |> result.map(fn(res) { List(res, types.Nil) }) - }), - ), - #( - "first", - func(fn(args) { - case args { - [List([first, ..], _)] | [Vector([first, ..], _)] -> Ok(first) - [types.Nil] | [List(_, _)] | [Vector(_, _)] -> Ok(types.Nil) - _ -> types.wrong_type_err("list | vector", args) } - }), - ), - #( - "rest", - func(fn(args) { - case args { - [List([_, ..rest], _)] | [Vector([_, ..rest], _)] -> - Ok(List(rest, types.Nil)) - [types.Nil] | [List(_, _)] | [Vector(_, _)] -> Ok(List([], types.Nil)) - _ -> types.wrong_type_err("list | vector", args) + [types.Nil, String(_)] | [types.Nil, Keyword(_)] -> Ok(types.Nil) + _ -> types.wrong_type_err("hashmap, string | keyword", args) + } + }), + f("contains?", fn(args) { + case args { + [HashMap(hm, _), String(_) as key] + | [HashMap(hm, _), Keyword(_) as key] -> { + case dict.has_key(hm, key) { + b -> bool(b) + } } - }), - ), - #( - "nth", - func(fn(args) { - case args { - [List(data, _), Int(n)] | [Vector(data, _), Int(n)] -> - nth(data, n) - |> option.to_result(StrErr("index out of bounds")) - _ -> types.wrong_type_err("list | vector, int", args) + _ -> types.wrong_type_err("hashmap, string | keyword", args) + } + }), + f("keys", fn(args) { + case args { + [HashMap(hm, _)] -> { + Ok(List(dict.keys(hm), types.Nil)) } - }), - ), + _ -> types.wrong_type_err("hashmap", args) + } + }), + f("vals", fn(args) { + case args { + [HashMap(hm, _)] -> { + Ok(List(dict.values(hm), types.Nil)) + } + _ -> types.wrong_type_err("hashmap", args) + } + }), // IO - #( - "prn", - func(fn(args) { - list.map(args, fn(x) { printer.pr_str(x, True) }) - |> string.join(" ") - |> io.println() - Ok(types.Nil) - }), - ), - #( - "println", - func(fn(args) { - list.map(args, fn(x) { printer.pr_str(x, False) }) - |> string.join(" ") - |> io.println() - Ok(types.Nil) - }), - ), - #( - "slurp", - func(fn(args) { - case args { - [String(path)] -> { - case simplifile.read(path) { - Ok(str) -> Ok(String(str)) - Error(err) -> - Error(StrErr("failed to open file: " <> string.inspect(err))) - } + f("prn", fn(args) { + list.map(args, fn(x) { printer.pr_str(x, True) }) + |> string.join(" ") + |> io.println() + Ok(types.Nil) + }), + f("println", fn(args) { + list.map(args, fn(x) { printer.pr_str(x, False) }) + |> string.join(" ") + |> io.println() + Ok(types.Nil) + }), + f("slurp", fn(args) { + case args { + [String(path)] -> { + case simplifile.read(path) { + Ok(str) -> Ok(String(str)) + Error(err) -> + Error(StrErr("failed to open file: " <> string.inspect(err))) } - _ -> types.wrong_type_err("string", args) } - }), - ), + _ -> types.wrong_type_err("string", args) + } + }), // Declare - #("list", func(fn(args) { Ok(List(args, types.Nil)) })), - #( - "vec", - func(fn(args) { - case args { - [List(l, _)] | [Vector(l, _)] -> Ok(Vector(l, types.Nil)) - _ -> types.wrong_type_err("list | vector", args) - } - }), - ), - #( - "atom", - func(fn(args) { - case args { - [data] -> Ok(Atom(mut_cell.new(data))) - _ -> types.wrong_type_err("any", args) - } - }), - ), + f("list", fn(args) { Ok(List(args, types.Nil)) }), + f("vector", fn(args) { Ok(Vector(args, types.Nil)) }), + f("vec", fn(args) { + case args { + [List(l, _)] | [Vector(l, _)] -> Ok(Vector(l, types.Nil)) + _ -> types.wrong_type_err("list | vector", args) + } + }), + f("atom", fn(args) { + case args { + [data] -> Ok(Atom(mut_cell.new(data))) + _ -> types.wrong_type_err("any", args) + } + }), + f("symbol", fn(args) { + case args { + [String(str)] -> Ok(Symbol(str)) + _ -> types.wrong_type_err("string", args) + } + }), + f("keyword", fn(args) { + case args { + [String(str)] | [Keyword(str)] -> Ok(Keyword(str)) + _ -> types.wrong_type_err("string | keyword", args) + } + }), + f("hash-map", fn(args) { types.hashmap(args) }), // Atom - #( - "deref", - func(fn(args) { - case args { - [Atom(ref)] -> Ok(mut_cell.get(ref)) - _ -> types.wrong_type_err("atom", args) - } - }), - ), - #( - "reset!", - func(fn(args) { - case args { - [Atom(ref), data] -> { - mut_cell.set(ref, data) - Ok(data) - } - _ -> types.wrong_type_err("atom", args) - } - }), - ), - #( - "swap!", - func(fn(args) { - case args { - [Atom(ref), Func(f, ..), ..rest] -> - mut_cell.try_update(ref, fn(data) { f([data, ..rest]) }) - _ -> types.wrong_type_err("atom, func", args) + f("deref", fn(args) { + case args { + [Atom(ref)] -> Ok(mut_cell.get(ref)) + _ -> types.wrong_type_err("atom", args) + } + }), + f("reset!", fn(args) { + case args { + [Atom(ref), data] -> { + mut_cell.set(ref, data) + Ok(data) } - }), - ), + _ -> types.wrong_type_err("atom", args) + } + }), + f("swap!", fn(args) { + case args { + [Atom(ref), Func(f, ..), ..rest] -> + mut_cell.try_update(ref, fn(data) { f([data, ..rest]) }) + _ -> types.wrong_type_err("atom, func", args) + } + }), // Other - #( - "count", - func(fn(args) { - case args { - [List(l, _)] | [Vector(l, _)] -> Ok(Int(list.length(l))) - _ -> Ok(Int(0)) - } - }), - ), - #( - "read-string", - func(fn(args) { - case args { - [String(str)] -> reader.read_str(str) - _ -> types.wrong_type_err("string", args) + f("apply", fn(args) { + case args { + [Func(f, ..), ..rest] -> { + let middle = list.take(rest, list.length(rest) - 1) + + case list.last(rest) { + Ok(List(l, _)) | Ok(Vector(l, _)) -> { + let appended = list.append(middle, l) + f(appended) + } + _ -> types.wrong_type_err("func, ...any, list | vector", args) + } } - }), - ), + _ -> types.wrong_type_err("func, ...any, list | vector", args) + } + }), + f("read-string", fn(args) { + case args { + [String(str)] -> reader.read_str(str) + _ -> types.wrong_type_err("string", args) + } + }), + f("throw", fn(args) { + case args { + [data] -> Error(types.Throw(data)) + _ -> types.wrong_type_err("any", args) + } + }), #( "*ARGV*", List( diff --git a/impls/gleam/src/printer.gleam b/impls/gleam/src/printer.gleam index 4f6b8f88b4..21667a5cd7 100644 --- a/impls/gleam/src/printer.gleam +++ b/impls/gleam/src/printer.gleam @@ -8,8 +8,7 @@ import types.{Bool, HashMap, Int, Keyword, List, Nil, String, Symbol, Vector} pub fn pr_err(err: types.Error) -> String { case err { types.ReaderEof(expected) -> "expected '" <> expected <> "', found EOF" - types.ReaderInvalidHashMap -> - "failed to construct hashmap: item count not divisible by 2" + types.ReaderInvalidHashMap -> "failed to construct hashmap" types.ReaderEmpyForm -> "read_form called with empty list" types.EvalWrongArgLen(expected, got) -> @@ -33,6 +32,8 @@ pub fn pr_err(err: types.Error) -> String { types.EvalWrongType(expected, got) -> "expected '" <> expected <> "', got '" <> got <> "'" + types.Throw(exception) -> "thrown exception: " <> pr_str(exception, True) + types.EvalDivideByZero -> "can't divide by zero" types.EvalApplyType(x) -> "can't apply '" <> pr_str(x, True) <> "'" types.EvalSymbolNotFound(sym) -> "'" <> sym <> "' not found" diff --git a/impls/gleam/src/reader.gleam b/impls/gleam/src/reader.gleam index 07fe4cd623..5453210f33 100644 --- a/impls/gleam/src/reader.gleam +++ b/impls/gleam/src/reader.gleam @@ -1,13 +1,11 @@ -import gleam/dict import gleam/int import gleam/list -import gleam/option.{None, Some} import gleam/regexp import gleam/result import gleam/string import types.{ - type Error, type MalType, Bool, HashMap, Int, Keyword, List, ReaderEmpyForm, - ReaderEof, ReaderInvalidHashMap, String, Symbol, Vector, + type Error, type MalType, Bool, Int, Keyword, List, ReaderEmpyForm, ReaderEof, + ReaderInvalidHashMap, String, Symbol, Vector, } pub fn read_str(str: String) { @@ -77,17 +75,6 @@ fn read_form(x: List(String)) { } } -fn list_to_pairs( - l: List(MalType), - acc: List(#(MalType, MalType)), -) -> option.Option(List(#(MalType, MalType))) { - case l { - [a, b, ..rest] -> list_to_pairs(rest, [#(a, b), ..acc]) - [_] -> None - [] -> Some(acc) - } -} - fn read_seq(input: List(String), acc: List(MalType), closing: String) { case input { [] -> Error(ReaderEof(closing)) @@ -96,15 +83,9 @@ fn read_seq(input: List(String), acc: List(MalType), closing: String) { ")" -> Ok(#(List(acc, types.Nil), rest)) "]" -> Ok(#(Vector(acc, types.Nil), rest)) "}" -> { - let tuples = list_to_pairs(acc, []) - - case tuples { - None -> Error(ReaderInvalidHashMap) - Some(t) -> { - let hm = dict.from_list(t) - - Ok(#(HashMap(hm, types.Nil), rest)) - } + case types.hashmap(acc) { + Ok(res) -> Ok(#(res, rest)) + _ -> Error(ReaderInvalidHashMap) } } _ -> panic as "read_seq called with unknown closing symbol" diff --git a/impls/gleam/src/step9_try.gleam b/impls/gleam/src/step9_try.gleam new file mode 100644 index 0000000000..d2ce9bd039 --- /dev/null +++ b/impls/gleam/src/step9_try.gleam @@ -0,0 +1,343 @@ +import argv +import core +import env +import gleam/dict +import gleam/io +import gleam/list +import gleam/option.{None, Some} +import gleam/result +import printer +import reader +import readline.{readline} +import types.{ + type MalRet, type MalType, Bool, Func, HashMap, List, String, Symbol, Vector, +} + +pub fn main() -> Nil { + let env = env.from_list(core.ns(), None) + + let assert Ok(_) = + [ + "(def! not (fn* (a) (if a false true)))", + "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", + "(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)))))))", + ] + |> list.try_map(fn(x) { rep(x, env) }) + + env.set( + env, + "eval", + types.func(fn(args) { + case args { + [ast] -> eval(ast, env) + _ -> types.wrong_type_err("any", args) + } + }), + ) + + case argv.load().arguments { + [file, ..] -> { + let _ = case rep("(load-file \"" <> file <> "\")", env) { + Error(err) -> printer.pr_err(err) |> io.println_error() + _ -> Nil + } + } + _ -> { + let _ = loop(env) + Nil + } + } +} + +fn loop(env) { + use input <- result.try(readline("user> ")) + + input + |> rep(env) + |> print_res() + + loop(env) +} + +fn print_res(res) { + case res { + Ok(str) -> { + io.println(str) + } + Error(err) -> { + printer.pr_err(err) |> io.println_error() + } + } +} + +fn apply(x: MalType, args: List(MalType)) -> MalRet { + case x { + Func(f, ..) -> f(args) + _ -> Error(types.EvalApplyType(x)) + } +} + +fn eval(ast: MalType, env: env.Env) -> MalRet { + case env.get(env, "DEBUG-EVAL") { + None | Some(Bool(False)) | Some(types.Nil) -> Nil + _ -> { + io.println("EVAL: " <> printer.pr_str(ast, True)) + } + } + + case ast { + Symbol(sym) -> + env.get(env, sym) + |> option.to_result(types.EvalSymbolNotFound(sym)) + List([first, ..rest], _) -> eval_list(first, rest, env) + Vector(vec, _) -> + list.try_map(vec, fn(x) { eval(x, env) }) + |> result.map(fn(x) { Vector(x, types.Nil) }) + HashMap(hm, _) -> { + dict.to_list(hm) + |> list.try_map(fn(x) { + let key = x.0 + let val = x.1 + + eval(val, env) + |> result.map(fn(e) { #(key, e) }) + }) + |> result.map(fn(res) { HashMap(dict.from_list(res), types.Nil) }) + } + _ -> Ok(ast) + } +} + +fn eval_list(first, rest, env) -> Result(MalType, types.Error) { + case first { + Symbol("def!") -> def_special(rest, env) + Symbol("defmacro!") -> defmacro_special(rest, env) + Symbol("let*") -> let_special(rest, env) + Symbol("fn*") -> fn_special(rest, env) + Symbol("try*") -> try_special(rest, env) + + Symbol("if") -> if_special(rest, env) + Symbol("do") -> do_special(rest, env) + Symbol("quote") -> + case rest { + [a] -> Ok(a) + _ -> types.wrong_type_err("any", rest) + } + Symbol("quasiquote") -> + case rest { + [a] -> quasiquote(a) |> eval(env) + _ -> types.wrong_type_err("any", rest) + } + _ -> { + use f <- result.try(eval(first, env)) + + case f { + Func(f, is_macro: True, ..) -> + f(rest) |> result.try(fn(res) { eval(res, env) }) + _ -> { + use args <- result.try(list.try_map(rest, fn(x) { eval(x, env) })) + + apply(f, args) + } + } + } + } +} + +fn def_special(rest, env) { + case rest { + [key, val] -> { + use k <- result.try(env.try_key(key)) + use v <- result.try(eval(val, env)) + env.set(env, k, v) + Ok(v) + } + _ -> types.wrong_type_err("symbol, any", rest) + } +} + +fn defmacro_special(rest, env) { + case rest { + [key, f] -> { + use k <- result.try(env.try_key(key)) + use f <- result.try(eval(f, env)) + case f { + Func(fun, ..) -> { + let v = Func(fun, is_macro: True, meta: types.Nil) + env.set(env, k, v) + Ok(v) + } + _ -> types.wrong_type_err("symbol, func", rest) + } + } + _ -> types.wrong_type_err("symbol, func", rest) + } +} + +fn let_special(rest, env) { + case rest { + [pairs, body] -> + case pairs { + List(l, _) | Vector(l, _) -> { + use pairs <- result.try(get_pairs(l, [])) + let let_env = env.into_outer(env) + + use _ <- result.try( + list.try_map(pairs, fn(x) { + use key <- result.try(env.try_key(x.0)) + use val <- result.try(eval(x.1, let_env)) + env.set(let_env, key, val) + + Ok(Nil) + }), + ) + + eval(body, let_env) + } + _ -> types.wrong_type_err("list | vector", [pairs]) + } + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } +} + +fn fn_special( + rest: List(MalType), + env: env.Env, +) -> Result(MalType, types.Error) { + case rest { + [List(params, _), body] | [Vector(params, _), body] -> { + use param_names <- result.try( + list.try_map(params, fn(x) { + case x { + Symbol(sym) -> Ok(sym) + _ -> Error(types.EvalFuncParamNotSymbol) + } + }), + ) + + let func = fn(args) { + let fn_env = env.into_outer(env) + use _ <- result.try(env.bind(fn_env, param_names, args)) + + eval(body, fn_env) + } + + Ok(Func(func, False, types.Nil)) + } + [_, _] as l -> types.wrong_type_err("list, any", l) + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } +} + +fn try_special(rest, env) { + case rest { + [a] -> eval(a, env) + [a, List([Symbol("catch*"), Symbol(sym), catch], _)] -> + case eval(a, env) { + Ok(res) -> Ok(res) + Error(err) -> { + let catch_env = env.into_outer(env) + let v = case err { + types.Throw(ast) -> ast + _ -> String(printer.pr_err(err)) + } + env.set(env, sym, v) + + eval(catch, catch_env) + } + } + _ -> types.wrong_type_err("any, (catch* symbol, any)?", rest) + } +} + +fn do_special( + rest: List(MalType), + env: env.Env, +) -> Result(MalType, types.Error) { + let do = list.take(rest, list.length(rest) - 1) + use _ <- result.try(list.try_map(do, fn(x) { eval(x, env) })) + + case list.last(rest) { + Error(_) -> Error(types.EvalWrongArgLenGreaterThan(1, 0)) + Ok(last) -> eval(last, env) + } +} + +fn if_special( + rest: List(MalType), + env: env.Env, +) -> Result(MalType, types.Error) { + case rest { + [predicate, branch_true, ..branch_false] -> { + use predicate <- result.try(eval(predicate, env)) + + use false_ast <- result.try(case branch_false { + [] -> Ok(types.Nil) + [f] -> Ok(f) + _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) + }) + + case predicate { + Bool(False) | types.Nil -> { + eval(false_ast, env) + } + _ -> { + eval(branch_true, env) + } + } + } + _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) + } +} + +fn make_call(sym, rest) { + List([Symbol(sym), ..rest], types.Nil) +} + +fn quasiquote(ast: MalType) -> MalType { + case ast { + List([Symbol("unquote"), x], _) -> x + List(l, _) -> qq_fold(l) + Vector(v, _) -> make_call("vec", [qq_fold(v)]) + HashMap(..) -> make_call("quote", [ast]) + Symbol(_) -> make_call("quote", [ast]) + _ -> ast + } +} + +fn qq_fold(l) { + list.fold_right(l, List([], types.Nil), qq_loop) +} + +fn qq_loop(acc, elt) { + case elt { + List([Symbol("splice-unquote"), splice], _) -> + make_call("concat", [splice, acc]) + _ -> make_call("cons", [quasiquote(elt), acc]) + } +} + +fn get_pairs(list, acc) { + case list { + [a, b, ..rest] -> { + get_pairs(rest, [#(a, b), ..acc]) + } + [] -> Ok(list.reverse(acc)) + _ -> Error(types.StrErr("can't get bind pairs: count not divisible by 2")) + } +} + +fn read(str) { + reader.read_str(str) +} + +fn print(ast) { + printer.pr_str(ast, True) +} + +fn rep(str, env) { + use r <- result.try(read(str)) + use e <- result.try(eval(r, env)) + + Ok(print(e)) +} diff --git a/impls/gleam/src/types.gleam b/impls/gleam/src/types.gleam index ced1bfc20a..ebd304085e 100644 --- a/impls/gleam/src/types.gleam +++ b/impls/gleam/src/types.gleam @@ -1,5 +1,6 @@ -import gleam/dict +import gleam/dict.{type Dict} import gleam/list +import gleam/result import gleam/string import mut_cell.{type MutCell} @@ -15,7 +16,7 @@ pub type MalType { Keyword(String) List(List(MalType), meta: MalType) Vector(List(MalType), meta: MalType) - HashMap(dict.Dict(MalType, MalType), meta: MalType) + HashMap(Dict(MalType, MalType), meta: MalType) Atom(MutCell(MalType)) Func(fn(List(MalType)) -> MalRet, is_macro: Bool, meta: MalType) @@ -25,6 +26,29 @@ pub fn func(f: fn(List(MalType)) -> MalRet) { Func(f, False, Nil) } +fn list_to_pairs( + l: List(MalType), + acc: List(#(MalType, MalType)), +) -> Result(List(#(MalType, MalType)), Error) { + case l { + [] -> Ok(acc) + [String(_) as a, b, ..rest] | [Keyword(_) as a, b, ..rest] -> + list_to_pairs(rest, [#(a, b), ..acc]) + _ -> Error(StrErr("failed to get hashmap pairs")) + } +} + +pub fn assoc(list) -> Result(Dict(_, _), Error) { + use pairs <- result.try(list_to_pairs(list, [])) + // Reversed for (get { :a 1 :a 2 } :a) -> 2 + Ok(dict.from_list(list.reverse(pairs))) +} + +pub fn hashmap(list) -> MalRet { + assoc(list) + |> result.map(fn(res) { HashMap(res, Nil) }) +} + pub fn eq(a: MalType, b: MalType) -> Bool { case a, b { List(l, _), Vector(v, _) @@ -32,10 +56,26 @@ pub fn eq(a: MalType, b: MalType) -> Bool { | List(l, _), List(v, _) | Vector(l, _), Vector(v, _) -> seq_eq(l, v) + HashMap(a, _), HashMap(b, _) -> hm_eq(a, b) _, _ -> a == b } } +fn hm_eq(a: Dict(MalType, MalType), b: Dict(MalType, MalType)) -> Bool { + case dict.size(a) == dict.size(b) { + True -> { + list.all(dict.to_list(a), fn(pairs) { + let #(key, val) = pairs + case dict.get(b, key) { + Ok(val_b) -> eq(val, val_b) + _ -> False + } + }) + } + False -> False + } +} + fn seq_eq(a: List(MalType), b: List(MalType)) { case a, b { [x, ..rest], [y, ..rest2] -> @@ -87,6 +127,8 @@ pub type Error { EnvToKey(ast: MalType) + Throw(MalType) + /// Used for one off errors StrErr(String) } From 88817e72774a08b351e3adc0b9a7bc68c63ce115 Mon Sep 17 00:00:00 2001 From: Donda Date: Sat, 6 Jun 2026 20:00:26 +0200 Subject: [PATCH 12/20] gleam: stepA --- impls/gleam/.gitignore | 1 + impls/gleam/Makefile | 6 +- impls/gleam/gleam.toml | 4 +- impls/gleam/manifest.toml | 4 +- impls/gleam/package-lock.json | 21 ++ impls/gleam/package.json | 5 + impls/gleam/run | 8 +- impls/gleam/src/core.gleam | 150 ++++++++++++-- impls/gleam/src/mal.gleam | 5 + impls/gleam/src/mut_cell.gleam | 52 +++-- impls/gleam/src/mut_cell_ffi.mjs | 17 ++ impls/gleam/src/reader.gleam | 2 +- impls/gleam/src/readline.gleam | 3 +- impls/gleam/src/readline_ffi.mjs | 10 + impls/gleam/src/stepa_mal.gleam | 344 +++++++++++++++++++++++++++++++ impls/gleam/test/mal_test.gleam | 5 - 16 files changed, 590 insertions(+), 47 deletions(-) create mode 100644 impls/gleam/package-lock.json create mode 100644 impls/gleam/package.json create mode 100644 impls/gleam/src/mal.gleam create mode 100644 impls/gleam/src/mut_cell_ffi.mjs create mode 100644 impls/gleam/src/readline_ffi.mjs create mode 100644 impls/gleam/src/stepa_mal.gleam delete mode 100644 impls/gleam/test/mal_test.gleam diff --git a/impls/gleam/.gitignore b/impls/gleam/.gitignore index 599be4eb92..f18ca5d5f1 100644 --- a/impls/gleam/.gitignore +++ b/impls/gleam/.gitignore @@ -1,4 +1,5 @@ *.beam *.ez /build +/node_modules erl_crash.dump diff --git a/impls/gleam/Makefile b/impls/gleam/Makefile index 93260cfbe8..d2b970e946 100644 --- a/impls/gleam/Makefile +++ b/impls/gleam/Makefile @@ -12,7 +12,11 @@ step9_try \ stepA_mal $(STEPS): - gleam build +ifneq ($(filter $(GLEAM_TARGET), javascript js), ) + npm install ../gleam +endif + gleam build --no-print-progress clean: gleam clean + rm -rf node_modules diff --git a/impls/gleam/gleam.toml b/impls/gleam/gleam.toml index 622f5421fe..66594746ce 100644 --- a/impls/gleam/gleam.toml +++ b/impls/gleam/gleam.toml @@ -9,6 +9,4 @@ gleam_regexp = ">= 1.1.1 and < 2.0.0" gleam_erlang = ">= 1.3.0 and < 2.0.0" gleam_otp = ">= 1.2.0 and < 2.0.0" simplifile = ">= 2.4.0 and < 3.0.0" - -[dev-dependencies] -gleeunit = ">= 1.0.0 and < 2.0.0" +gleam_time = ">= 1.8.0 and < 2.0.0" diff --git a/impls/gleam/manifest.toml b/impls/gleam/manifest.toml index e1e5378705..8ad918caa6 100644 --- a/impls/gleam/manifest.toml +++ b/impls/gleam/manifest.toml @@ -8,7 +8,7 @@ packages = [ { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, { name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" }, - { name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" }, + { name = "gleam_time", version = "1.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "533D8723774D61AD4998324F5DD1DABDCDBFABAFB9E87CB5D03C6955448FC97D" }, { name = "input", version = "1.0.1", build_tools = ["gleam"], requirements = [], otp_app = "input", source = "hex", outer_checksum = "FE84CDADC78A1367E4AFD561A529825A8FEC88D165CBDF511FD3226CABCDEE6F" }, { name = "simplifile", version = "2.4.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "7C18AFA4FED0B4CE1FA5B0B4BAC1FA1744427054EA993565F6F3F82E5453170D" }, ] @@ -19,6 +19,6 @@ gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" } gleam_otp = { version = ">= 1.2.0 and < 2.0.0" } gleam_regexp = { version = ">= 1.1.1 and < 2.0.0" } gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } -gleeunit = { version = ">= 1.0.0 and < 2.0.0" } +gleam_time = { version = ">= 1.8.0 and < 2.0.0" } input = { version = ">= 1.0.1 and < 2.0.0" } simplifile = { version = ">= 2.4.0 and < 3.0.0" } diff --git a/impls/gleam/package-lock.json b/impls/gleam/package-lock.json new file mode 100644 index 0000000000..5121d0aaba --- /dev/null +++ b/impls/gleam/package-lock.json @@ -0,0 +1,21 @@ +{ + "name": "gleam", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "readline-sync": "^1.4.10" + } + }, + "node_modules/readline-sync": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", + "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + } + } +} diff --git a/impls/gleam/package.json b/impls/gleam/package.json new file mode 100644 index 0000000000..65adf18429 --- /dev/null +++ b/impls/gleam/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "readline-sync": "^1.4.10" + } +} diff --git a/impls/gleam/run b/impls/gleam/run index 1cdbc8a315..a8dd6e343e 100755 --- a/impls/gleam/run +++ b/impls/gleam/run @@ -1,3 +1,9 @@ #!/usr/bin/env bash + +# Gleam does not let you have uppercase letters in module(file) names +if [ "$STEP" == "stepA_mal" ]; then + STEP=stepa_mal +fi + cd $(dirname $0) -exec gleam run --module ${STEP:-stepA_mal} --no-print-progress -- "${@}" +exec gleam run --module ${STEP:-stepa_mal} --no-print-progress --target ${GLEAM_TARGET:-erlang} -- "${@}" diff --git a/impls/gleam/src/core.gleam b/impls/gleam/src/core.gleam index f1a728d968..8e0316844b 100644 --- a/impls/gleam/src/core.gleam +++ b/impls/gleam/src/core.gleam @@ -1,13 +1,16 @@ import argv import gleam/dict +import gleam/float import gleam/io import gleam/list import gleam/option.{type Option, None, Some} import gleam/result import gleam/string +import gleam/time/timestamp import mut_cell import printer import reader +import readline import simplifile import types.{ type Error, type MalType, Atom, Bool, Func, HashMap, Int, Keyword, List, @@ -55,6 +58,42 @@ fn nth(list: List(a), n: Int) -> Option(a) { } } +fn do_str(args, print_readability) { + let join = case print_readability { + True -> " " + False -> "" + } + + Ok( + list.map(args, fn(x) { printer.pr_str(x, print_readability) }) + |> string.join(join) + |> String, + ) +} + +fn str(args) { + do_str(args, False) +} + +fn pr_str(args) { + do_str(args, True) +} + +fn time_ms(args) { + case args { + [] -> { + let time = + timestamp.system_time() + |> timestamp.to_unix_seconds() + + let int = float.truncate(time *. 1000.0) + + Ok(Int(int)) + } + _ -> types.wrong_type_err("", args) + } +} + pub fn ns() { [ // Int @@ -79,6 +118,13 @@ pub fn ns() { _ -> bool(False) } }), + f("number?", fn(args) { + case args { + [Int(_)] -> bool(True) + [_] -> bool(False) + _ -> types.wrong_type_err("any", args) + } + }), f("list?", fn(args) { case args { [List(..), ..] -> bool(True) @@ -110,6 +156,13 @@ pub fn ns() { _ -> bool(False) } }), + f("fn?", fn(args) { + case args { + [Func(is_macro: False, ..)] -> bool(True) + [_] -> bool(False) + _ -> types.wrong_type_err("any", args) + } + }), f("macro?", fn(args) { case args { [Func(is_macro: True, ..)] -> bool(True) @@ -117,58 +170,59 @@ pub fn ns() { _ -> types.wrong_type_err("any", args) } }), + f("string?", fn(args) { + case args { + [String(_)] -> bool(True) + [_] -> bool(False) + _ -> types.wrong_type_err("any", args) + } + }), f("symbol?", fn(args) { case args { [Symbol(_)] -> bool(True) - _ -> bool(False) + [_] -> bool(False) + _ -> types.wrong_type_err("any", args) } }), f("nil?", fn(args) { case args { [types.Nil] -> bool(True) - _ -> bool(False) + [_] -> bool(False) + _ -> types.wrong_type_err("any", args) } }), f("true?", fn(args) { case args { [Bool(True)] -> bool(True) - _ -> bool(False) + [_] -> bool(False) + _ -> types.wrong_type_err("any", args) } }), f("false?", fn(args) { case args { [Bool(False)] -> bool(True) - _ -> bool(False) + [_] -> bool(False) + _ -> types.wrong_type_err("any", args) } }), f("keyword?", fn(args) { case args { [Keyword(_)] -> bool(True) - _ -> bool(False) + [_] -> bool(False) + _ -> types.wrong_type_err("any", args) } }), f("map?", fn(args) { case args { [HashMap(..)] -> bool(True) - _ -> bool(False) + [_] -> bool(False) + _ -> types.wrong_type_err("any", args) } }), // String - f("pr-str", fn(args) { - Ok( - list.map(args, fn(x) { printer.pr_str(x, True) }) - |> string.join(" ") - |> String, - ) - }), - f("str", fn(args) { - Ok( - list.map(args, fn(x) { printer.pr_str(x, False) }) - |> string.join("") - |> String, - ) - }), + f("pr-str", pr_str), + f("str", str), // Seq f("count", fn(args) { @@ -193,6 +247,15 @@ pub fn ns() { }) |> result.map(fn(res) { List(res, types.Nil) }) }), + f("conj", fn(args) { + case args { + [List(data, _), ..rest] -> + Ok(List(list.append(list.reverse(rest), data), types.Nil)) + [Vector(data, _), ..rest] -> + Ok(Vector(list.append(data, rest), types.Nil)) + _ -> types.wrong_type_err("any, list | vector", args) + } + }), f("first", fn(args) { case args { [List([first, ..], _)] | [Vector([first, ..], _)] -> Ok(first) @@ -224,6 +287,20 @@ pub fn ns() { _ -> types.wrong_type_err("list | vector, func", args) } }), + f("seq", fn(args) { + case args { + [List([], _)] | [Vector([], _)] | [String("")] | [types.Nil] -> + Ok(types.Nil) + [List(l, _)] | [Vector(l, _)] -> Ok(List(l, types.Nil)) + [String(str)] -> { + let chars = + string.to_graphemes(str) |> list.map(fn(char) { String(char) }) + + Ok(List(chars, types.Nil)) + } + _ -> types.wrong_type_err("list | vector | string | nil", args) + } + }), // HashMap f("assoc", fn(args) { @@ -310,6 +387,17 @@ pub fn ns() { _ -> types.wrong_type_err("string", args) } }), + f("readline", fn(args) { + case args { + [String(str)] -> { + case readline.readline(str) { + Ok(input) -> Ok(String(input)) + Error(_) -> Ok(types.Nil) + } + } + _ -> types.wrong_type_err("string", args) + } + }), // Declare f("list", fn(args) { Ok(List(args, types.Nil)) }), @@ -364,6 +452,26 @@ pub fn ns() { } }), + // Meta + f("meta", fn(args) { + case args { + [List(meta:, ..)] + | [Vector(meta:, ..)] + | [HashMap(meta:, ..)] + | [Func(meta:, ..)] -> Ok(meta) + _ -> types.wrong_type_err("list | vector | hashmap | func", args) + } + }), + f("with-meta", fn(args) { + case args { + [List(..) as l, meta] -> Ok(List(..l, meta:)) + [Vector(..) as l, meta] -> Ok(Vector(..l, meta:)) + [HashMap(..) as l, meta] -> Ok(HashMap(..l, meta:)) + [Func(..) as l, meta] -> Ok(Func(..l, meta: meta)) + _ -> types.wrong_type_err("list | vector | hashmap | func", args) + } + }), + // Other f("apply", fn(args) { case args { @@ -393,6 +501,7 @@ pub fn ns() { _ -> types.wrong_type_err("any", args) } }), + f("time-ms", time_ms), #( "*ARGV*", List( @@ -403,5 +512,6 @@ pub fn ns() { types.Nil, ), ), + #("*host-language*", String("gleam")), ] } diff --git a/impls/gleam/src/mal.gleam b/impls/gleam/src/mal.gleam new file mode 100644 index 0000000000..a50feabb2a --- /dev/null +++ b/impls/gleam/src/mal.gleam @@ -0,0 +1,5 @@ +import stepa_mal + +pub fn main() -> Nil { + stepa_mal.main() +} diff --git a/impls/gleam/src/mut_cell.gleam b/impls/gleam/src/mut_cell.gleam index 48de0d97cf..1faeac132e 100644 --- a/impls/gleam/src/mut_cell.gleam +++ b/impls/gleam/src/mut_cell.gleam @@ -1,8 +1,29 @@ +@target(erlang) import gleam/erlang/process.{type Subject} +@target(erlang) import gleam/otp/actor import gleam/result -pub fn new(value: a) { +@target(javascript) +pub type MutCell(a) + +@target(erlang) +pub type MutCell(a) = + actor.Started(process.Subject(Message(a))) + +@target(erlang) +pub type Message(a) { + Get(Subject(a)) + Set(a) + Update(fn(a) -> a) +} + +@target(javascript) +@external(javascript, "./mut_cell_ffi.mjs", "new_cell") +pub fn new(value: a) -> MutCell(a) + +@target(erlang) +pub fn new(value: a) -> MutCell(a) { let handle_message = fn(state: a, message: Message(a)) -> actor.Next( a, Message(a), @@ -28,25 +49,30 @@ pub fn new(value: a) { actor } -pub type Message(a) { - Get(Subject(a)) - Set(a) - Update(fn(a) -> a) -} +@target(javascript) +@external(javascript, "./mut_cell_ffi.mjs", "get") +pub fn get(cell: MutCell(a)) -> a -pub type MutCell(a) = - actor.Started(process.Subject(Message(a))) - -pub fn get(cell: MutCell(a)) { +@target(erlang) +pub fn get(cell: MutCell(a)) -> a { actor.call(cell.data, waiting: 10, sending: Get) } -pub fn set(cell: MutCell(a), new: a) { +@target(javascript) +@external(javascript, "./mut_cell_ffi.mjs", "set") +pub fn set(cell: MutCell(a), new: a) -> Nil + +@target(erlang) +pub fn set(cell: MutCell(a), new: a) -> Nil { actor.send(cell.data, Set(new)) - Nil } -pub fn update(cell: MutCell(a), f: fn(a) -> a) { +@target(javascript) +@external(javascript, "./mut_cell_ffi.mjs", "update") +pub fn update(cell: MutCell(a), f: fn(a) -> a) -> Nil + +@target(erlang) +pub fn update(cell: MutCell(a), f: fn(a) -> a) -> Nil { actor.send(cell.data, Update(f)) } diff --git a/impls/gleam/src/mut_cell_ffi.mjs b/impls/gleam/src/mut_cell_ffi.mjs new file mode 100644 index 0000000000..258c3d1930 --- /dev/null +++ b/impls/gleam/src/mut_cell_ffi.mjs @@ -0,0 +1,17 @@ +export function new_cell(value) { + return { value }; +} + +export function get(cell) { + return cell.value; +} + +export function set(cell, value) { + cell.value = value; + return undefined; +} + +export function update(cell, func) { + cell.value = func(cell.value); + return undefined; +} diff --git a/impls/gleam/src/reader.gleam b/impls/gleam/src/reader.gleam index 5453210f33..860c60df02 100644 --- a/impls/gleam/src/reader.gleam +++ b/impls/gleam/src/reader.gleam @@ -26,7 +26,7 @@ fn tokenize(str: String) { <> "(" <> "~@" <> "|[\\[\\]{}()'`~^@]" - <> "|\"(?:\\\\.|[^\\\"])*\"?" + <> "|\"(?:\\\\.|[^\"])*\"?" <> "|;.*" <> "|[^\\s\\[\\]{}('\"`,;)]*" <> ")", diff --git a/impls/gleam/src/readline.gleam b/impls/gleam/src/readline.gleam index 5ab4eacf71..1bf871c7b7 100644 --- a/impls/gleam/src/readline.gleam +++ b/impls/gleam/src/readline.gleam @@ -1,5 +1,6 @@ import input.{input} -pub fn readline(prn: String) { +@external(javascript, "./readline_ffi.mjs", "readline_func") +pub fn readline(prn: String) -> Result(String, Nil) { input(prn) } diff --git a/impls/gleam/src/readline_ffi.mjs b/impls/gleam/src/readline_ffi.mjs new file mode 100644 index 0000000000..02bcfa1fbd --- /dev/null +++ b/impls/gleam/src/readline_ffi.mjs @@ -0,0 +1,10 @@ +import { Ok } from "./gleam.mjs"; +import readlineSync from "readline-sync"; + +export function readline_func(prn) { + const input = readlineSync.question(prn, { + history: true, + }); + + return new Ok(input); +} diff --git a/impls/gleam/src/stepa_mal.gleam b/impls/gleam/src/stepa_mal.gleam new file mode 100644 index 0000000000..9993c832df --- /dev/null +++ b/impls/gleam/src/stepa_mal.gleam @@ -0,0 +1,344 @@ +import argv +import core +import env +import gleam/dict +import gleam/io +import gleam/list +import gleam/option.{None, Some} +import gleam/result +import printer +import reader +import readline.{readline} +import types.{ + type MalRet, type MalType, Bool, Func, HashMap, List, String, Symbol, Vector, +} + +pub fn main() -> Nil { + let env = env.from_list(core.ns(), None) + + let assert Ok(_) = + [ + "(def! not (fn* (a) (if a false true)))", + "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", + "(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)))))))", + ] + |> list.try_map(fn(x) { rep(x, env) }) + + env.set( + env, + "eval", + types.func(fn(args) { + case args { + [ast] -> eval(ast, env) + _ -> types.wrong_type_err("any", args) + } + }), + ) + + case argv.load().arguments { + [file, ..] -> { + let _ = case rep("(load-file \"" <> file <> "\")", env) { + Error(err) -> printer.pr_err(err) |> io.println_error() + _ -> Nil + } + } + _ -> { + let _ = rep("(println (str \"Mal [\" *host-language* \"]\"))", env) + let _ = loop(env) + Nil + } + } +} + +fn loop(env) { + use input <- result.try(readline("user> ")) + + input + |> rep(env) + |> print_res() + + loop(env) +} + +fn print_res(res) { + case res { + Ok(str) -> { + io.println(str) + } + Error(err) -> { + printer.pr_err(err) |> io.println_error() + } + } +} + +fn apply(x: MalType, args: List(MalType)) -> MalRet { + case x { + Func(f, ..) -> f(args) + _ -> Error(types.EvalApplyType(x)) + } +} + +fn eval(ast: MalType, env: env.Env) -> MalRet { + case env.get(env, "DEBUG-EVAL") { + None | Some(Bool(False)) | Some(types.Nil) -> Nil + _ -> { + io.println("EVAL: " <> printer.pr_str(ast, True)) + } + } + + case ast { + Symbol(sym) -> + env.get(env, sym) + |> option.to_result(types.EvalSymbolNotFound(sym)) + List([first, ..rest], _) -> eval_list(first, rest, env) + Vector(vec, _) -> + list.try_map(vec, fn(x) { eval(x, env) }) + |> result.map(fn(x) { Vector(x, types.Nil) }) + HashMap(hm, _) -> { + dict.to_list(hm) + |> list.try_map(fn(x) { + let key = x.0 + let val = x.1 + + eval(val, env) + |> result.map(fn(e) { #(key, e) }) + }) + |> result.map(fn(res) { HashMap(dict.from_list(res), types.Nil) }) + } + _ -> Ok(ast) + } +} + +fn eval_list(first, rest, env) -> Result(MalType, types.Error) { + case first { + Symbol("def!") -> def_special(rest, env) + Symbol("defmacro!") -> defmacro_special(rest, env) + Symbol("let*") -> let_special(rest, env) + Symbol("fn*") -> fn_special(rest, env) + Symbol("try*") -> try_special(rest, env) + + Symbol("if") -> if_special(rest, env) + Symbol("do") -> do_special(rest, env) + Symbol("quote") -> + case rest { + [a] -> Ok(a) + _ -> types.wrong_type_err("any", rest) + } + Symbol("quasiquote") -> + case rest { + [a] -> quasiquote(a) |> eval(env) + _ -> types.wrong_type_err("any", rest) + } + _ -> { + use f <- result.try(eval(first, env)) + + case f { + Func(f, is_macro: True, ..) -> + f(rest) |> result.try(fn(res) { eval(res, env) }) + _ -> { + use args <- result.try(list.try_map(rest, fn(x) { eval(x, env) })) + + apply(f, args) + } + } + } + } +} + +fn def_special(rest, env) { + case rest { + [key, val] -> { + use k <- result.try(env.try_key(key)) + use v <- result.try(eval(val, env)) + env.set(env, k, v) + Ok(v) + } + _ -> types.wrong_type_err("symbol, any", rest) + } +} + +fn defmacro_special(rest, env) { + case rest { + [key, f] -> { + use k <- result.try(env.try_key(key)) + use f <- result.try(eval(f, env)) + case f { + Func(fun, ..) -> { + let v = Func(fun, is_macro: True, meta: types.Nil) + env.set(env, k, v) + Ok(v) + } + _ -> types.wrong_type_err("symbol, func", rest) + } + } + _ -> types.wrong_type_err("symbol, func", rest) + } +} + +fn let_special(rest, env) { + case rest { + [pairs, body] -> + case pairs { + List(l, _) | Vector(l, _) -> { + use pairs <- result.try(get_pairs(l, [])) + let let_env = env.into_outer(env) + + use _ <- result.try( + list.try_map(pairs, fn(x) { + use key <- result.try(env.try_key(x.0)) + use val <- result.try(eval(x.1, let_env)) + env.set(let_env, key, val) + + Ok(Nil) + }), + ) + + eval(body, let_env) + } + _ -> types.wrong_type_err("list | vector", [pairs]) + } + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } +} + +fn fn_special( + rest: List(MalType), + env: env.Env, +) -> Result(MalType, types.Error) { + case rest { + [List(params, _), body] | [Vector(params, _), body] -> { + use param_names <- result.try( + list.try_map(params, fn(x) { + case x { + Symbol(sym) -> Ok(sym) + _ -> Error(types.EvalFuncParamNotSymbol) + } + }), + ) + + let func = fn(args) { + let fn_env = env.into_outer(env) + use _ <- result.try(env.bind(fn_env, param_names, args)) + + eval(body, fn_env) + } + + Ok(Func(func, False, types.Nil)) + } + [_, _] as l -> types.wrong_type_err("list, any", l) + _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + } +} + +fn try_special(rest, env) { + case rest { + [a] -> eval(a, env) + [a, List([Symbol("catch*"), Symbol(sym), catch], _)] -> + case eval(a, env) { + Ok(res) -> Ok(res) + Error(err) -> { + let catch_env = env.into_outer(env) + let v = case err { + types.Throw(ast) -> ast + _ -> String(printer.pr_err(err)) + } + env.set(env, sym, v) + + eval(catch, catch_env) + } + } + _ -> types.wrong_type_err("any, (catch* symbol, any)?", rest) + } +} + +fn do_special( + rest: List(MalType), + env: env.Env, +) -> Result(MalType, types.Error) { + let do = list.take(rest, list.length(rest) - 1) + use _ <- result.try(list.try_map(do, fn(x) { eval(x, env) })) + + case list.last(rest) { + Error(_) -> Error(types.EvalWrongArgLenGreaterThan(1, 0)) + Ok(last) -> eval(last, env) + } +} + +fn if_special( + rest: List(MalType), + env: env.Env, +) -> Result(MalType, types.Error) { + case rest { + [predicate, branch_true, ..branch_false] -> { + use predicate <- result.try(eval(predicate, env)) + + use false_ast <- result.try(case branch_false { + [] -> Ok(types.Nil) + [f] -> Ok(f) + _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) + }) + + case predicate { + Bool(False) | types.Nil -> { + eval(false_ast, env) + } + _ -> { + eval(branch_true, env) + } + } + } + _ -> Error(types.EvalWrongArgLenRange(#(3, 4), list.length(rest))) + } +} + +fn make_call(sym, rest) { + List([Symbol(sym), ..rest], types.Nil) +} + +fn quasiquote(ast: MalType) -> MalType { + case ast { + List([Symbol("unquote"), x], _) -> x + List(l, _) -> qq_fold(l) + Vector(v, _) -> make_call("vec", [qq_fold(v)]) + HashMap(..) -> make_call("quote", [ast]) + Symbol(_) -> make_call("quote", [ast]) + _ -> ast + } +} + +fn qq_fold(l) { + list.fold_right(l, List([], types.Nil), qq_loop) +} + +fn qq_loop(acc, elt) { + case elt { + List([Symbol("splice-unquote"), splice], _) -> + make_call("concat", [splice, acc]) + _ -> make_call("cons", [quasiquote(elt), acc]) + } +} + +fn get_pairs(list, acc) { + case list { + [a, b, ..rest] -> { + get_pairs(rest, [#(a, b), ..acc]) + } + [] -> Ok(list.reverse(acc)) + _ -> Error(types.StrErr("can't get bind pairs: count not divisible by 2")) + } +} + +fn read(str) { + reader.read_str(str) +} + +fn print(ast) { + printer.pr_str(ast, True) +} + +fn rep(str, env) { + use r <- result.try(read(str)) + use e <- result.try(eval(r, env)) + + Ok(print(e)) +} diff --git a/impls/gleam/test/mal_test.gleam b/impls/gleam/test/mal_test.gleam deleted file mode 100644 index 902c4da7e0..0000000000 --- a/impls/gleam/test/mal_test.gleam +++ /dev/null @@ -1,5 +0,0 @@ -import gleeunit - -pub fn main() -> Nil { - gleeunit.main() -} From c3894cc7b034beece39457503f38375b12fbd99c Mon Sep 17 00:00:00 2001 From: Donda Date: Mon, 22 Jun 2026 18:33:09 +0200 Subject: [PATCH 13/20] gleam: dockerfile --- impls/gleam/Dockerfile | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 impls/gleam/Dockerfile diff --git a/impls/gleam/Dockerfile b/impls/gleam/Dockerfile new file mode 100644 index 0000000000..5954a445a1 --- /dev/null +++ b/impls/gleam/Dockerfile @@ -0,0 +1,22 @@ +FROM alpine:3.24.1 +MAINTAINER NAME +LABEL org.opencontainers.image.source=https://github.com/kanaka/mal +LABEL org.opencontainers.image.description="mal test container: gleam" + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +# Required for running tests +RUN apk add --no-cache make python3 bash +RUN ln -fs /usr/bin/python3 /usr/local/bin/python + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +RUN apk add --no-cache gleam erlang rebar3 From d48c090c154e55034b6876b64474aa5026909702 Mon Sep 17 00:00:00 2001 From: Donda Date: Mon, 22 Jun 2026 23:19:10 +0200 Subject: [PATCH 14/20] gleam: fix dockerfile --- impls/gleam/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/impls/gleam/Dockerfile b/impls/gleam/Dockerfile index 5954a445a1..43ff7e385a 100644 --- a/impls/gleam/Dockerfile +++ b/impls/gleam/Dockerfile @@ -1,5 +1,6 @@ FROM alpine:3.24.1 -MAINTAINER NAME +# I would rather not provide my real name +MAINTAINER dondorio LABEL org.opencontainers.image.source=https://github.com/kanaka/mal LABEL org.opencontainers.image.description="mal test container: gleam" @@ -19,4 +20,4 @@ WORKDIR /mal # Specific implementation requirements ########################################################## -RUN apk add --no-cache gleam erlang rebar3 +RUN apk add --no-cache gleam erlang28 rebar3 From 96b5d47f0d08afa4c70a49bea2e3a37295f0a948 Mon Sep 17 00:00:00 2001 From: Donda Date: Mon, 22 Jun 2026 23:39:50 +0200 Subject: [PATCH 15/20] gleam: github actions --- IMPLS.yml | 2 ++ Makefile.impls | 1 + impls/gleam/Dockerfile | 2 +- impls/gleam/Makefile | 2 +- impls/gleam/run | 2 +- 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/IMPLS.yml b/IMPLS.yml index 17e38a53eb..a98302166c 100644 --- a/IMPLS.yml +++ b/IMPLS.yml @@ -30,6 +30,8 @@ IMPL: - {IMPL: fennel} - {IMPL: forth} - {IMPL: fsharp} + - {IMPL: gleam, gleam_MODE: erlang} + - {IMPL: gleam, gleam_MODE: js} - {IMPL: go} - {IMPL: groovy} - {IMPL: gnu-smalltalk} diff --git a/Makefile.impls b/Makefile.impls index c6a51d5075..e6a24164ba 100644 --- a/Makefile.impls +++ b/Makefile.impls @@ -51,6 +51,7 @@ step5_EXCLUDES += powershell # too slow for 10,000 step5_EXCLUDES += prolog # no iteration (but interpreter does TCO implicitly) step5_EXCLUDES += sml # not implemented :( step5_EXCLUDES += $(if $(filter cpp,$(haxe_MODE)),haxe,) # cpp finishes 10,000, segfaults at 100,000 +step5_EXCLUDES += $(if $(filter js,$(gleam_MODE)),gleam,) step5_EXCLUDES += xslt # iteration cannot be expressed step5_EXCLUDES += vbs # too slow for 10,000 diff --git a/impls/gleam/Dockerfile b/impls/gleam/Dockerfile index 43ff7e385a..01d1b694c7 100644 --- a/impls/gleam/Dockerfile +++ b/impls/gleam/Dockerfile @@ -20,4 +20,4 @@ WORKDIR /mal # Specific implementation requirements ########################################################## -RUN apk add --no-cache gleam erlang28 rebar3 +RUN apk add --no-cache gleam erlang28 rebar3 nodejes diff --git a/impls/gleam/Makefile b/impls/gleam/Makefile index d2b970e946..042a0d3e4d 100644 --- a/impls/gleam/Makefile +++ b/impls/gleam/Makefile @@ -12,7 +12,7 @@ step9_try \ stepA_mal $(STEPS): -ifneq ($(filter $(GLEAM_TARGET), javascript js), ) +ifneq ($(filter $(gleam_MODE), javascript js), ) npm install ../gleam endif gleam build --no-print-progress diff --git a/impls/gleam/run b/impls/gleam/run index a8dd6e343e..0133764a29 100755 --- a/impls/gleam/run +++ b/impls/gleam/run @@ -6,4 +6,4 @@ if [ "$STEP" == "stepA_mal" ]; then fi cd $(dirname $0) -exec gleam run --module ${STEP:-stepa_mal} --no-print-progress --target ${GLEAM_TARGET:-erlang} -- "${@}" +exec gleam run --module ${STEP:-stepa_mal} --no-print-progress --target ${gleam_MODE:-erlang} -- "${@}" From 2de8f6cabb26436db65598eb018068136a9e9876 Mon Sep 17 00:00:00 2001 From: Donda Date: Wed, 24 Jun 2026 11:58:11 +0200 Subject: [PATCH 16/20] gleam: fix dockerfile --- impls/gleam/Dockerfile | 6 +++++- impls/gleam/Makefile | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/impls/gleam/Dockerfile b/impls/gleam/Dockerfile index 01d1b694c7..84294ea8c7 100644 --- a/impls/gleam/Dockerfile +++ b/impls/gleam/Dockerfile @@ -20,4 +20,8 @@ WORKDIR /mal # Specific implementation requirements ########################################################## -RUN apk add --no-cache gleam erlang28 rebar3 nodejes +RUN apk add --no-cache gleam erlang28 rebar3 nodejs npm +RUN mkdir -p /.cache +RUN chmod -R 777 /.cache +RUN mkdir -p /.npm +RUN chmod -R 777 /.npm diff --git a/impls/gleam/Makefile b/impls/gleam/Makefile index 042a0d3e4d..54f701eb7a 100644 --- a/impls/gleam/Makefile +++ b/impls/gleam/Makefile @@ -13,7 +13,10 @@ stepA_mal $(STEPS): ifneq ($(filter $(gleam_MODE), javascript js), ) - npm install ../gleam + echo "npm installing" + mkdir -p /.npm + ls -la /.npm + npm install endif gleam build --no-print-progress From 347c86fde0ccc6eb974eeb7758f43cde0796f77d Mon Sep 17 00:00:00 2001 From: Donda Date: Wed, 24 Jun 2026 13:05:30 +0200 Subject: [PATCH 17/20] gleam: fix javascript target build --- impls/gleam/.github/workflows/test.yml | 23 --------------- impls/gleam/Makefile | 5 +--- impls/gleam/README.md | 24 --------------- impls/gleam/src/mut_cell.gleam | 41 ++++++++++++++------------ impls/gleam/src/readline.gleam | 7 ++++- 5 files changed, 29 insertions(+), 71 deletions(-) delete mode 100644 impls/gleam/.github/workflows/test.yml delete mode 100644 impls/gleam/README.md diff --git a/impls/gleam/.github/workflows/test.yml b/impls/gleam/.github/workflows/test.yml deleted file mode 100644 index 4a7fe22d29..0000000000 --- a/impls/gleam/.github/workflows/test.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: test - -on: - push: - branches: - - master - - main - pull_request: - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: erlef/setup-beam@v1 - with: - otp-version: "27.1.2" - gleam-version: "1.12.0" - rebar3-version: "3" - # elixir-version: "1" - - run: gleam deps download - - run: gleam test - - run: gleam format --check src test diff --git a/impls/gleam/Makefile b/impls/gleam/Makefile index 54f701eb7a..b0719311c3 100644 --- a/impls/gleam/Makefile +++ b/impls/gleam/Makefile @@ -13,12 +13,9 @@ stepA_mal $(STEPS): ifneq ($(filter $(gleam_MODE), javascript js), ) - echo "npm installing" - mkdir -p /.npm - ls -la /.npm npm install endif - gleam build --no-print-progress + gleam build --target $(gleam_MODE) clean: gleam clean diff --git a/impls/gleam/README.md b/impls/gleam/README.md deleted file mode 100644 index 6fc8195c24..0000000000 --- a/impls/gleam/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# mal - -[![Package Version](https://img.shields.io/hexpm/v/mal)](https://hex.pm/packages/mal) -[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/mal/) - -```sh -gleam add mal@1 -``` -```gleam -import mal - -pub fn main() -> Nil { - // TODO: An example of the project in use -} -``` - -Further documentation can be found at . - -## Development - -```sh -gleam run # Run the project -gleam test # Run the tests -``` diff --git a/impls/gleam/src/mut_cell.gleam b/impls/gleam/src/mut_cell.gleam index 1faeac132e..2083e4db43 100644 --- a/impls/gleam/src/mut_cell.gleam +++ b/impls/gleam/src/mut_cell.gleam @@ -4,9 +4,7 @@ import gleam/erlang/process.{type Subject} import gleam/otp/actor import gleam/result -@target(javascript) -pub type MutCell(a) - +// ----- erlang ----- @target(erlang) pub type MutCell(a) = actor.Started(process.Subject(Message(a))) @@ -18,10 +16,6 @@ pub type Message(a) { Update(fn(a) -> a) } -@target(javascript) -@external(javascript, "./mut_cell_ffi.mjs", "new_cell") -pub fn new(value: a) -> MutCell(a) - @target(erlang) pub fn new(value: a) -> MutCell(a) { let handle_message = fn(state: a, message: Message(a)) -> actor.Next( @@ -49,33 +43,42 @@ pub fn new(value: a) -> MutCell(a) { actor } -@target(javascript) -@external(javascript, "./mut_cell_ffi.mjs", "get") -pub fn get(cell: MutCell(a)) -> a - @target(erlang) pub fn get(cell: MutCell(a)) -> a { actor.call(cell.data, waiting: 10, sending: Get) } -@target(javascript) -@external(javascript, "./mut_cell_ffi.mjs", "set") -pub fn set(cell: MutCell(a), new: a) -> Nil - @target(erlang) pub fn set(cell: MutCell(a), new: a) -> Nil { actor.send(cell.data, Set(new)) } -@target(javascript) -@external(javascript, "./mut_cell_ffi.mjs", "update") -pub fn update(cell: MutCell(a), f: fn(a) -> a) -> Nil - @target(erlang) pub fn update(cell: MutCell(a), f: fn(a) -> a) -> Nil { actor.send(cell.data, Update(f)) } +// ----- javascript ----- +@target(javascript) +pub type MutCell(a) + +@target(javascript) +@external(javascript, "./mut_cell_ffi.mjs", "new_cell") +pub fn new(value: a) -> MutCell(a) + +@target(javascript) +@external(javascript, "./mut_cell_ffi.mjs", "get") +pub fn get(cell: MutCell(a)) -> a + +@target(javascript) +@external(javascript, "./mut_cell_ffi.mjs", "set") +pub fn set(cell: MutCell(a), new: a) -> Nil + +@target(javascript) +@external(javascript, "./mut_cell_ffi.mjs", "update") +pub fn update(cell: MutCell(a), f: fn(a) -> a) -> Nil + +// ----- common ----- pub fn try_update(cell: MutCell(a), f: fn(a) -> Result(a, e)) { let data = get(cell) use res <- result.try(f(data)) diff --git a/impls/gleam/src/readline.gleam b/impls/gleam/src/readline.gleam index 1bf871c7b7..aad3daa443 100644 --- a/impls/gleam/src/readline.gleam +++ b/impls/gleam/src/readline.gleam @@ -1,6 +1,11 @@ +@target(erlang) import input.{input} -@external(javascript, "./readline_ffi.mjs", "readline_func") +@target(erlang) pub fn readline(prn: String) -> Result(String, Nil) { input(prn) } + +@target(javascript) +@external(javascript, "./readline_ffi.mjs", "readline_func") +pub fn readline(prn: String) -> Result(String, Nil) From 3b525924b78a26c4c4d4d2da4a488201e85a6f68 Mon Sep 17 00:00:00 2001 From: Donda Date: Wed, 24 Jun 2026 13:15:25 +0200 Subject: [PATCH 18/20] gleam: disable js target perf test --- IMPLS.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IMPLS.yml b/IMPLS.yml index a98302166c..ec96e2b3a5 100644 --- a/IMPLS.yml +++ b/IMPLS.yml @@ -31,7 +31,7 @@ IMPL: - {IMPL: forth} - {IMPL: fsharp} - {IMPL: gleam, gleam_MODE: erlang} - - {IMPL: gleam, gleam_MODE: js} + - {IMPL: gleam, gleam_MODE: js, NO_PERF: 1, NO_SELF_HOST_PERF: 1} # stack overflow - {IMPL: go} - {IMPL: groovy} - {IMPL: gnu-smalltalk} From 8878d3806cf3d1e366cf0625ce98ddec779cc0e9 Mon Sep 17 00:00:00 2001 From: Donda Date: Thu, 25 Jun 2026 11:44:31 +0200 Subject: [PATCH 19/20] gleam: misc --- impls/gleam/Dockerfile | 2 +- impls/gleam/Makefile | 2 ++ impls/gleam/src/core.gleam | 1 - impls/gleam/src/printer.gleam | 6 ++++ impls/gleam/src/readline.gleam | 7 +--- impls/gleam/src/step3_env.gleam | 52 ++++++++++++++++------------ impls/gleam/src/step4_if_fn_do.gleam | 9 +++-- impls/gleam/src/step5_tco.gleam | 9 +++-- impls/gleam/src/step6_file.gleam | 9 +++-- impls/gleam/src/step7_quote.gleam | 9 +++-- impls/gleam/src/step8_macros.gleam | 9 +++-- impls/gleam/src/step9_try.gleam | 9 +++-- impls/gleam/src/stepa_mal.gleam | 9 +++-- impls/gleam/src/types.gleam | 1 + 14 files changed, 83 insertions(+), 51 deletions(-) diff --git a/impls/gleam/Dockerfile b/impls/gleam/Dockerfile index 84294ea8c7..a62d1eb7e1 100644 --- a/impls/gleam/Dockerfile +++ b/impls/gleam/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:3.24.1 # I would rather not provide my real name -MAINTAINER dondorio +MAINTAINER Dondorio LABEL org.opencontainers.image.source=https://github.com/kanaka/mal LABEL org.opencontainers.image.description="mal test container: gleam" diff --git a/impls/gleam/Makefile b/impls/gleam/Makefile index b0719311c3..276ba0b0b8 100644 --- a/impls/gleam/Makefile +++ b/impls/gleam/Makefile @@ -11,6 +11,8 @@ step8_macros \ step9_try \ stepA_mal +gleam_MODE ?= erlang + $(STEPS): ifneq ($(filter $(gleam_MODE), javascript js), ) npm install diff --git a/impls/gleam/src/core.gleam b/impls/gleam/src/core.gleam index 8e0316844b..cdf8624558 100644 --- a/impls/gleam/src/core.gleam +++ b/impls/gleam/src/core.gleam @@ -25,7 +25,6 @@ fn f(name: String, fun) { #(name, func(fun)) } -// fn list_op() fn int_op(name, with: fn(Int, Int) -> Result(MalType, Error)) { #( name, diff --git a/impls/gleam/src/printer.gleam b/impls/gleam/src/printer.gleam index 21667a5cd7..bcfb8b9d31 100644 --- a/impls/gleam/src/printer.gleam +++ b/impls/gleam/src/printer.gleam @@ -28,6 +28,12 @@ pub fn pr_err(err: types.Error) -> String { <> int.to_string(expected.1) <> " arguments, got " <> int.to_string(got) + types.EvalLetPairOddCount(got) -> + "can't bind let values: odd number of items " + <> "(" + <> list.map(got, fn(x) { pr_str(x, True) }) + |> string.join(" ") + <> ")" types.EvalWrongType(expected, got) -> "expected '" <> expected <> "', got '" <> got <> "'" diff --git a/impls/gleam/src/readline.gleam b/impls/gleam/src/readline.gleam index aad3daa443..1bf871c7b7 100644 --- a/impls/gleam/src/readline.gleam +++ b/impls/gleam/src/readline.gleam @@ -1,11 +1,6 @@ -@target(erlang) import input.{input} -@target(erlang) +@external(javascript, "./readline_ffi.mjs", "readline_func") pub fn readline(prn: String) -> Result(String, Nil) { input(prn) } - -@target(javascript) -@external(javascript, "./readline_ffi.mjs", "readline_func") -pub fn readline(prn: String) -> Result(String, Nil) diff --git a/impls/gleam/src/step3_env.gleam b/impls/gleam/src/step3_env.gleam index 67d09d06b9..3c3e0210f1 100644 --- a/impls/gleam/src/step3_env.gleam +++ b/impls/gleam/src/step3_env.gleam @@ -107,23 +107,17 @@ fn eval(ast: MalType, env: env.Env) -> MalRet { } _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) } - Symbol("let*") -> { + Symbol("let*") -> case rest { - [pairs, closure] -> { + [pairs, body] -> case pairs { List(l, _) | Vector(l, _) -> { - let new_env = env.into_outer(env) - use e <- result.try(let_special(l, [], new_env)) - - use res <- result.try(eval(closure, e)) - Ok(res) + let_special(l, body, env) } - _ -> Error(types.EvalWrongType("list | vector", "")) + _ -> types.wrong_type_err("list | vector", [pairs]) } - } _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) } - } _ -> { use f <- result.try(eval(l, env)) @@ -164,22 +158,36 @@ fn eval(ast: MalType, env: env.Env) -> MalRet { } } -fn let_special(pairs, acc, env) { - case pairs { - [key, val, ..rest] -> { - use res <- result.try(eval(val, env)) - use k <- result.try(env.try_key(key)) - - env.set(env, k, res) - - let_special(rest, [res, ..acc], env) +fn get_pairs(list, acc) { + case list { + [a, b, ..rest] -> { + get_pairs(rest, [#(a, b), ..acc]) } - [] -> Ok(env) - // TODO different err - _ -> Error(types.EvalWrongArgLen(2, list.length(pairs))) + [] -> Ok(list.reverse(acc)) + _ -> Error(Nil) } } +fn let_special(p, body, env) { + use pairs <- result.try( + get_pairs(p, []) + |> result.replace_error(types.EvalLetPairOddCount(p)), + ) + let let_env = env.into_outer(env) + + use _ <- result.try( + list.try_map(pairs, fn(x) { + use key <- result.try(env.try_key(x.0)) + use val <- result.try(eval(x.1, let_env)) + env.set(let_env, key, val) + + Ok(Nil) + }), + ) + + eval(body, let_env) +} + fn read(str) { reader.read_str(str) } diff --git a/impls/gleam/src/step4_if_fn_do.gleam b/impls/gleam/src/step4_if_fn_do.gleam index 18dde02b5c..7b7f938f4b 100644 --- a/impls/gleam/src/step4_if_fn_do.gleam +++ b/impls/gleam/src/step4_if_fn_do.gleam @@ -174,12 +174,15 @@ fn get_pairs(list, acc) { get_pairs(rest, [#(a, b), ..acc]) } [] -> Ok(list.reverse(acc)) - _ -> Error(types.StrErr("can't get bind pairs: count not divisible by 2")) + _ -> Error(Nil) } } -fn let_special(pairs, body, env) { - use pairs <- result.try(get_pairs(pairs, [])) +fn let_special(p, body, env) { + use pairs <- result.try( + get_pairs(p, []) + |> result.replace_error(types.EvalLetPairOddCount(p)), + ) let let_env = env.into_outer(env) use _ <- result.try( diff --git a/impls/gleam/src/step5_tco.gleam b/impls/gleam/src/step5_tco.gleam index 18dde02b5c..7b7f938f4b 100644 --- a/impls/gleam/src/step5_tco.gleam +++ b/impls/gleam/src/step5_tco.gleam @@ -174,12 +174,15 @@ fn get_pairs(list, acc) { get_pairs(rest, [#(a, b), ..acc]) } [] -> Ok(list.reverse(acc)) - _ -> Error(types.StrErr("can't get bind pairs: count not divisible by 2")) + _ -> Error(Nil) } } -fn let_special(pairs, body, env) { - use pairs <- result.try(get_pairs(pairs, [])) +fn let_special(p, body, env) { + use pairs <- result.try( + get_pairs(p, []) + |> result.replace_error(types.EvalLetPairOddCount(p)), + ) let let_env = env.into_outer(env) use _ <- result.try( diff --git a/impls/gleam/src/step6_file.gleam b/impls/gleam/src/step6_file.gleam index 1b220135d6..f7e540ff1b 100644 --- a/impls/gleam/src/step6_file.gleam +++ b/impls/gleam/src/step6_file.gleam @@ -202,12 +202,15 @@ fn get_pairs(list, acc) { get_pairs(rest, [#(a, b), ..acc]) } [] -> Ok(list.reverse(acc)) - _ -> Error(types.StrErr("can't get bind pairs: count not divisible by 2")) + _ -> Error(Nil) } } -fn let_special(pairs, body, env) { - use pairs <- result.try(get_pairs(pairs, [])) +fn let_special(p, body, env) { + use pairs <- result.try( + get_pairs(p, []) + |> result.replace_error(types.EvalLetPairOddCount(p)), + ) let let_env = env.into_outer(env) use _ <- result.try( diff --git a/impls/gleam/src/step7_quote.gleam b/impls/gleam/src/step7_quote.gleam index f2ed3cf7c6..4ce397466d 100644 --- a/impls/gleam/src/step7_quote.gleam +++ b/impls/gleam/src/step7_quote.gleam @@ -150,7 +150,10 @@ fn let_special(rest, env) { [pairs, body] -> case pairs { List(l, _) | Vector(l, _) -> { - use pairs <- result.try(get_pairs(l, [])) + use pairs <- result.try( + get_pairs(l, []) + |> result.replace_error(types.EvalLetPairOddCount(l)), + ) let let_env = env.into_outer(env) use _ <- result.try( @@ -167,7 +170,7 @@ fn let_special(rest, env) { } _ -> types.wrong_type_err("list | vector", [pairs]) } - _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + _ -> types.wrong_type_err("list | vector, any", rest) } } @@ -273,7 +276,7 @@ fn get_pairs(list, acc) { get_pairs(rest, [#(a, b), ..acc]) } [] -> Ok(list.reverse(acc)) - _ -> Error(types.StrErr("can't get bind pairs: count not divisible by 2")) + _ -> Error(Nil) } } diff --git a/impls/gleam/src/step8_macros.gleam b/impls/gleam/src/step8_macros.gleam index 3ba4f3aa3c..5837f10127 100644 --- a/impls/gleam/src/step8_macros.gleam +++ b/impls/gleam/src/step8_macros.gleam @@ -177,7 +177,10 @@ fn let_special(rest, env) { [pairs, body] -> case pairs { List(l, _) | Vector(l, _) -> { - use pairs <- result.try(get_pairs(l, [])) + use pairs <- result.try( + get_pairs(l, []) + |> result.replace_error(types.EvalLetPairOddCount(l)), + ) let let_env = env.into_outer(env) use _ <- result.try( @@ -194,7 +197,7 @@ fn let_special(rest, env) { } _ -> types.wrong_type_err("list | vector", [pairs]) } - _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + _ -> types.wrong_type_err("list | vector, any", rest) } } @@ -300,7 +303,7 @@ fn get_pairs(list, acc) { get_pairs(rest, [#(a, b), ..acc]) } [] -> Ok(list.reverse(acc)) - _ -> Error(types.StrErr("can't get bind pairs: count not divisible by 2")) + _ -> Error(Nil) } } diff --git a/impls/gleam/src/step9_try.gleam b/impls/gleam/src/step9_try.gleam index d2ce9bd039..819457befa 100644 --- a/impls/gleam/src/step9_try.gleam +++ b/impls/gleam/src/step9_try.gleam @@ -179,7 +179,10 @@ fn let_special(rest, env) { [pairs, body] -> case pairs { List(l, _) | Vector(l, _) -> { - use pairs <- result.try(get_pairs(l, [])) + use pairs <- result.try( + get_pairs(l, []) + |> result.replace_error(types.EvalLetPairOddCount(l)), + ) let let_env = env.into_outer(env) use _ <- result.try( @@ -196,7 +199,7 @@ fn let_special(rest, env) { } _ -> types.wrong_type_err("list | vector", [pairs]) } - _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + _ -> types.wrong_type_err("list | vector, any", rest) } } @@ -323,7 +326,7 @@ fn get_pairs(list, acc) { get_pairs(rest, [#(a, b), ..acc]) } [] -> Ok(list.reverse(acc)) - _ -> Error(types.StrErr("can't get bind pairs: count not divisible by 2")) + _ -> Error(Nil) } } diff --git a/impls/gleam/src/stepa_mal.gleam b/impls/gleam/src/stepa_mal.gleam index 9993c832df..f2397b0243 100644 --- a/impls/gleam/src/stepa_mal.gleam +++ b/impls/gleam/src/stepa_mal.gleam @@ -180,7 +180,10 @@ fn let_special(rest, env) { [pairs, body] -> case pairs { List(l, _) | Vector(l, _) -> { - use pairs <- result.try(get_pairs(l, [])) + use pairs <- result.try( + get_pairs(l, []) + |> result.replace_error(types.EvalLetPairOddCount(l)), + ) let let_env = env.into_outer(env) use _ <- result.try( @@ -197,7 +200,7 @@ fn let_special(rest, env) { } _ -> types.wrong_type_err("list | vector", [pairs]) } - _ -> Error(types.EvalWrongArgLen(2, list.length(rest))) + _ -> types.wrong_type_err("list | vector, any", rest) } } @@ -324,7 +327,7 @@ fn get_pairs(list, acc) { get_pairs(rest, [#(a, b), ..acc]) } [] -> Ok(list.reverse(acc)) - _ -> Error(types.StrErr("can't get bind pairs: count not divisible by 2")) + _ -> Error(Nil) } } diff --git a/impls/gleam/src/types.gleam b/impls/gleam/src/types.gleam index ebd304085e..bd2c0a7af8 100644 --- a/impls/gleam/src/types.gleam +++ b/impls/gleam/src/types.gleam @@ -121,6 +121,7 @@ pub type Error { EvalWrongArgLenGreaterThan(expected: Int, got: Int) EvalWrongType(expected: String, got: String) EvalDivideByZero + EvalLetPairOddCount(got: List(MalType)) EvalApplyType(got: MalType) EvalSymbolNotFound(symbol: String) EvalFuncParamNotSymbol From 5612eacabb158ff40c0836c0a6102c6ef7d100a6 Mon Sep 17 00:00:00 2001 From: Donda Date: Thu, 25 Jun 2026 11:54:35 +0200 Subject: [PATCH 20/20] gleam: update readme --- README.md | 10 ++++++++++ impls/gleam/Dockerfile | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3dca01ab2d..125e6f59ca 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ FAQ](docs/FAQ.md) where I attempt to answer some common questions. | [Fantom](#fantom) | [Dov Murik](https://github.com/dubek) | | [Fennel](#fennel) | [sogaiu](https://github.com/sogaiu) | | [Forth](#forth) | [Chris Houser](https://github.com/chouser) | +| [Gleam](#gleam) | [Dondorio](https://github.com/Dondorio) | | [GNU Guile](#gnu-guile-21) | [Mu Lei](https://github.com/NalaGinrut) | | [GNU Smalltalk](#gnu-smalltalk) | [Vasilij Schneidermann](https://github.com/wasamasa) | | [Go](#go) | [Joel Martin](https://github.com/kanaka) | @@ -531,6 +532,15 @@ cd impls/forth gforth stepX_YYY.fs ``` +### Gleam + +The Gleam implementation of mal has been tested with Gleam 1.17.0 + +``` +cd impls/gleam +./run +``` + ### GNU Guile 2.1+ ``` diff --git a/impls/gleam/Dockerfile b/impls/gleam/Dockerfile index a62d1eb7e1..12a326cac4 100644 --- a/impls/gleam/Dockerfile +++ b/impls/gleam/Dockerfile @@ -1,5 +1,4 @@ FROM alpine:3.24.1 -# I would rather not provide my real name MAINTAINER Dondorio LABEL org.opencontainers.image.source=https://github.com/kanaka/mal LABEL org.opencontainers.image.description="mal test container: gleam"