objpascal/pas-readline
objpascal/regexpr/Source/RegExpr.ppu
perl/mal.pl
+perl6/.precomp/
php/mal.php
ps/mal.ps
python/mal.pyz
miniMAL_TEST_OPTS = --start-timeout 60 --test-timeout 120
plpgsql_TEST_OPTS = --start-timeout 60 --test-timeout 180
plsql_TEST_OPTS = --start-timeout 120 --test-timeout 120
+perl6_TEST_OPTS = --test-timeout=60
DOCKERIZE=
IMPLS = ada awk bash c d clojure coffee cpp crystal cs erlang elisp \
elixir es6 factor forth fsharp go groovy guile haskell haxe \
io java julia js kotlin lua make mal ocaml matlab miniMAL \
- nim objc objpascal perl php plpgsql plsql ps python r racket \
- rpython ruby rust scala swift swift3 tcl vb vhdl vimscript
+ nim objc objpascal perl perl6 php plpgsql plsql ps python r \
+ racket rpython ruby rust scala swift swift3 tcl vb vhdl vimscript
step0 = step0_repl
step1 = step1_read_print
objc_STEP_TO_PROG = objc/$($(1))
objpascal_STEP_TO_PROG = objpascal/$($(1))
perl_STEP_TO_PROG = perl/$($(1)).pl
+perl6_STEP_TO_PROG = perl6/$($(1)).pl
php_STEP_TO_PROG = php/$($(1)).php
plpgsql_STEP_TO_PROG = plpgsql/$($(1)).sql
plsql_STEP_TO_PROG = plsql/$($(1)).sql
Mal is a Clojure inspired Lisp interpreter.
-Mal is implemented in 54 languages:
+Mal is implemented in 55 languages:
* Ada
* GNU awk
* Objective C
* OCaml
* Perl
+* Perl 6
* PHP
* PL/pgSQL (Postgres)
* PL/SQL (Oracle)
perl stepX_YYY.pl
```
+### Perl 6
+
+*The Perl 6 implementation was created by [Hinrik Örn Sigurðsson](https://github.com/hinrik)*
+
+The Perl 6 implementation was tested on Rakudo Perl 6 2016.04.
+
+```
+cd perl6
+perl6 stepX_YYY.pl
+```
### PHP 5.3
--- /dev/null
+all:
+ @true
--- /dev/null
+unit module core;
+use types;
+use printer;
+use reader;
+
+sub equal ($a, $b) {
+ if $a ~~ MalSequence && $b ~~ MalSequence {
+ return $FALSE if $a.elems != $b.elems;
+ for |$a Z |$b -> ($a_el, $b_el) {
+ return $FALSE if equal($a_el, $b_el) ~~ $FALSE;
+ }
+ return $TRUE;
+ }
+ elsif $a ~~ MalHashMap && $b ~~ MalHashMap {
+ return $FALSE if $a.elems != $b.elems;
+ for $a.pairs {
+ return $FALSE if !$b{.key} || equal(.value, $b{.key}) ~~ $FALSE;
+ }
+ return $TRUE;
+ }
+ else {
+ return $a.^name eq $b.^name && $a.val ~~ $b.val ?? $TRUE !! $FALSE;
+ }
+}
+
+our %ns = (
+ '+' => MalCode({ MalNumber($^a.val + $^b.val) }),
+ '-' => MalCode({ MalNumber($^a.val - $^b.val) }),
+ '*' => MalCode({ MalNumber($^a.val * $^b.val) }),
+ '/' => MalCode({ MalNumber(($^a.val / $^b.val).Int) }),
+ '<' => MalCode({ $^a.val < $^b.val ?? $TRUE !! $FALSE }),
+ '<=' => MalCode({ $^a.val <= $^b.val ?? $TRUE !! $FALSE }),
+ '>' => MalCode({ $^a.val > $^b.val ?? $TRUE !! $FALSE }),
+ '>=' => MalCode({ $^a.val >= $^b.val ?? $TRUE !! $FALSE }),
+ '=' => MalCode({ equal($^a, $^b) }),
+ prn => MalCode({ say @_.map({ pr_str($_, True) }).join(' '); $NIL }),
+ println => MalCode({ say @_.map({ pr_str($_) }).join(' '); $NIL }),
+ pr-str => MalCode({ MalString(@_.map({ pr_str($_, True) }).join(' ') ) }),
+ str => MalCode({ MalString(@_.map({ pr_str($_) }).join) }),
+ read-string => MalCode({ read_str($^a.val) }),
+ slurp => MalCode({ MalString($^a.val.IO.slurp) }),
+ list => MalCode({ MalList(@_) }),
+ 'list?' => MalCode({ $^a ~~ MalList ?? $TRUE !! $FALSE }),
+ 'empty?' => MalCode({ $^a.elems ?? $FALSE !! $TRUE }),
+ count => MalCode({ MalNumber($^a ~~ $NIL ?? 0 !! $^a.elems) }),
+ atom => MalCode({ MalAtom($^a) }),
+ 'atom?' => MalCode({ $^a ~~ MalAtom ?? $TRUE !! $FALSE }),
+ deref => MalCode({ $^a.val }),
+ 'reset!' => MalCode({ $^a.val = $^b }),
+ 'swap!' => MalCode(-> $atom, $func, *@args { $atom.val = $func.apply($atom.val, |@args) }),
+ cons => MalCode({ MalList([$^a, |$^b.val]) }),
+ concat => MalCode({ MalList([@_.map({|$_.val})]) }),
+ nth => MalCode({ $^a[$^b.val] // die X::MalOutOfRange.new }),
+ first => MalCode({ $^a[0] // $NIL }),
+ rest => MalCode({ MalList([$^a[1..*]]) }),
+ throw => MalCode({ die X::MalThrow.new(value => $^a) }),
+ apply => MalCode(-> $func, *@args { $func.apply(|@args[0..*-2], |@args[*-1].val) }),
+ map => MalCode(-> $func, $list { MalList([$list.map({ $func.apply($_) })]) }),
+ 'nil?' => MalCode({ $^a ~~ MalNil ?? $TRUE !! $FALSE }),
+ 'true?' => MalCode({ $^a ~~ MalTrue ?? $TRUE !! $FALSE }),
+ 'false?' => MalCode({ $^a ~~ MalFalse ?? $TRUE !! $FALSE }),
+ 'symbol?' => MalCode({ $^a ~~ MalSymbol ?? $TRUE !! $FALSE }),
+ symbol => MalCode({ MalSymbol($^a.val) }),
+ keyword => MalCode({ $^a.val ~~ /^\x29E/ ?? $^a !! MalString("\x29E" ~ $^a.val) }),
+ 'keyword?' => MalCode({ $^a.val ~~ /^\x29E/ ?? $TRUE !! $FALSE }),
+ vector => MalCode({ MalVector(@_) }),
+ 'vector?' => MalCode({ $^a ~~ MalVector ?? $TRUE !! $FALSE }),
+ hash-map => MalCode({ MalHashMap(@_.map({ $^a.val => $^b }).Hash) }),
+ 'map?' => MalCode({ $^a ~~ MalHashMap ?? $TRUE !! $FALSE }),
+ assoc => MalCode(-> $map, *@kv { MalHashMap(Hash.new(|$map.kv, |@kv.map({$^a.val, $^b}))) }),
+ dissoc => MalCode(-> $map, *@keys { my %h = $map.val.clone; %h{@keys.map(*.val)}:delete; MalHashMap(%h) }),
+ get => MalCode({ $^a.val{$^b.val} // $NIL }),
+ 'contains?' => MalCode({ $^a.val{$^b.val}:exists ?? $TRUE !! $FALSE }),
+ keys => MalCode({ MalList([$^a.keys.map({ MalString($_) })]) }),
+ vals => MalCode({ MalList([$^a.values]) }),
+ 'sequential?' => MalCode({ $^a ~~ MalList|MalVector ?? $TRUE !! $FALSE }),
+ readline => MalCode({ with prompt($^a.val) { MalString($_) } else { $NIL } }),
+ time-ms => MalCode({ MalNumber((now * 1000).Int) }),
+ conj => MalCode(-> $seq, *@args { $seq.conj(@args) }),
+ 'string?' => MalCode({ $^a ~~ MalString && $^a.val !~~ /^\x29E/ ?? $TRUE !! $FALSE }),
+ seq => MalCode({ $^a.seq }),
+ with-meta => MalCode({ return $NIL if !$^a.can('meta'); my $x = $^a.clone; $x.meta = $^b; $x }),
+ meta => MalCode({ $^a.?meta // $NIL }),
+);
--- /dev/null
+unit class MalEnv;
+use types;
+
+has $.outer;
+has %.data;
+has @.binds;
+has @.exprs;
+
+method new ($outer?, @binds?, @exprs?) {
+ self.bless(:$outer, :@binds, :@exprs);
+}
+
+submethod BUILD (:@!binds, :@!exprs, :$!outer, :%!data) {
+ for @!binds.kv -> $idx, $key {
+ if $key eq '&' {
+ my $value = MalList([@!exprs[$idx..*]]);
+ self.set(@!binds[$idx+1], $value);
+ last;
+ }
+ my $value = @!exprs[$idx];
+ self.set($key, $value);
+ }
+}
+
+method set ($key, $value) {
+ %.data{$key} = $value;
+}
+
+method find ($key) {
+ return %.data{$key} ?? self !! $.outer && $.outer.find($key);
+}
+
+method get ($key) {
+ my $env = self.find($key) or die X::MalNotFound.new(name => $key);
+ return $env.data{$key};
+}
--- /dev/null
+unit module printer;
+use types;
+
+sub pr_str ($exp, $print_readably = False) is export {
+ given $exp {
+ when MalFunction { "#<fn* ({$exp.params}) {pr_str($exp.ast)}>" }
+ when MalCode { "#<builtin_fn* {$exp.fn.gist}>" }
+ when MalList {
+ '(' ~ join(' ', |$exp.map({ pr_str($_, $print_readably) })) ~ ')';
+ }
+ when MalVector {
+ '[' ~ join(' ', |$exp.map({ pr_str($_, $print_readably) })) ~ ']';
+ }
+ when MalHashMap {
+ '{' ~ $exp.kv.flatmap({ MalString($^a), $^b }).map({ pr_str($_, $print_readably) }) ~ '}'
+ }
+ when MalString {
+ my $str = $exp.val;
+ if $str ~~ s/^\x29E/:/ || !$print_readably {
+ $str;
+ }
+ else {
+ '"' ~ $str.trans(/\\/ => '\\\\', /\"/ => '\\"', /\n/ => '\\n') ~ '"';
+ }
+ }
+ when MalAtom { "(atom {pr_str($exp.val, $print_readably)})" }
+ when MalValue { $exp.val }
+ }
+}
--- /dev/null
+unit module reader;
+use types;
+
+class Reader {
+ has @.tokens;
+ has $!position = 0;
+ method peek { @.tokens[$!position] }
+ method next { @.tokens[$!position++] }
+}
+
+sub read_form ($rdr) {
+ given $rdr.peek {
+ when "'" { $rdr.next; MalList([MalSymbol('quote'), read_form($rdr)]) }
+ when '`' { $rdr.next; MalList([MalSymbol('quasiquote'), read_form($rdr)]) }
+ when '~' { $rdr.next; MalList([MalSymbol('unquote'), read_form($rdr)]) }
+ when '~@' { $rdr.next; MalList([MalSymbol('splice-unquote'), read_form($rdr)]) }
+ when '@' { $rdr.next; MalList([MalSymbol('deref'), read_form($rdr)]) }
+ when '^' {
+ $rdr.next;
+ my $meta = read_form($rdr);
+ MalList([MalSymbol('with-meta'), read_form($rdr), $meta]);
+ }
+ when ')'|']'|'}' { die X::MalUnexpected.new(token => $_) }
+ when '(' { MalList(read_list($rdr, ')')) }
+ when '[' { MalVector(read_list($rdr, ']')) }
+ when '{' { MalHashMap(read_list($rdr, '}').map({ $^a.val => $^b }).Hash) }
+ default { read_atom($rdr) }
+ }
+}
+
+sub read_list ($rdr, $end) {
+ my @list;
+ my $token = $rdr.next;
+
+ while ($token = $rdr.peek).defined {
+ last if $token eq $end;
+ die X::MalIncomplete.new(end => $end) if !$token.defined;
+ @list.push(read_form($rdr));
+ }
+ $rdr.next;
+
+ return @list;
+}
+
+sub read_atom ($rdr) {
+ my $atom = $rdr.next;
+ given $atom {
+ when /^\"/ {
+ die X::MalIncomplete.new(end => '"') if $atom !~~ /\"$/;
+ s:g/^\"|\"$//;
+ MalString(.trans(/\\\"/ => '"', /\\n/ => "\n", /\\\\/ => '\\'));
+ }
+ when /^\:(.*)/ { MalString("\x29E$0") }
+ when /^'-'? <[0..9]>+$/ { MalNumber($_) }
+ when 'nil' { $NIL }
+ when 'true' { $TRUE }
+ when 'false' { $FALSE }
+ default { MalSymbol($_) }
+ }
+}
+
+my regex mal {
+ [
+ <[\s,]>* # whitespace/commas
+ $<token>=(
+ || '~@' # ~@
+ || <[\[\]{}()'`~^@]> # special single-char tokens
+ || '"' [ \\. || <-[\"\\]> ]* '"'? # double-quoted strings
+ || ';'<-[\n]>* # comments
+ || <-[\s\[\]{}('"`,;)]>+ # symbols
+ )
+ ]+
+}
+
+sub tokenizer ($str) {
+ return [] if !$str.match(/^<mal>/);
+ return grep { ! /^\;/ }, $<mal><token>.map({~$_});
+}
+
+sub read_str ($str) is export {
+ my @tokens = tokenizer($str);
+ die X::MalNoTokens.new if !@tokens;
+ return read_form(Reader.new(tokens => @tokens));
+}
--- /dev/null
+#!/bin/bash
+exec perl6 $(dirname $0)/${STEP:-stepA_mal}.pl "${@}"
--- /dev/null
+use v6;
+#use Linenoise;
+
+sub read ($str) {
+ return $str;
+}
+
+sub eval ($ast) {
+ return $ast;
+}
+
+sub print ($exp) {
+ return $exp;
+}
+
+sub rep ($str) {
+ return print(eval(read($str)));
+}
+
+sub MAIN {
+ #while (my $line = linenoise('user> ')).defined {
+ # say rep($line);
+ #}
+ while (my $line = prompt 'user> ').defined {
+ say rep($line);
+ }
+}
--- /dev/null
+use v6;
+use lib IO::Path.new($?FILE).dirname;
+use reader;
+use printer;
+use types;
+
+sub read ($str) {
+ return read_str($str);
+}
+
+sub eval ($ast) {
+ return $ast;
+}
+
+sub print ($exp) {
+ return pr_str($exp, True);
+}
+
+sub rep ($str) {
+ return print(eval(read($str)));
+}
+
+sub MAIN {
+ while (my $line = prompt 'user> ').defined {
+ say rep($line);
+ CATCH {
+ when X::MalException { .Str.say }
+ }
+ }
+}
--- /dev/null
+use v6;
+use lib IO::Path.new($?FILE).dirname;
+use reader;
+use printer;
+use types;
+
+sub read ($str) {
+ return read_str($str);
+}
+
+sub eval_ast ($ast, $env) {
+ given $ast {
+ when MalSymbol { $env{$ast.val} || die X::MalNotFound.new(name => $ast.val) }
+ when MalList { MalList([$ast.map({ eval($_, $env) })]) }
+ when MalVector { MalVector([$ast.map({ eval($_, $env) })]) }
+ when MalHashMap { MalHashMap($ast.kv.map({ $^a => eval($^b, $env) }).Hash) }
+ default { $ast // $NIL }
+ }
+}
+
+sub eval ($ast, $env) {
+ return eval_ast($ast, $env) if $ast !~~ MalList;
+ return $ast if !$ast.elems;
+
+ my ($func, @args) = eval_ast($ast, $env).val;
+ my $arglist = MalList(@args);
+ return $func.apply($arglist);
+}
+
+sub print ($exp) {
+ return pr_str($exp, True);
+}
+
+my $repl_env;
+
+sub rep ($str) {
+ return print(eval(read($str), $repl_env));
+}
+
+sub MAIN {
+ $repl_env<+> = MalCode({ MalNumber($^a[0].val + $^a[1].val) });
+ $repl_env<-> = MalCode({ MalNumber($^a[0].val - $^a[1].val) });
+ $repl_env<*> = MalCode({ MalNumber($^a[0].val * $^a[1].val) });
+ $repl_env</> = MalCode({ MalNumber(($^a[0].val / $^a[1].val).Int) });
+
+ while (my $line = prompt 'user> ').defined {
+ say rep($line);
+ CATCH {
+ when X::MalException { .Str.say }
+ }
+ }
+}
--- /dev/null
+use v6;
+use lib IO::Path.new($?FILE).dirname;
+use reader;
+use printer;
+use types;
+use env;
+
+sub read ($str) {
+ return read_str($str);
+}
+
+sub eval_ast ($ast, $env) {
+ given $ast {
+ when MalSymbol { $env.get($ast.val) || die X::MalNotFound.new(name => $ast.val) }
+ when MalList { MalList([$ast.map({ eval($_, $env) })]) }
+ when MalVector { MalVector([$ast.map({ eval($_, $env) })]) }
+ when MalHashMap { MalHashMap($ast.kv.map({ $^a => eval($^b, $env) }).Hash) }
+ default { $ast // $NIL }
+ }
+}
+
+sub eval ($ast, $env) {
+ return eval_ast($ast, $env) if $ast !~~ MalList;
+ return $ast if !$ast.elems;
+
+ my ($a0, $a1, $a2, $a3) = $ast.val;
+ given $a0.val {
+ when 'def!' {
+ return $env.set($a1.val, eval($a2, $env));
+ }
+ when 'let*' {
+ my $new_env = MalEnv.new($env);
+ for |$a1.val -> $key, $value {
+ $new_env.set($key.val, eval($value, $new_env));
+ }
+ return eval($a2, $new_env);
+ }
+ default {
+ my ($func, @args) = eval_ast($ast, $env).val;
+ return $func.apply(|@args);
+ }
+ }
+}
+
+sub print ($exp) {
+ return pr_str($exp, True);
+}
+
+my $repl_env = MalEnv.new;
+
+sub rep ($str) {
+ return print(eval(read($str), $repl_env));
+}
+
+sub MAIN {
+ $repl_env.set('+', MalCode({ MalNumber($^a.val + $^b.val) }));
+ $repl_env.set('-', MalCode({ MalNumber($^a.val - $^b.val) }));
+ $repl_env.set('*', MalCode({ MalNumber($^a.val * $^b.val) }));
+ $repl_env.set('/', MalCode({ MalNumber(($^a.val / $^b.val).Int) }));
+
+ while (my $line = prompt 'user> ').defined {
+ say rep($line);
+ CATCH {
+ when X::MalException { .Str.say }
+ }
+ }
+}
--- /dev/null
+use v6;
+use lib IO::Path.new($?FILE).dirname;
+use reader;
+use printer;
+use types;
+use env;
+use core;
+
+sub read ($str) {
+ return read_str($str);
+}
+
+sub eval_ast ($ast, $env) {
+ given $ast {
+ when MalSymbol { $env.get($ast.val) || die X::MalNotFound.new(name => $ast.val) }
+ when MalList { MalList([$ast.map({ eval($_, $env) })]) }
+ when MalVector { MalVector([$ast.map({ eval($_, $env) })]) }
+ when MalHashMap { MalHashMap($ast.kv.map({ $^a => eval($^b, $env) }).Hash) }
+ default { $ast // $NIL }
+ }
+}
+
+sub eval ($ast, $env) {
+ return eval_ast($ast, $env) if $ast !~~ MalList;
+ return $ast if !$ast.elems;
+
+ my ($a0, $a1, $a2, $a3) = $ast.val;
+ given $a0.val {
+ when 'def!' {
+ return $env.set($a1.val, eval($a2, $env));
+ }
+ when 'let*' {
+ my $new_env = MalEnv.new($env);
+ for |$a1.val -> $key, $value {
+ $new_env.set($key.val, eval($value, $new_env));
+ }
+ return eval($a2, $new_env);
+ }
+ when 'do' {
+ return eval_ast(MalList([$ast[1..*]]), $env)[*-1];
+ }
+ when 'if' {
+ return eval($a1, $env) !~~ MalNil|MalFalse
+ ?? return eval($a2, $env)
+ !! return $a3 ?? eval($a3, $env) !! $NIL;
+ }
+ when 'fn*' {
+ return MalCode(-> *@args {
+ my @binds = $a1 ?? $a1.map(*.val) !! ();
+ eval($a2, MalEnv.new($env, @binds, @args));
+ });
+ }
+ default {
+ my ($func, @args) = eval_ast($ast, $env).val;
+ return $func.apply(|@args);
+ }
+ }
+}
+
+sub print ($exp) {
+ return pr_str($exp, True);
+}
+
+my $repl_env = MalEnv.new;
+
+sub rep ($str) {
+ return print(eval(read($str), $repl_env));
+}
+
+sub MAIN {
+ $repl_env.set(.key, .value) for %core::ns;
+ rep(q{(def! not (fn* (a) (if a false true)))});
+
+ while (my $line = prompt 'user> ').defined {
+ say rep($line);
+ CATCH {
+ when X::MalException { .Str.say }
+ }
+ }
+}
--- /dev/null
+use v6;
+use lib IO::Path.new($?FILE).dirname;
+use reader;
+use printer;
+use types;
+use env;
+use core;
+
+sub read ($str) {
+ return read_str($str);
+}
+
+sub eval_ast ($ast, $env) {
+ given $ast {
+ when MalSymbol { $env.get($ast.val) || die X::MalNotFound.new(name => $ast.val) }
+ when MalList { MalList([$ast.map({ eval($_, $env) })]) }
+ when MalVector { MalVector([$ast.map({ eval($_, $env) })]) }
+ when MalHashMap { MalHashMap($ast.kv.map({ $^a => eval($^b, $env) }).Hash) }
+ default { $ast // $NIL }
+ }
+}
+
+sub eval ($ast is copy, $env is copy) {
+ loop {
+ return eval_ast($ast, $env) if $ast !~~ MalList;
+ return $ast if !$ast.elems;
+
+ my ($a0, $a1, $a2, $a3) = $ast.val;
+ given $a0.val {
+ when 'def!' {
+ return $env.set($a1.val, eval($a2, $env));
+ }
+ when 'let*' {
+ my $new_env = MalEnv.new($env);
+ for |$a1.val -> $key, $value {
+ $new_env.set($key.val, eval($value, $new_env));
+ }
+ $env = $new_env;
+ $ast = $a2;
+ }
+ when 'do' {
+ eval_ast(MalList([$ast[1..*-2]]), $env);
+ $ast = $ast[*-1];
+ }
+ when 'if' {
+ if eval($a1, $env) ~~ MalNil|MalFalse {
+ return $NIL if $a3 ~~ $NIL;
+ $ast = $a3;
+ }
+ else {
+ $ast = $a2;
+ }
+ }
+ when 'fn*' {
+ my @binds = $a1 ?? $a1.map(*.val) !! ();
+ my &fn = -> *@args {
+ eval($a2, MalEnv.new($env, @binds, @args));
+ };
+ return MalFunction($a2, $env, @binds, &fn);
+ }
+ default {
+ my ($func, @args) = eval_ast($ast, $env).val;
+ return $func.apply(|@args) if $func !~~ MalFunction;
+ $ast = $func.ast;
+ $env = MalEnv.new($func.env, $func.params, @args);
+ }
+ }
+ }
+}
+
+sub print ($exp) {
+ return pr_str($exp, True);
+}
+
+my $repl_env = MalEnv.new;
+
+sub rep ($str) {
+ return print(eval(read($str), $repl_env));
+}
+
+sub MAIN {
+ $repl_env.set(.key, .value) for %core::ns;
+ rep(q{(def! not (fn* (a) (if a false true)))});
+
+ while (my $line = prompt 'user> ').defined {
+ say rep($line);
+ CATCH {
+ when X::MalException { .Str.say }
+ }
+ }
+}
--- /dev/null
+use v6;
+use lib IO::Path.new($?FILE).dirname;
+use reader;
+use printer;
+use types;
+use env;
+use core;
+
+sub read ($str) {
+ return read_str($str);
+}
+
+sub eval_ast ($ast, $env) {
+ given $ast {
+ when MalSymbol { $env.get($ast.val) || die X::MalNotFound.new(name => $ast.val) }
+ when MalList { MalList([$ast.map({ eval($_, $env) })]) }
+ when MalVector { MalVector([$ast.map({ eval($_, $env) })]) }
+ when MalHashMap { MalHashMap($ast.kv.map({ $^a => eval($^b, $env) }).Hash) }
+ default { $ast // $NIL }
+ }
+}
+
+sub eval ($ast is copy, $env is copy) {
+ loop {
+ return eval_ast($ast, $env) if $ast !~~ MalList;
+ return $ast if !$ast.elems;
+
+ my ($a0, $a1, $a2, $a3) = $ast.val;
+ given $a0.val {
+ when 'def!' {
+ return $env.set($a1.val, eval($a2, $env));
+ }
+ when 'let*' {
+ my $new_env = MalEnv.new($env);
+ for |$a1.val -> $key, $value {
+ $new_env.set($key.val, eval($value, $new_env));
+ }
+ $env = $new_env;
+ $ast = $a2;
+ }
+ when 'do' {
+ eval_ast(MalList([$ast[1..*-2]]), $env);
+ $ast = $ast[*-1];
+ }
+ when 'if' {
+ if eval($a1, $env) ~~ MalNil|MalFalse {
+ return $NIL if $a3 ~~ $NIL;
+ $ast = $a3;
+ }
+ else {
+ $ast = $a2;
+ }
+ }
+ when 'fn*' {
+ my @binds = $a1 ?? $a1.map(*.val) !! ();
+ my &fn = -> *@args {
+ eval($a2, MalEnv.new($env, @binds, @args));
+ };
+ return MalFunction($a2, $env, @binds, &fn);
+ }
+ default {
+ my ($func, @args) = eval_ast($ast, $env).val;
+ return $func.apply(|@args) if $func !~~ MalFunction;
+ $ast = $func.ast;
+ $env = MalEnv.new($func.env, $func.params, @args);
+ }
+ }
+ }
+}
+
+sub print ($exp) {
+ return pr_str($exp, True);
+}
+
+my $repl_env = MalEnv.new;
+
+sub rep ($str) {
+ return print(eval(read($str), $repl_env));
+}
+
+sub MAIN ($source_file?, *@args) {
+ $repl_env.set(.key, .value) for %core::ns;
+ $repl_env.set('eval', MalCode({ eval($^a, $repl_env) }));
+ $repl_env.set('*ARGV*', MalList([@args.map({ MalString($_) })]));
+ rep(q{(def! not (fn* (a) (if a false true)))});
+ rep(q{(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))});
+
+ if ($source_file.defined) {
+ rep("(load-file \"$source_file\")");
+ exit;
+ }
+
+ while (my $line = prompt 'user> ').defined {
+ say rep($line);
+ CATCH {
+ when X::MalException { .Str.say }
+ }
+ }
+}
--- /dev/null
+use v6;
+use lib IO::Path.new($?FILE).dirname;
+use reader;
+use printer;
+use types;
+use env;
+use core;
+
+sub read ($str) {
+ return read_str($str);
+}
+
+sub eval_ast ($ast, $env) {
+ given $ast {
+ when MalSymbol { $env.get($ast.val) || die X::MalNotFound.new(name => $ast.val) }
+ when MalList { MalList([$ast.map({ eval($_, $env) })]) }
+ when MalVector { MalVector([$ast.map({ eval($_, $env) })]) }
+ when MalHashMap { MalHashMap($ast.kv.map({ $^a => eval($^b, $env) }).Hash) }
+ default { $ast // $NIL }
+ }
+}
+
+sub is_pair ($ast) {
+ return so $ast ~~ MalList|MalVector && $ast.elems;
+}
+
+sub quasiquote ($ast) {
+ if !is_pair($ast) {
+ return MalList([MalSymbol('quote'), $ast]);
+ }
+ elsif $ast[0] ~~ MalSymbol && $ast[0].val eq 'unquote' {
+ return $ast[1];
+ }
+ elsif is_pair($ast[0]) && $ast[0][0] ~~ MalSymbol && $ast[0][0].val eq 'splice-unquote' {
+ return MalList([MalSymbol('concat'), $ast[0][1], quasiquote(MalList([$ast[1..*]]))]);
+ }
+ else {
+ return MalList([MalSymbol('cons'), quasiquote($ast[0]), quasiquote(MalList([$ast[1..*]]))]);
+ }
+}
+
+sub eval ($ast is copy, $env is copy) {
+ loop {
+ return eval_ast($ast, $env) if $ast !~~ MalList;
+ return $ast if !$ast.elems;
+
+ my ($a0, $a1, $a2, $a3) = $ast.val;
+ given $a0.val {
+ when 'def!' {
+ return $env.set($a1.val, eval($a2, $env));
+ }
+ when 'let*' {
+ my $new_env = MalEnv.new($env);
+ for |$a1.val -> $key, $value {
+ $new_env.set($key.val, eval($value, $new_env));
+ }
+ $env = $new_env;
+ $ast = $a2;
+ }
+ when 'do' {
+ eval_ast(MalList([$ast[1..*-2]]), $env);
+ $ast = $ast[*-1];
+ }
+ when 'if' {
+ if eval($a1, $env) ~~ MalNil|MalFalse {
+ return $NIL if $a3 ~~ $NIL;
+ $ast = $a3;
+ }
+ else {
+ $ast = $a2;
+ }
+ }
+ when 'fn*' {
+ my @binds = $a1 ?? $a1.map(*.val) !! ();
+ my &fn = -> *@args {
+ eval($a2, MalEnv.new($env, @binds, @args));
+ };
+ return MalFunction($a2, $env, @binds, &fn);
+ }
+ when 'quote' { return $a1 }
+ when 'quasiquote' { $ast = quasiquote($a1) }
+ default {
+ my ($func, @args) = eval_ast($ast, $env).val;
+ return $func.apply(|@args) if $func !~~ MalFunction;
+ $ast = $func.ast;
+ $env = MalEnv.new($func.env, $func.params, @args);
+ }
+ }
+ }
+}
+
+sub print ($exp) {
+ return pr_str($exp, True);
+}
+
+my $repl_env = MalEnv.new;
+
+sub rep ($str) {
+ return print(eval(read($str), $repl_env));
+}
+
+sub MAIN ($source_file?, *@args) {
+ $repl_env.set(.key, .value) for %core::ns;
+ $repl_env.set('eval', MalCode({ eval($^a, $repl_env) }));
+ $repl_env.set('*ARGV*', MalList([@args.map({ MalString($_) })]));
+ rep(q{(def! not (fn* (a) (if a false true)))});
+ rep(q{(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))});
+
+ if ($source_file.defined) {
+ rep("(load-file \"$source_file\")");
+ exit;
+ }
+
+ while (my $line = prompt 'user> ').defined {
+ say rep($line);
+ CATCH {
+ when X::MalException { .Str.say }
+ }
+ }
+}
--- /dev/null
+use v6;
+use lib IO::Path.new($?FILE).dirname;
+use reader;
+use printer;
+use types;
+use env;
+use core;
+
+sub read ($str) {
+ return read_str($str);
+}
+
+sub eval_ast ($ast, $env) {
+ given $ast {
+ when MalSymbol { $env.get($ast.val) || die X::MalNotFound.new(name => $ast.val) }
+ when MalList { MalList([$ast.map({ eval($_, $env) })]) }
+ when MalVector { MalVector([$ast.map({ eval($_, $env) })]) }
+ when MalHashMap { MalHashMap($ast.kv.map({ $^a => eval($^b, $env) }).Hash) }
+ default { $ast // $NIL }
+ }
+}
+
+sub is_pair ($ast) {
+ return so $ast ~~ MalList|MalVector && $ast.elems;
+}
+
+sub quasiquote ($ast) {
+ if !is_pair($ast) {
+ return MalList([MalSymbol('quote'), $ast]);
+ }
+ elsif $ast[0] ~~ MalSymbol && $ast[0].val eq 'unquote' {
+ return $ast[1];
+ }
+ elsif is_pair($ast[0]) && $ast[0][0] ~~ MalSymbol && $ast[0][0].val eq 'splice-unquote' {
+ return MalList([MalSymbol('concat'), $ast[0][1], quasiquote(MalList([$ast[1..*]]))]);
+ }
+ else {
+ return MalList([MalSymbol('cons'), quasiquote($ast[0]), quasiquote(MalList([$ast[1..*]]))]);
+ }
+}
+
+sub is_macro_call ($ast, $env) {
+ return so $ast ~~ MalList && $ast[0] ~~ MalSymbol
+ && $env.find($ast[0].val).?get($ast[0].val).?is_macro;
+}
+
+sub macroexpand ($ast is copy, $env is copy) {
+ while is_macro_call($ast, $env) {
+ my $func = $env.get($ast[0].val);
+ $ast = $func.apply($ast[1..*]);
+ }
+ return $ast;
+}
+
+sub eval ($ast is copy, $env is copy) {
+ loop {
+ return eval_ast($ast, $env) if $ast !~~ MalList;
+ $ast = macroexpand($ast, $env);
+ return eval_ast($ast, $env) if $ast !~~ MalList;
+ return $ast if !$ast.elems;
+
+ my ($a0, $a1, $a2, $a3) = $ast.val;
+ given $a0.val {
+ when 'def!' {
+ return $env.set($a1.val, eval($a2, $env));
+ }
+ when 'let*' {
+ my $new_env = MalEnv.new($env);
+ for |$a1.val -> $key, $value {
+ $new_env.set($key.val, eval($value, $new_env));
+ }
+ $env = $new_env;
+ $ast = $a2;
+ }
+ when 'do' {
+ eval_ast(MalList([$ast[1..*-2]]), $env);
+ $ast = $ast[*-1];
+ }
+ when 'if' {
+ if eval($a1, $env) ~~ MalNil|MalFalse {
+ return $NIL if $a3 ~~ $NIL;
+ $ast = $a3;
+ }
+ else {
+ $ast = $a2;
+ }
+ }
+ when 'fn*' {
+ my @binds = $a1 ?? $a1.map(*.val) !! ();
+ my &fn = -> *@args {
+ eval($a2, MalEnv.new($env, @binds, @args));
+ };
+ return MalFunction($a2, $env, @binds, &fn);
+ }
+ when 'quote' { return $a1 }
+ when 'quasiquote' { $ast = quasiquote($a1) }
+ when 'defmacro!' {
+ my $func = eval($a2, $env);
+ $func.is_macro = True;
+ return $env.set($a1.val, $func);
+ }
+ when 'macroexpand' { return macroexpand($a1, $env) }
+ default {
+ my ($func, @args) = eval_ast($ast, $env).val;
+ return $func.apply(|@args) if $func !~~ MalFunction;
+ $ast = $func.ast;
+ $env = MalEnv.new($func.env, $func.params, @args);
+ }
+ }
+ }
+}
+
+sub print ($exp) {
+ return pr_str($exp, True);
+}
+
+my $repl_env = MalEnv.new;
+
+sub rep ($str) {
+ return print(eval(read($str), $repl_env));
+}
+
+sub MAIN ($source_file?, *@args) {
+ $repl_env.set(.key, .value) for %core::ns;
+ $repl_env.set('eval', MalCode({ eval($^a, $repl_env) }));
+ $repl_env.set('*ARGV*', MalList([@args.map({ MalString($_) })]));
+ rep(q{(def! not (fn* (a) (if a false true)))});
+ rep(q{(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))});
+ rep(q{(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)))))))});
+ rep(q{(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))});
+
+ if ($source_file.defined) {
+ rep("(load-file \"$source_file\")");
+ exit;
+ }
+
+ while (my $line = prompt 'user> ').defined {
+ say rep($line);
+ CATCH {
+ when X::MalException { .Str.say }
+ }
+ }
+}
--- /dev/null
+use v6;
+use lib IO::Path.new($?FILE).dirname;
+use reader;
+use printer;
+use types;
+use env;
+use core;
+
+sub read ($str) {
+ return read_str($str);
+}
+
+sub eval_ast ($ast, $env) {
+ given $ast {
+ when MalSymbol { $env.get($ast.val) || die X::MalNotFound.new(name => $ast.val) }
+ when MalList { MalList([$ast.map({ eval($_, $env) })]) }
+ when MalVector { MalVector([$ast.map({ eval($_, $env) })]) }
+ when MalHashMap { MalHashMap($ast.kv.map({ $^a => eval($^b, $env) }).Hash) }
+ default { $ast // $NIL }
+ }
+}
+
+sub is_pair ($ast) {
+ return so $ast ~~ MalList|MalVector && $ast.elems;
+}
+
+sub quasiquote ($ast) {
+ if !is_pair($ast) {
+ return MalList([MalSymbol('quote'), $ast]);
+ }
+ elsif $ast[0] ~~ MalSymbol && $ast[0].val eq 'unquote' {
+ return $ast[1];
+ }
+ elsif is_pair($ast[0]) && $ast[0][0] ~~ MalSymbol && $ast[0][0].val eq 'splice-unquote' {
+ return MalList([MalSymbol('concat'), $ast[0][1], quasiquote(MalList([$ast[1..*]]))]);
+ }
+ else {
+ return MalList([MalSymbol('cons'), quasiquote($ast[0]), quasiquote(MalList([$ast[1..*]]))]);
+ }
+}
+
+sub is_macro_call ($ast, $env) {
+ return so $ast ~~ MalList && $ast[0] ~~ MalSymbol
+ && $env.find($ast[0].val).?get($ast[0].val).?is_macro;
+}
+
+sub macroexpand ($ast is copy, $env is copy) {
+ while is_macro_call($ast, $env) {
+ my $func = $env.get($ast[0].val);
+ $ast = $func.apply($ast[1..*]);
+ }
+ return $ast;
+}
+
+sub eval ($ast is copy, $env is copy) {
+ loop {
+ return eval_ast($ast, $env) if $ast !~~ MalList;
+ $ast = macroexpand($ast, $env);
+ return eval_ast($ast, $env) if $ast !~~ MalList;
+ return $ast if !$ast.elems;
+
+ my ($a0, $a1, $a2, $a3) = $ast.val;
+ given $a0.val {
+ when 'def!' {
+ return $env.set($a1.val, eval($a2, $env));
+ }
+ when 'let*' {
+ my $new_env = MalEnv.new($env);
+ for |$a1.val -> $key, $value {
+ $new_env.set($key.val, eval($value, $new_env));
+ }
+ $env = $new_env;
+ $ast = $a2;
+ }
+ when 'do' {
+ eval_ast(MalList([$ast[1..*-2]]), $env);
+ $ast = $ast[*-1];
+ }
+ when 'if' {
+ if eval($a1, $env) ~~ MalNil|MalFalse {
+ return $NIL if $a3 ~~ $NIL;
+ $ast = $a3;
+ }
+ else {
+ $ast = $a2;
+ }
+ }
+ when 'fn*' {
+ my @binds = $a1 ?? $a1.map(*.val) !! ();
+ my &fn = -> *@args {
+ eval($a2, MalEnv.new($env, @binds, @args));
+ };
+ return MalFunction($a2, $env, @binds, &fn);
+ }
+ when 'quote' { return $a1 }
+ when 'quasiquote' { $ast = quasiquote($a1) }
+ when 'defmacro!' {
+ my $func = eval($a2, $env);
+ $func.is_macro = True;
+ return $env.set($a1.val, $func);
+ }
+ when 'macroexpand' { return macroexpand($a1, $env) }
+ when 'try*' {
+ return eval($a1, $env);
+ CATCH {
+ my $ex = $_ ~~ X::MalThrow ?? .value !! MalString(.Str);
+ my $new_env = $env;
+ $env.set($a2[1].val, $ex);
+ return eval($a2[2], $new_env);
+ }
+ }
+ default {
+ my ($func, @args) = eval_ast($ast, $env).val;
+ return $func.apply(|@args) if $func !~~ MalFunction;
+ $ast = $func.ast;
+ $env = MalEnv.new($func.env, $func.params, @args);
+ }
+ }
+ }
+}
+
+sub print ($exp) {
+ return pr_str($exp, True);
+}
+
+my $repl_env = MalEnv.new;
+
+sub rep ($str) {
+ return print(eval(read($str), $repl_env));
+}
+
+sub MAIN ($source_file?, *@args) {
+ $repl_env.set(.key, .value) for %core::ns;
+ $repl_env.set('eval', MalCode({ eval($^a, $repl_env) }));
+ $repl_env.set('*ARGV*', MalList([@args.map({ MalString($_) })]));
+ rep(q{(def! not (fn* (a) (if a false true)))});
+ rep(q{(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))});
+ rep(q{(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)))))))});
+ rep(q{(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))});
+
+ if ($source_file.defined) {
+ rep("(load-file \"$source_file\")");
+ exit;
+ }
+
+ while (my $line = prompt 'user> ').defined {
+ say rep($line);
+ CATCH {
+ when X::MalException { .Str.say }
+ }
+ }
+}
--- /dev/null
+use v6;
+use lib IO::Path.new($?FILE).dirname;
+use reader;
+use printer;
+use types;
+use env;
+use core;
+
+sub read ($str) {
+ return read_str($str);
+}
+
+sub eval_ast ($ast, $env) {
+ given $ast {
+ when MalSymbol { $env.get($ast.val) || die X::MalNotFound.new(name => $ast.val) }
+ when MalList { MalList([$ast.map({ eval($_, $env) })]) }
+ when MalVector { MalVector([$ast.map({ eval($_, $env) })]) }
+ when MalHashMap { MalHashMap($ast.kv.map({ $^a => eval($^b, $env) }).Hash) }
+ default { $ast // $NIL }
+ }
+}
+
+sub is_pair ($ast) {
+ return so $ast ~~ MalList|MalVector && $ast.elems;
+}
+
+sub quasiquote ($ast) {
+ if !is_pair($ast) {
+ return MalList([MalSymbol('quote'), $ast]);
+ }
+ elsif $ast[0] ~~ MalSymbol && $ast[0].val eq 'unquote' {
+ return $ast[1];
+ }
+ elsif is_pair($ast[0]) && $ast[0][0] ~~ MalSymbol && $ast[0][0].val eq 'splice-unquote' {
+ return MalList([MalSymbol('concat'), $ast[0][1], quasiquote(MalList([$ast[1..*]]))]);
+ }
+ else {
+ return MalList([MalSymbol('cons'), quasiquote($ast[0]), quasiquote(MalList([$ast[1..*]]))]);
+ }
+}
+
+sub is_macro_call ($ast, $env) {
+ return so $ast ~~ MalList && $ast[0] ~~ MalSymbol
+ && $env.find($ast[0].val).?get($ast[0].val).?is_macro;
+}
+
+sub macroexpand ($ast is copy, $env is copy) {
+ while is_macro_call($ast, $env) {
+ my $func = $env.get($ast[0].val);
+ $ast = $func.apply($ast[1..*]);
+ }
+ return $ast;
+}
+
+sub eval ($ast is copy, $env is copy) {
+ loop {
+ return eval_ast($ast, $env) if $ast !~~ MalList;
+ $ast = macroexpand($ast, $env);
+ return eval_ast($ast, $env) if $ast !~~ MalList;
+ return $ast if !$ast.elems;
+
+ my ($a0, $a1, $a2, $a3) = $ast.val;
+ given $a0.val {
+ when 'def!' {
+ return $env.set($a1.val, eval($a2, $env));
+ }
+ when 'let*' {
+ my $new_env = MalEnv.new($env);
+ for |$a1.val -> $key, $value {
+ $new_env.set($key.val, eval($value, $new_env));
+ }
+ $env = $new_env;
+ $ast = $a2;
+ }
+ when 'do' {
+ eval_ast(MalList([$ast[1..*-2]]), $env);
+ $ast = $ast[*-1];
+ }
+ when 'if' {
+ if eval($a1, $env) ~~ MalNil|MalFalse {
+ return $NIL if $a3 ~~ $NIL;
+ $ast = $a3;
+ }
+ else {
+ $ast = $a2;
+ }
+ }
+ when 'fn*' {
+ my @binds = $a1 ?? $a1.map(*.val) !! ();
+ my &fn = -> *@args {
+ eval($a2, MalEnv.new($env, @binds, @args));
+ };
+ return MalFunction($a2, $env, @binds, &fn);
+ }
+ when 'quote' { return $a1 }
+ when 'quasiquote' { $ast = quasiquote($a1) }
+ when 'defmacro!' {
+ my $func = eval($a2, $env);
+ $func.is_macro = True;
+ return $env.set($a1.val, $func);
+ }
+ when 'macroexpand' { return macroexpand($a1, $env) }
+ when 'try*' {
+ return eval($a1, $env);
+ CATCH {
+ my $ex = $_ ~~ X::MalThrow ?? .value !! MalString(.Str);
+ my $new_env = $env;
+ $env.set($a2[1].val, $ex);
+ return eval($a2[2], $new_env);
+ }
+ }
+ default {
+ my ($func, @args) = eval_ast($ast, $env).val;
+ return $func.apply(|@args) if $func !~~ MalFunction;
+ $ast = $func.ast;
+ $env = MalEnv.new($func.env, $func.params, @args);
+ }
+ }
+ }
+}
+
+sub print ($exp) {
+ return pr_str($exp, True);
+}
+
+my $repl_env = MalEnv.new;
+
+sub rep ($str) {
+ return print(eval(read($str), $repl_env));
+}
+
+sub MAIN ($source_file?, *@args) {
+ $repl_env.set(.key, .value) for %core::ns;
+ $repl_env.set('eval', MalCode({ eval($^a, $repl_env) }));
+ $repl_env.set('*ARGV*', MalList([@args.map({ MalString($_) })]));
+ $repl_env.set('*host-language*', MalString('perl6'));
+ rep(q{(def! not (fn* (a) (if a false true)))});
+ rep(q{(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))});
+ rep(q{(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)))))))});
+ rep(q{(def! *gensym-counter* (atom 0))});
+ rep(q{(def! gensym (fn* [] (symbol (str "G__" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))});
+ rep(q{(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))});
+
+ if ($source_file.defined) {
+ rep("(load-file \"$source_file\")");
+ exit;
+ }
+ rep(q{(println (str "Mal [" *host-language* "]"))});
+
+ while (my $line = prompt 'user> ').defined {
+ say rep($line);
+ CATCH {
+ when X::MalException { .Str.say }
+ }
+ }
+}
--- /dev/null
+unit module types;
+
+class X::MalException is Exception is export {}
+class X::MalNoTokens is X::MalException is export {
+ method message() { "got no tokens" }
+}
+class X::MalIncomplete is X::MalException is export {
+ has $.end;
+ method message() { "expected '$.end', got EOF" }
+}
+class X::MalUnexpected is X::MalException is export {
+ has $.token;
+ method message() { "unexpected '$.token'" }
+}
+class X::MalNotFound is X::MalException is export {
+ has $.name;
+ method message() { "'$.name' not found" }
+}
+class X::MalOutOfRange is X::MalException is export {
+ method message() { "nth: index out of range" }
+}
+class X::MalThrow is X::MalException is export {
+ has $.value;
+}
+
+role MalValue is export {
+ has $.val is rw;
+ method CALL-ME ($val) { self.new(:$val) }
+}
+role MalSequence is export {
+ has $.val handles <cache AT-POS EXISTS-POS elems end iterator>;
+ has $.meta is rw;
+ method CALL-ME ($val) { self.new(:$val) }
+}
+role MalCallable is export {
+ has &.fn;
+ method apply (*@_) { &!fn(|@_) }
+}
+role MalMeta is export {
+ has $.meta is rw;
+}
+
+class MalNil does MalValue is export {
+ method seq { self }
+}
+class MalTrue does MalValue is export {}
+class MalFalse does MalValue is export {}
+
+our $NIL is export = MalNil('nil');
+our $TRUE is export = MalTrue('true');
+our $FALSE is export = MalFalse('false');
+
+class MalSymbol does MalValue does MalMeta is export {}
+
+class MalList does MalSequence is export {
+ method conj (@args) { return self.new(val => [|@args.reverse, |$.val]) }
+ method seq { return self.elems ?? self !! $NIL }
+}
+
+class MalVector does MalSequence is export {
+ method conj (@args) { return self.new(val => [|$.val, |@args]) }
+ method seq { return self.elems ?? MalList(self.val) !! $NIL }
+}
+
+class MalHashMap does MalMeta is export {
+ has $.val handles <cache AT-KEY EXISTS-KEY elems pairs keys values kv>;
+ method CALL-ME ($val) { self.new(:$val) }
+}
+
+class MalNumber does MalValue is export {}
+
+class MalString does MalValue is export {
+ method seq {
+ return self.val.chars
+ ?? MalList(self.val.comb.map({MalString($_)}))
+ !! $NIL;
+ }
+}
+
+class MalCode does MalCallable does MalMeta is export {
+ method CALL-ME (&fn) { self.new(:&fn) }
+}
+
+class MalFunction does MalCallable does MalMeta is export {
+ has $.ast;
+ has @.params;
+ has $.env;
+ has $.is_macro is rw = False;
+ method CALL-ME ($ast, $env, @params, &fn) {
+ self.bless(:$ast, :$env, :@params, :&fn);
+ }
+}
+
+class MalAtom does MalValue does MalMeta is export {}