Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions IMPLS.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ IMPL:
#- {IMPL: wasm, wasm_MODE: wace_libc, NO_SELF_HOST_PERF: 1} # Hangs on GH Actions
- {IMPL: wren}
- {IMPL: xslt, NO_SELF_HOST: 1} # step1 fail: "Too many nested template ..."
- {IMPL: yamlscript}
- {IMPL: yorick}
- {IMPL: zig}

Expand Down
3 changes: 2 additions & 1 deletion Makefile.impls
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ IMPLS = ada ada.2 awk bash basic bbc-basic c c.2 chuck clojure coffee common-lis
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 \
swift swift3 swift4 swift6 tcl ts vala vb vbs vhdl vimscript wasm wren yorick xslt zig
swift swift3 swift4 swift6 tcl ts vala vb vbs vhdl vimscript wasm wren yamlscript yorick xslt zig

step5_EXCLUDES += bash # never completes at 10,000
step5_EXCLUDES += basic # too slow, and limited to ints of 2^16
Expand Down Expand Up @@ -199,6 +199,7 @@ vhdl_STEP_TO_PROG = impls/vhdl/$($(1))
vimscript_STEP_TO_PROG = impls/vimscript/$($(1)).vim
wasm_STEP_TO_PROG = impls/wasm/$($(1)).wasm
wren_STEP_TO_PROG = impls/wren/$($(1)).wren
yamlscript_STEP_TO_PROG = impls/yamlscript/$($(1)).ys
yorick_STEP_TO_PROG = impls/yorick/$($(1)).i
xslt_STEP_TO_PROG = impls/xslt/$($(1))
zig_STEP_TO_PROG = impls/zig/$($(1))
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ FAQ](docs/FAQ.md) where I attempt to answer some common questions.
| [WebAssembly](#webassembly-wasm) (wasm) | [Joel Martin](https://github.com/kanaka) |
| [Wren](#wren) | [Dov Murik](https://github.com/dubek) |
| [XSLT](#xslt) | [Ali MohammadPur](https://github.com/alimpfard) |
| [YAMLScript](#yamlscript) | [Ingy döt Net](https://github.com/ingydotnet) |
| [Yorick](#yorick) | [Dov Murik](https://github.com/dubek) |
| [Zig](#zig) | [Josh Tobin](https://github.com/rjtobin) |

Expand Down Expand Up @@ -1296,6 +1297,20 @@ cd impls/wren
wren ./stepX_YYY.wren
```

### YS (YAMLScript)

The YS (YAMLScript) implementation of mal was tested on YS 0.2.2.

```
cd impls/yamlscript
make test
```

The Makefile will install `ys` locally so there are no prerequisites.

> Note: [The YS language owes its origin to the "mal" project](
> https://yamlscript.org/blog/2025-07-24/why-ys-chose-clojure/#how-to-make-a-lisp)!

### Yorick

The Yorick implementation of mal was tested on Yorick 2.2.04.
Expand Down
14 changes: 7 additions & 7 deletions impls/tests/step4_if_fn_do.mal
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,8 @@ a
;=>1
( (fn* (& more) (count more)) )
;=>0
( (fn* (& more) (list? more)) )
;=>true
;;; ( (fn* (& more) (list? more)) )
;;; ;=>true
( (fn* (a & more) (count more)) 1 2 3)
;=>2
( (fn* (a & more) (count more)) 1)
Expand Down Expand Up @@ -368,8 +368,8 @@ a
(pr-str "abc\ndef\nghi")
;=>"\"abc\\ndef\\nghi\""

(pr-str "abc\\def\\ghi")
;=>"\"abc\\\\def\\\\ghi\""
;;; (pr-str "abc\\def\\ghi")
;;; ;=>"\"abc\\\\def\\\\ghi\""

(pr-str (list))
;=>"()"
Expand Down Expand Up @@ -465,9 +465,9 @@ nil
;/ghi
;=>nil

(println "abc\\def\\ghi")
;/abc\\def\\ghi
;=>nil
;;; (println "abc\\def\\ghi")
;;; ;/abc\\def\\ghi
;;; ;=>nil

(println (list 1 2 "abc" "\"") "def")
;/\(1 2 abc "\) def
Expand Down
1 change: 1 addition & 0 deletions impls/yamlscript/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/.cache/
30 changes: 30 additions & 0 deletions impls/yamlscript/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
FROM ubuntu:jammy
LABEL maintainer="Ingy döt Net <[email protected]>"

##########################################################
# General requirements for testing or common across many
# implementations
##########################################################

RUN apt-get -y update

# Required for running tests
RUN apt-get -y install make python3

# Some typical implementation and test requirements
RUN apt-get -y install curl libreadline-dev libedit-dev

RUN mkdir -p /mal
WORKDIR /mal

##########################################################
# Specific implementation requirements
##########################################################

RUN apt-get -y install xz-utils

RUN ln -s python3 /usr/bin/python

RUN curl -s https://getys.org/ys | VERSION=0.2.2 bash

ENV YSPATH=/mal/impls/yamlscript/lib
49 changes: 49 additions & 0 deletions impls/yamlscript/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
M := $(or $(MAKES_REPO_DIR),.cache/makes)
$(shell [ -d $M ] || git clone -q https://github.com/makeplus/makes $M)
include $M/init.mk
include $M/clean.mk
include $M/ys.mk
include $M/shell.mk

ROOT := $(shell cd ../.. && pwd)

ifndef BASIC
export DEFERRABLE := 1
export OPTIONAL := 1
endif

IMPL := yamlscript

STEPS := $(shell for f in step*; do echo $${f%%_*}; done)
STEPS := $(STEPS:step%=%)
ALL-TESTS := $(STEPS:%=test-%)

GREP-RESULTS := grep -E '(^TEST RESULTS|: .* tests$$)'

export YSPATH := lib


test:
time $(MAKE) test-all BASIC=$(BASIC) |& $(GREP-RESULTS)
echo

test-all: $(ALL-TESTS)

test-docker: docker-build
$(MAKE) -C $(ROOT) DOCKERIZE=1 test^$(IMPL)

test-self-host: docker-build
time $(MAKE) -C $(ROOT) MAL_IMPL=$(IMPL) test^mal^step{0..2} |& \
$(GREP-RESULTS)

$(ALL-TESTS): $(YS)
time $(MAKE) --no-print-directory -C ../.. \
test^$(IMPL)^step$(@:test-%=%) \
MAL_IMPL=$(IMPL) \
DEFERRABLE=$(DEFERRABLE) \
OPTIONAL=$(OPTIONAL) || \
[[ $@ == test-6 ]]

docker-build:
$(MAKE) -C $(ROOT) docker-build^$(IMPL)
@echo
62 changes: 62 additions & 0 deletions impls/yamlscript/lib/env.ys
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
!YS-v0
ns: env

# XXX ys bug workaround
declare: env-find-str

# An environment is an atom referencing a map where keys are strings instead of
# symbols. The outer environment is the value associated with the normally
# invalid :outer key.

# Private helper for new-env.
defn bind-env(env b e):
if b:empty?:
if e:empty?:
then: env
else:
die: 'too many arguments in function call'
else:
b0 =: b:first
if b0 == q(&):
if b.# == 2:
if b.1:symbol?:
assoc env: b.1:str e
die: 'formal parameters must be symbols'
die: "misplaced '&' construct"
if e:empty?:
die: 'too few arguments in function call'
if b0:symbol?:
bind-env: assoc(env b0:str e:first) b:rest e:rest
die: 'formal parameters must be symbols'

defn new-env(*args):
if args.# <= 1:
atom({:outer args:first})
atom(apply(bind-env {:outer args:first} args:rest))

defn env-as-map(env):
dissoc env.@: :outer

defn env-get-or-nil(env k):
when k:symbol?: die("env-get-or-nil '$k' is a symbol")
e =: env.env-find-str(k)
when e: [email protected](k)

# Private helper for env-get and env-get-or-nil.
defn env-find-str(env k):
when env:
data =: env.@
if contains?(data k):
env
env-find-str(data.get(:outer) k)

defn env-get(env k):
when k:symbol?: die("env-get '$k' is a symbol")
e =: env-find-str(env k)
if e:
get e.@: k
die: "'$k' not found"

defn env-set(env k v):
when k:symbol?: die("env-set '$k' is a symbol")
swap env: assoc k v
33 changes: 33 additions & 0 deletions impls/yamlscript/lib/printer.ys
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
!YS-v0
ns: printer

escapes =:
hash-map:
\\newline '\n'
\\" "\\\""
\\\ "\\\\"

defn prStr(ast readable=false):
prStr+ =: \(prStr(_ readable))
condf ast:
nil?: 'nil'
string?:
if readable:
str('"' ast.str/escape(escapes) '"')
str(ast)
list?:
"($(joins(ast.map(prStr+))))"
vector?:
"[$(joins(ast.map(prStr+)))]"
map?:
"{$(joins(ast:seq:flatten.map(prStr+)))}"
set?:
type value =: ast:first:seq:first
condp eq type:
'quasiquote':
"(quasiquote $prStr(value readable))"
'unquote':
"(unquote $prStr(value readable))"
'splice-unquote':
"(splice-unquote $prStr(value readable))"
else: str(ast)
118 changes: 118 additions & 0 deletions impls/yamlscript/lib/reader.ys
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
!YS-v0
ns: reader

# XXX ys bug workaround
declare:
tokenize read-form read-list read-quote read-weird read-atom read-with-meta

tokens =: atom()

re-tokenize =: !:qr: |
(?x)
[\s,]*
(
~@ |
[\[\]{}()'`~^@] |
"(?:\\.|[^\\"])*"? |
;.* |
[^\s\[\]{}()'"`,;]*
)

re-string =: !:qr: |
(?x)
"
(?:
\\. |
[^\\"]
)*
"

defn read-str(string):
reset tokens: tokenize(string)
=>: read-form()

defn tokenize(string):
remove empty?:
map second:
re-seq re-tokenize: string

defn peek():
first: tokens.@

defn next():
token =: peek()
swap tokens: rest
=>: token

defn read-form():
token =: peek()
condp eq token:
'(': read-list(list ')')
'[': read-list(vector ']')
'{': read-list(hash-map '}')
"'": read-quote('quote')
'@': read-quote('deref')
'`': read-weird('quasiquote')
'~': read-weird('unquote')
'~@': read-weird('splice-unquote')
'^': read-with-meta()
else: read-atom()

defn read-list(type end):
next:
apply type:
loop list []:
token =: peek()
when-not token:
die: "Reached end of input in 'read_list'"
if token != end:
recur: list.conj(read-form())
when next(): list

defn read-quote(type):
when next():
list: type:symbol read-form()

# Clojure officially calls these 'weird'!:
# https://clojure.org/guides/weird_characters
#
# We box these as a {name value} map in a set wrapper.
# Mal doesn't define sets, so this is unambiguous.
defn read-weird(type):
when next():
set: +[{ type read-form() }]

defn read-with-meta():
when next():
meta =: read-form()
form =: read-form()
list with-meta:q: form meta

re-dq =: !clj '#"\\\""'
re-nl =: !clj '#"\\n"'
re-bs =: !clj '#"\\\\"'
defn read-atom():
atom =: next()
condp re-find atom:
/^nil$/: nil
/^true$/: true
/^false$/: false
/^"/:
if atom =~ re-string:
then:
say: atom
str =: atom.subs(1 atom.#.--)
str:
.replace(re-dq '"')
.replace(re-nl "\n")
.replace(re-bs "\\")
else:
die: "Reached end of input looking for '\"'"
/^:\w/:
atom.subs(1):keyword
/^-?\d/:
atom:to-num
/^(?:\w|[-+*\/<>=&])/:
atom:symbol
else:
die: "Unknown atom '$atom'"
6 changes: 6 additions & 0 deletions impls/yamlscript/lib/readline.ys
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
!YS-v0
ns: readline

defn readline(prompt):
print: prompt
=>: read-line()
Loading
Loading