1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-11 13:55:55 +03:00

Tcl implementation

This commit is contained in:
Dov Murik 2015-07-09 11:14:16 -04:00
parent 595376919b
commit 54d9903c0c
22 changed files with 2725 additions and 2 deletions

View File

@ -18,7 +18,7 @@ mal_TEST_OPTS = --start-timeout 60 --test-timeout 120
IMPLS = awk bash c clojure coffee cpp crystal cs erlang elixir es6 factor forth fsharp go groovy \
guile haskell java julia js kotlin lua make mal ocaml matlab miniMAL nim \
perl php ps python r racket rpython ruby rust scala swift vb vimscript
perl php ps python r racket rpython ruby rust scala swift tcl vb vimscript
step0 = step0_repl
step1 = step1_read_print
@ -101,6 +101,7 @@ ruby_STEP_TO_PROG = ruby/$($(1)).rb
rust_STEP_TO_PROG = rust/target/release/$($(1))
scala_STEP_TO_PROG = scala/$($(1)).scala
swift_STEP_TO_PROG = swift/$($(1))
tcl_STEP_TO_PROG = tcl/$($(1)).tcl
vb_STEP_TO_PROG = vb/$($(1)).exe
vimscript_STEP_TO_PROG = vimscript/$($(1)).vim
guile_STEP_TO_PROG = guile/$($(1)).scm
@ -151,6 +152,7 @@ ruby_RUNSTEP = ruby ../$(2) $(3)
rust_RUNSTEP = ../$(2) $(3)
scala_RUNSTEP = sbt 'run-main $($(1))$(if $(3), $(3),)'
swift_RUNSTEP = ../$(2) $(3)
tcl_RUNSTEP = tclsh ../$(2) $(3)
vb_RUNSTEP = mono ../$(2) --raw $(3)
vimscript_RUNSTEP = ./run_vimscript.sh ../$(2) $(3)
# needs TERM=dumb to work with readline

View File

@ -6,7 +6,7 @@
Mal is a Clojure inspired Lisp interpreter.
Mal is implemented in 42 different languages:
Mal is implemented in 43 different languages:
* GNU awk
* Bash shell
@ -48,6 +48,7 @@ Mal is implemented in 42 different languages:
* Rust
* Scala
* Swift
* Tcl
* Vimscript
* Visual Basic.NET
@ -534,6 +535,18 @@ make
./stepX_YYY
```
### Tcl 8.6
*The Tcl implementation was created by [Dov Murik](https://github.com/dubek)*
The Tcl implementation of mal requires Tcl 8.6 to run. For readline line
editing support, install tclreadline.
```
cd tcl
tclsh ./stepX_YYY.tcl
```
### Vimscript
*The Vimscript implementation was created by [Dov Murik](https://github.com/dubek)*

24
tcl/Dockerfile Normal file
View File

@ -0,0 +1,24 @@
FROM ubuntu:vivid
MAINTAINER Joel Martin <github@martintribe.org>
##########################################################
# General requirements for testing or common across many
# implementations
##########################################################
RUN apt-get -y update
# Required for running tests
RUN apt-get -y install make python
# 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 tcl tcl-tclreadline

12
tcl/Makefile Normal file
View File

@ -0,0 +1,12 @@
SOURCES_BASE = mal_readline.tcl types.tcl reader.tcl printer.tcl
SOURCES_LISP = env.tcl core.tcl stepA_mal.tcl
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
.PHONY: stats stats-lisp
stats: $(SOURCES)
@wc $^
@printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]"
stats-lisp: $(SOURCES_LISP)
@wc $^
@printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]"

404
tcl/core.tcl Normal file
View File

@ -0,0 +1,404 @@
proc mal_equal {a} {
bool_new [equal_q [lindex $a 0] [lindex $a 1]]
}
set ::mal_exception_obj 0
proc mal_throw {a} {
set ::mal_exception_obj [lindex $a 0]
error "__MalException__"
}
proc mal_nil_q {a} {
bool_new [nil_q [lindex $a 0]]
}
proc mal_true_q {a} {
bool_new [true_q [lindex $a 0]]
}
proc mal_false_q {a} {
bool_new [false_q [lindex $a 0]]
}
proc mal_symbol {a} {
symbol_new [obj_val [lindex $a 0]]
}
proc mal_symbol_q {a} {
bool_new [symbol_q [lindex $a 0]]
}
proc mal_keyword {a} {
keyword_new [obj_val [lindex $a 0]]
}
proc mal_keyword_q {a} {
bool_new [keyword_q [lindex $a 0]]
}
proc render_array {arr readable delim} {
set res {}
foreach e $arr {
lappend res [pr_str $e $readable]
}
join $res $delim
}
proc mal_pr_str {a} {
string_new [render_array $a 1 " "]
}
proc mal_str {a} {
string_new [render_array $a 0 ""]
}
proc mal_prn {a} {
puts [render_array $a 1 " "]
return $::mal_nil
}
proc mal_println {a} {
puts [render_array $a 0 " "]
return $::mal_nil
}
proc mal_read_string {a} {
read_str [obj_val [lindex $a 0]]
}
proc mal_readline {a} {
set prompt [obj_val [lindex $a 0]]
set res [_readline $prompt]
if {[lindex $res 0] == "EOF"} {
return $::mal_nil
}
string_new [lindex $res 1]
}
proc mal_slurp {a} {
set filename [obj_val [lindex $a 0]]
set file [open $filename]
set content [read $file]
close $file
string_new $content
}
proc mal_lt {a} {
bool_new [expr {[obj_val [lindex $a 0]] < [obj_val [lindex $a 1]]}]
}
proc mal_lte {a} {
bool_new [expr {[obj_val [lindex $a 0]] <= [obj_val [lindex $a 1]]}]
}
proc mal_gt {a} {
bool_new [expr {[obj_val [lindex $a 0]] > [obj_val [lindex $a 1]]}]
}
proc mal_gte {a} {
bool_new [expr {[obj_val [lindex $a 0]] >= [obj_val [lindex $a 1]]}]
}
proc mal_add {a} {
integer_new [expr {[obj_val [lindex $a 0]] + [obj_val [lindex $a 1]]}]
}
proc mal_sub {a} {
integer_new [expr {[obj_val [lindex $a 0]] - [obj_val [lindex $a 1]]}]
}
proc mal_mul {a} {
integer_new [expr {[obj_val [lindex $a 0]] * [obj_val [lindex $a 1]]}]
}
proc mal_div {a} {
integer_new [expr {[obj_val [lindex $a 0]] / [obj_val [lindex $a 1]]}]
}
proc mal_time_ms {a} {
integer_new [clock milliseconds]
}
proc mal_list {a} {
list_new $a
}
proc mal_list_q {a} {
bool_new [list_q [lindex $a 0]]
}
proc mal_vector {a} {
vector_new $a
}
proc mal_vector_q {a} {
bool_new [vector_q [lindex $a 0]]
}
proc mal_hash_map {a} {
set d [dict create]
foreach {k v} $a {
dict set d [obj_val $k] $v
}
hashmap_new $d
}
proc mal_map_q {a} {
bool_new [hashmap_q [lindex $a 0]]
}
proc mal_assoc {a} {
set d [dict create]
dict for {k v} [obj_val [lindex $a 0]] {
dict set d $k $v
}
foreach {k v} [lrange $a 1 end] {
dict set d [obj_val $k] $v
}
hashmap_new $d
}
proc mal_dissoc {a} {
set d [dict create]
dict for {k v} [obj_val [lindex $a 0]] {
dict set d $k $v
}
foreach k [lrange $a 1 end] {
dict unset d [obj_val $k]
}
hashmap_new $d
}
proc mal_get {a} {
lassign $a hashmap_obj key_obj
if {[dict exists [obj_val $hashmap_obj] [obj_val $key_obj]]} {
dict get [obj_val $hashmap_obj] [obj_val $key_obj]
} else {
return $::mal_nil
}
}
proc mal_contains_q {a} {
lassign $a hashmap_obj key_obj
bool_new [dict exists [obj_val $hashmap_obj] [obj_val $key_obj]]
}
proc mal_keys {a} {
set res {}
foreach k [dict keys [obj_val [lindex $a 0]]] {
lappend res [string_new $k]
}
list_new $res
}
proc mal_vals {a} {
list_new [dict values [obj_val [lindex $a 0]]]
}
proc mal_sequential_q {a} {
bool_new [sequential_q [lindex $a 0]]
}
proc mal_cons {a} {
lassign $a head lst
list_new [concat [list $head] [obj_val $lst]]
}
proc mal_concat {a} {
set res {}
foreach lst $a {
if {[nil_q $lst]} {
continue
}
set res [concat $res [obj_val $lst]]
}
list_new $res
}
proc mal_nth {a} {
lassign $a lst_obj index_obj
set index [obj_val $index_obj]
set lst [obj_val $lst_obj]
if {$index >= [llength $lst]} {
error "nth: index out of range"
}
lindex $lst $index
}
proc mal_first {a} {
lassign $a lst
if {[nil_q $lst] || [llength [obj_val $lst]] == 0} {
return $::mal_nil
}
lindex [obj_val $lst] 0
}
proc mal_rest {a} {
lassign $a lst
list_new [lrange [obj_val $lst] 1 end]
}
proc mal_empty_q {a} {
bool_new [expr {[llength [obj_val [lindex $a 0]]] == 0}]
}
proc mal_count {a} {
integer_new [llength [obj_val [lindex $a 0]]]
}
proc mal_apply {a} {
set f [lindex $a 0]
if {[llength $a] > 1} {
set mid_args [lrange $a 1 end-1]
set last_list [lindex $a end]
set apply_args [concat $mid_args [obj_val $last_list]]
} else {
set apply_args {}
}
switch [obj_type $f] {
function {
set funcdict [obj_val $f]
set body [dict get $funcdict body]
set env [dict get $funcdict env]
set binds [dict get $funcdict binds]
set funcenv [Env new $env $binds $apply_args]
return [EVAL $body $funcenv]
}
nativefunction {
set body [concat [list [obj_val $f]] {$a}]
set lambda [list {a} $body]
return [apply $lambda $apply_args]
}
default {
error "Not a function"
}
}
}
proc mal_map {a} {
lassign $a f seq
set res {}
foreach item [obj_val $seq] {
set mappeditem [mal_apply [list $f [list_new [list $item]]]]
lappend res $mappeditem
}
list_new $res
}
proc mal_conj {a} {
lassign $a a0
if {[list_q $a0]} {
set lst $a0
foreach item [lrange $a 1 end] {
set lst [mal_cons [list $item $lst]]
}
return $lst
} elseif {[vector_q $a0]} {
set res [obj_val $a0]
foreach item [lrange $a 1 end] {
lappend res $item
}
vector_new $res
} else {
error "conj requires list or vector"
}
}
proc mal_meta {a} {
obj_meta [lindex $a 0]
}
proc mal_with_meta {a} {
lassign $a a0 a1
obj_new [obj_type $a0] [obj_val $a0] $a1
}
proc mal_atom {a} {
atom_new [lindex $a 0]
}
proc mal_atom_q {a} {
bool_new [atom_q [lindex $a 0]]
}
proc mal_deref {a} {
obj_val [lindex $a 0]
}
proc mal_reset_bang {a} {
lassign $a a0 a1
obj_set_val $a0 $a1
}
proc mal_swap_bang {a} {
lassign $a a0 f
set apply_args [concat [list [obj_val $a0]] [lrange $a 2 end]]
set newval [mal_apply [list $f [list_new $apply_args]]]
mal_reset_bang [list $a0 $newval]
}
set core_ns [dict create \
"=" [nativefunction_new mal_equal] \
"throw" [nativefunction_new mal_throw] \
\
"nil?" [nativefunction_new mal_nil_q] \
"true?" [nativefunction_new mal_true_q] \
"false?" [nativefunction_new mal_false_q] \
"symbol" [nativefunction_new mal_symbol] \
"symbol?" [nativefunction_new mal_symbol_q] \
"keyword" [nativefunction_new mal_keyword] \
"keyword?" [nativefunction_new mal_keyword_q] \
\
"pr-str" [nativefunction_new mal_pr_str] \
"str" [nativefunction_new mal_str] \
"prn" [nativefunction_new mal_prn] \
"println" [nativefunction_new mal_println] \
"read-string" [nativefunction_new mal_read_string] \
"readline" [nativefunction_new mal_readline] \
"slurp" [nativefunction_new mal_slurp] \
\
"<" [nativefunction_new mal_lt] \
"<=" [nativefunction_new mal_lte] \
">" [nativefunction_new mal_gt] \
">=" [nativefunction_new mal_gte] \
"+" [nativefunction_new mal_add] \
"-" [nativefunction_new mal_sub] \
"*" [nativefunction_new mal_mul] \
"/" [nativefunction_new mal_div] \
"time-ms" [nativefunction_new mal_time_ms] \
\
"list" [nativefunction_new mal_list] \
"list?" [nativefunction_new mal_list_q] \
"vector" [nativefunction_new mal_vector] \
"vector?" [nativefunction_new mal_vector_q] \
"hash-map" [nativefunction_new mal_hash_map] \
"map?" [nativefunction_new mal_map_q] \
"assoc" [nativefunction_new mal_assoc] \
"dissoc" [nativefunction_new mal_dissoc] \
"get" [nativefunction_new mal_get] \
"contains?" [nativefunction_new mal_contains_q] \
"keys" [nativefunction_new mal_keys] \
"vals" [nativefunction_new mal_vals] \
\
"sequential?" [nativefunction_new mal_sequential_q] \
"cons" [nativefunction_new mal_cons] \
"concat" [nativefunction_new mal_concat] \
"nth" [nativefunction_new mal_nth] \
"first" [nativefunction_new mal_first] \
"rest" [nativefunction_new mal_rest] \
"empty?" [nativefunction_new mal_empty_q] \
"count" [nativefunction_new mal_count] \
"apply" [nativefunction_new mal_apply] \
"map" [nativefunction_new mal_map] \
\
"conj" [nativefunction_new mal_conj] \
\
"meta" [nativefunction_new mal_meta] \
"with-meta" [nativefunction_new mal_with_meta] \
"atom" [nativefunction_new mal_atom] \
"atom?" [nativefunction_new mal_atom_q] \
"deref" [nativefunction_new mal_deref] \
"reset!" [nativefunction_new mal_reset_bang] \
"swap!" [nativefunction_new mal_swap_bang] \
]

49
tcl/env.tcl Normal file
View File

@ -0,0 +1,49 @@
oo::class create Env {
variable outer data
constructor {{outerenv 0} {binds ""} {exprs ""}} {
set outer $outerenv
set data [dict create]
if {$binds != ""} {
for {set i 0} {$i < [llength $binds]} {incr i} {
set b [lindex $binds $i]
if {$b == "&"} {
set varrest [lindex $binds [expr {$i + 1}]]
set restexprs [list_new [lrange $exprs $i end]]
my set $varrest $restexprs
break
} else {
my set $b [lindex $exprs $i]
}
}
}
}
method set {symbol objval} {
dict set data $symbol $objval
return $objval
}
method find {symbol} {
if {[dict exist $data $symbol]} {
return [self]
} elseif {$outer != 0} {
return [$outer find $symbol]
} else {
return 0
}
}
method get {symbol} {
set foundenv [my find $symbol]
if {$foundenv == 0} {
error "'$symbol' not found"
} else {
return [$foundenv get_symbol $symbol]
}
}
method get_symbol {symbol} {
dict get $data $symbol
}
}

24
tcl/mal_readline.tcl Normal file
View File

@ -0,0 +1,24 @@
if {[catch {package require tclreadline}]} {
proc _readline prompt {
puts -nonewline $prompt
flush stdout
if {[gets stdin line] < 0} {
return {"EOF" ""}
}
list "OK" $line
}
} else {
set ::historyfile "$env(HOME)/.mal-history"
::tclreadline::readline initialize $::historyfile
proc _readline prompt {
set reached_eof 0
::tclreadline::readline eofchar { set reached_eof 1 }
set line [::tclreadline::readline read $prompt]
if {$reached_eof} {
return {"EOF" ""}
}
::tclreadline::readline write $::historyfile
list "OK" $line
}
}

56
tcl/printer.tcl Normal file
View File

@ -0,0 +1,56 @@
proc format_list {elements start_char end_char readable} {
set res {}
foreach element $elements {
lappend res [pr_str $element $readable]
}
set joined [join $res " "]
return "${start_char}${joined}${end_char}"
}
proc format_hashmap {dictionary readable} {
set lst {}
dict for {keystr valobj} $dictionary {
lappend lst [string_new $keystr]
lappend lst $valobj
}
format_list $lst "\{" "\}" $readable
}
proc format_string {str readable} {
if {[string index $str 0] == "\u029E"} {
return ":[string range $str 1 end]"
} elseif {$readable} {
set escaped [string map {"\n" "\\n" "\"" "\\\"" "\\" "\\\\"} $str]
return "\"$escaped\""
} else {
return $str
}
}
proc format_function {funcdict} {
set type "function"
if {[dict get $funcdict is_macro]} {
set type "macro"
}
return "<$type:args=[join [dict get $funcdict binds] ","]>"
}
proc pr_str {ast readable} {
set nodetype [obj_type $ast]
set nodevalue [obj_val $ast]
switch $nodetype {
nil { return "nil" }
true { return "true" }
false { return "false" }
integer { return $nodevalue }
symbol { return $nodevalue }
string { return [format_string $nodevalue $readable] }
list { return [format_list $nodevalue "(" ")" $readable] }
vector { return [format_list $nodevalue "\[" "\]" $readable] }
hashmap { return [format_hashmap [dict get $nodevalue] $readable] }
atom { return "(atom [pr_str $nodevalue $readable])" }
function { return [format_function $nodevalue] }
nativefunction { return "<nativefunction:$nodevalue>" }
default { error "cannot print type $nodetype" }
}
}

124
tcl/reader.tcl Normal file
View File

@ -0,0 +1,124 @@
oo::class create Reader {
variable pos tokens
constructor {tokens_list} {
set tokens $tokens_list
set pos 0
}
method peek {} {
lindex $tokens $pos
}
method next {} {
set token [my peek]
incr pos
return $token
}
}
proc tokenize str {
set re {[\s,]*(~@|[\[\]\{\}()'`~^@]|\"(?:\\.|[^\\\"])*\"|;.*|[^\s\[\]\{\}('\"`~^@,;)]*)}
set tokens {}
foreach {_ capture} [regexp -line -all -inline $re $str] {
if {[string length $capture] > 0 && [string range $capture 0 0] != ";"} {
lappend tokens $capture
}
}
return $tokens
}
proc read_tokens_list {reader start_char end_char} {
set token [$reader next]
if {$token != $start_char} {
error "expected '$start_char'"
}
set elements {}
set token [$reader peek]
while {$token != $end_char} {
if {$token == ""} {
error "expected '$end_char'"
}
lappend elements [read_form $reader]
set token [$reader peek]
}
$reader next
return $elements
}
proc read_list {reader} {
set elements [read_tokens_list $reader "(" ")"]
list_new $elements
}
proc read_vector {reader} {
set elements [read_tokens_list $reader "\[" "\]"]
vector_new $elements
}
proc read_hashmap {reader} {
set res [dict create]
foreach {keytoken valtoken} [read_tokens_list $reader "{" "}"] {
dict set res [obj_val $keytoken] $valtoken
}
hashmap_new $res
}
proc parse_string {str} {
set res [string range $str 1 end-1]
string map {"\\n" "\n" "\\\"" "\"" "\\\\" "\\"} $res
}
proc parse_keyword {str} {
# Remove initial ":"
string range $str 1 end
}
proc read_atom {reader} {
set token [$reader next]
switch -regexp $token {
^-?[0-9]+$ { return [obj_new "integer" $token] }
^nil$ { return $::mal_nil }
^true$ { return $::mal_true }
^false$ { return $::mal_false }
^: { return [keyword_new [parse_keyword $token]] }
^\".*\"$ { return [string_new [parse_string $token]] }
default { return [symbol_new $token] }
}
}
proc symbol_shortcut {symbol_name reader} {
$reader next
list_new [list [symbol_new $symbol_name] [read_form $reader]]
}
proc read_form {reader} {
switch [$reader peek] {
"'" { return [symbol_shortcut "quote" $reader] }
"`" { return [symbol_shortcut "quasiquote" $reader] }
"~" { return [symbol_shortcut "unquote" $reader] }
"~@" { return [symbol_shortcut "splice-unquote" $reader] }
"^" {
$reader next
set meta [read_form $reader]
return [list_new [list [symbol_new "with-meta"] [read_form $reader] $meta]]
}
"@" { return [symbol_shortcut "deref" $reader] }
"(" { return [read_list $reader] }
")" { error "unexpected ')'" }
"\[" { return [read_vector $reader] }
"\]" { error "unexpected '\]'" }
"\{" { return [read_hashmap $reader] }
"\}" { error "unexpected '\}'" }
default { return [read_atom $reader] }
}
}
proc read_str str {
set tokens [tokenize $str]
set reader [Reader new $tokens]
set res [read_form $reader]
$reader destroy
return $res
}

33
tcl/step0_repl.tcl Normal file
View File

@ -0,0 +1,33 @@
source mal_readline.tcl
proc READ str {
return $str
}
proc EVAL {ast env} {
return $ast
}
proc PRINT exp {
return $exp
}
proc REP str {
PRINT [EVAL [READ $str] {}]
}
fconfigure stdout -translation binary
# repl loop
while {true} {
set res [_readline "user> "]
if {[lindex $res 0] == "EOF"} {
break
}
set line [lindex $res 1]
if {$line == ""} {
continue
}
puts [REP $line]
}
puts ""

38
tcl/step1_read_print.tcl Normal file
View File

@ -0,0 +1,38 @@
source mal_readline.tcl
source types.tcl
source reader.tcl
source printer.tcl
proc READ str {
read_str $str
}
proc EVAL {ast env} {
return $ast
}
proc PRINT exp {
pr_str $exp 1
}
proc REP str {
PRINT [EVAL [READ $str] {}]
}
fconfigure stdout -translation binary
# repl loop
while {true} {
set res [_readline "user> "]
if {[lindex $res 0] == "EOF"} {
break
}
set line [lindex $res 1]
if {$line == ""} {
continue
}
if { [catch { puts [REP $line] } exception] } {
puts "Error: $exception"
}
}
puts ""

103
tcl/step2_eval.tcl Normal file
View File

@ -0,0 +1,103 @@
source mal_readline.tcl
source types.tcl
source reader.tcl
source printer.tcl
proc READ str {
read_str $str
}
proc eval_ast {ast env} {
switch [obj_type $ast] {
"symbol" {
set varname [obj_val $ast]
if {[dict exists $env $varname]} {
return [dict get $env $varname]
} else {
error "'$varname' not found"
}
}
"list" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [list_new $res]
}
"vector" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [vector_new $res]
}
"hashmap" {
set res [dict create]
dict for {k v} [obj_val $ast] {
dict set res $k [EVAL $v $env]
}
return [hashmap_new $res]
}
default { return $ast }
}
}
proc EVAL {ast env} {
if {![list_q $ast]} {
return [eval_ast $ast $env]
}
set lst_obj [eval_ast $ast $env]
set lst [obj_val $lst_obj]
set f [lindex $lst 0]
set call_args [lrange $lst 1 end]
apply $f $call_args
}
proc PRINT exp {
pr_str $exp 1
}
proc REP {str env} {
PRINT [EVAL [READ $str] $env]
}
proc mal_add {a} {
integer_new [expr {[obj_val [lindex $a 0]] + [obj_val [lindex $a 1]]}]
}
proc mal_sub {a} {
integer_new [expr {[obj_val [lindex $a 0]] - [obj_val [lindex $a 1]]}]
}
proc mal_mul {a} {
integer_new [expr {[obj_val [lindex $a 0]] * [obj_val [lindex $a 1]]}]
}
proc mal_div {a} {
integer_new [expr {[obj_val [lindex $a 0]] / [obj_val [lindex $a 1]]}]
}
set repl_env [dict create \
"+" {{a} {mal_add $a}} \
"-" {{a} {mal_sub $a}} \
"*" {{a} {mal_mul $a}} \
"/" {{a} {mal_div $a}} \
]
fconfigure stdout -translation binary
# repl loop
while {true} {
set res [_readline "user> "]
if {[lindex $res 0] == "EOF"} {
break
}
set line [lindex $res 1]
if {$line == ""} {
continue
}
if { [catch { puts [REP $line $repl_env] } exception] } {
puts "Error: $exception"
}
}
puts ""

119
tcl/step3_env.tcl Normal file
View File

@ -0,0 +1,119 @@
source mal_readline.tcl
source types.tcl
source reader.tcl
source printer.tcl
source env.tcl
proc READ str {
read_str $str
}
proc eval_ast {ast env} {
switch [obj_type $ast] {
"symbol" {
set varname [obj_val $ast]
return [$env get $varname]
}
"list" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [list_new $res]
}
"vector" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [vector_new $res]
}
"hashmap" {
set res [dict create]
dict for {k v} [obj_val $ast] {
dict set res $k [EVAL $v $env]
}
return [hashmap_new $res]
}
default { return $ast }
}
}
proc EVAL {ast env} {
if {![list_q $ast]} {
return [eval_ast $ast $env]
}
set a0 [lindex [obj_val $ast] 0]
set a1 [lindex [obj_val $ast] 1]
set a2 [lindex [obj_val $ast] 2]
switch [obj_val $a0] {
"def!" {
set varname [obj_val $a1]
set value [EVAL $a2 $env]
return [$env set $varname $value]
}
"let*" {
set letenv [Env new $env]
set bindings_list [obj_val $a1]
foreach {varnameobj varvalobj} $bindings_list {
$letenv set [obj_val $varnameobj] [EVAL $varvalobj $letenv]
}
return [EVAL $a2 $letenv]
}
default {
set lst_obj [eval_ast $ast $env]
set lst [obj_val $lst_obj]
set f [lindex $lst 0]
set call_args [lrange $lst 1 end]
return [apply $f $call_args]
}
}
}
proc PRINT exp {
pr_str $exp 1
}
proc REP {str env} {
PRINT [EVAL [READ $str] $env]
}
proc mal_add {a} {
integer_new [expr {[obj_val [lindex $a 0]] + [obj_val [lindex $a 1]]}]
}
proc mal_sub {a} {
integer_new [expr {[obj_val [lindex $a 0]] - [obj_val [lindex $a 1]]}]
}
proc mal_mul {a} {
integer_new [expr {[obj_val [lindex $a 0]] * [obj_val [lindex $a 1]]}]
}
proc mal_div {a} {
integer_new [expr {[obj_val [lindex $a 0]] / [obj_val [lindex $a 1]]}]
}
set repl_env [Env new]
$repl_env set "+" {{a} {mal_add $a}}
$repl_env set "-" {{a} {mal_sub $a}}
$repl_env set "*" {{a} {mal_mul $a}}
$repl_env set "/" {{a} {mal_div $a}}
fconfigure stdout -translation binary
# repl loop
while {true} {
set res [_readline "user> "]
if {[lindex $res 0] == "EOF"} {
break
}
set line [lindex $res 1]
if {$line == ""} {
continue
}
if { [catch { puts [REP $line $repl_env] } exception] } {
puts "Error: $exception"
}
}
puts ""

155
tcl/step4_if_fn_do.tcl Normal file
View File

@ -0,0 +1,155 @@
source mal_readline.tcl
source types.tcl
source reader.tcl
source printer.tcl
source env.tcl
source core.tcl
proc READ str {
read_str $str
}
proc eval_ast {ast env} {
switch [obj_type $ast] {
"symbol" {
set varname [obj_val $ast]
return [$env get $varname]
}
"list" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [list_new $res]
}
"vector" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [vector_new $res]
}
"hashmap" {
set res [dict create]
dict for {k v} [obj_val $ast] {
dict set res $k [EVAL $v $env]
}
return [hashmap_new $res]
}
default { return $ast }
}
}
proc EVAL {ast env} {
if {![list_q $ast]} {
return [eval_ast $ast $env]
}
lassign [obj_val $ast] a0 a1 a2 a3
switch [obj_val $a0] {
"def!" {
set varname [obj_val $a1]
set value [EVAL $a2 $env]
return [$env set $varname $value]
}
"let*" {
set letenv [Env new $env]
set bindings_list [obj_val $a1]
foreach {varnameobj varvalobj} $bindings_list {
$letenv set [obj_val $varnameobj] [EVAL $varvalobj $letenv]
}
return [EVAL $a2 $letenv]
}
"do" {
set el [list_new [lrange [obj_val $ast] 1 end-1]]
eval_ast $el $env
return [EVAL [lindex [obj_val $ast] end] $env]
}
"if" {
set condval [EVAL $a1 $env]
if {[false_q $condval] || [nil_q $condval]} {
if {$a3 == ""} {
return $::mal_nil
}
return [EVAL $a3 $env]
}
return [EVAL $a2 $env]
}
"fn*" {
set binds {}
foreach v [obj_val $a1] {
lappend binds [obj_val $v]
}
return [function_new $a2 $env $binds]
}
default {
set lst_obj [eval_ast $ast $env]
set lst [obj_val $lst_obj]
set f [lindex $lst 0]
set call_args [lrange $lst 1 end]
switch [obj_type $f] {
function {
set funcdict [obj_val $f]
set body [dict get $funcdict body]
set env [dict get $funcdict env]
set binds [dict get $funcdict binds]
set funcenv [Env new $env $binds $call_args]
return [EVAL $body $funcenv]
}
nativefunction {
set body [concat [list [obj_val $f]] {$a}]
set lambda [list {a} $body]
return [apply $lambda $call_args]
}
default {
error "Not a function"
}
}
}
}
}
proc PRINT exp {
pr_str $exp 1
}
proc REP {str env} {
PRINT [EVAL [READ $str] $env]
}
proc RE {str env} {
EVAL [READ $str] $env
}
set repl_env [Env new]
dict for {k v} $core_ns {
$repl_env set $k $v
}
# core.mal: defined using the language itself
RE "(def! not (fn* (a) (if a false true)))" $repl_env
fconfigure stdout -translation binary
set DEBUG_MODE 0
if { [array names env DEBUG] != "" && $env(DEBUG) != "0" } {
set DEBUG_MODE 1
}
# repl loop
while {true} {
set res [_readline "user> "]
if {[lindex $res 0] == "EOF"} {
break
}
set line [lindex $res 1]
if {$line == ""} {
continue
}
if { [catch { puts [REP $line $repl_env] } exception] } {
puts "Error: $exception"
if { $DEBUG_MODE } {
puts $::errorInfo
}
}
}
puts ""

160
tcl/step5_tco.tcl Normal file
View File

@ -0,0 +1,160 @@
source mal_readline.tcl
source types.tcl
source reader.tcl
source printer.tcl
source env.tcl
source core.tcl
proc READ str {
read_str $str
}
proc eval_ast {ast env} {
switch [obj_type $ast] {
"symbol" {
set varname [obj_val $ast]
return [$env get $varname]
}
"list" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [list_new $res]
}
"vector" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [vector_new $res]
}
"hashmap" {
set res [dict create]
dict for {k v} [obj_val $ast] {
dict set res $k [EVAL $v $env]
}
return [hashmap_new $res]
}
default { return $ast }
}
}
proc EVAL {ast env} {
while {true} {
if {![list_q $ast]} {
return [eval_ast $ast $env]
}
lassign [obj_val $ast] a0 a1 a2 a3
switch [obj_val $a0] {
"def!" {
set varname [obj_val $a1]
set value [EVAL $a2 $env]
return [$env set $varname $value]
}
"let*" {
set letenv [Env new $env]
set bindings_list [obj_val $a1]
foreach {varnameobj varvalobj} $bindings_list {
$letenv set [obj_val $varnameobj] [EVAL $varvalobj $letenv]
}
set ast $a2
set env $letenv
# TCO: Continue loop
}
"do" {
set el [list_new [lrange [obj_val $ast] 1 end-1]]
eval_ast $el $env
set ast [lindex [obj_val $ast] end]
# TCO: Continue loop
}
"if" {
set condval [EVAL $a1 $env]
if {[false_q $condval] || [nil_q $condval]} {
if {$a3 == ""} {
return $::mal_nil
}
set ast $a3
} else {
set ast $a2
}
# TCO: Continue loop
}
"fn*" {
set binds {}
foreach v [obj_val $a1] {
lappend binds [obj_val $v]
}
return [function_new $a2 $env $binds]
}
default {
set lst_obj [eval_ast $ast $env]
set lst [obj_val $lst_obj]
set f [lindex $lst 0]
set call_args [lrange $lst 1 end]
switch [obj_type $f] {
function {
set fn [obj_val $f]
set ast [dict get $fn body]
set env [Env new [dict get $fn env] [dict get $fn binds] $call_args]
# TCO: Continue loop
}
nativefunction {
set body [concat [list [obj_val $f]] {$a}]
set lambda [list {a} $body]
return [apply $lambda $call_args]
}
default {
error "Not a function"
}
}
}
}
}
}
proc PRINT exp {
pr_str $exp 1
}
proc REP {str env} {
PRINT [EVAL [READ $str] $env]
}
proc RE {str env} {
EVAL [READ $str] $env
}
set repl_env [Env new]
dict for {k v} $core_ns {
$repl_env set $k $v
}
# core.mal: defined using the language itself
RE "(def! not (fn* (a) (if a false true)))" $repl_env
fconfigure stdout -translation binary
set DEBUG_MODE 0
if { [array names env DEBUG] != "" && $env(DEBUG) != "0" } {
set DEBUG_MODE 1
}
# repl loop
while {true} {
set res [_readline "user> "]
if {[lindex $res 0] == "EOF"} {
break
}
set line [lindex $res 1]
if {$line == ""} {
continue
}
if { [catch { puts [REP $line $repl_env] } exception] } {
puts "Error: $exception"
if { $DEBUG_MODE } {
puts $::errorInfo
}
}
}
puts ""

179
tcl/step6_file.tcl Normal file
View File

@ -0,0 +1,179 @@
source mal_readline.tcl
source types.tcl
source reader.tcl
source printer.tcl
source env.tcl
source core.tcl
proc READ str {
read_str $str
}
proc eval_ast {ast env} {
switch [obj_type $ast] {
"symbol" {
set varname [obj_val $ast]
return [$env get $varname]
}
"list" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [list_new $res]
}
"vector" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [vector_new $res]
}
"hashmap" {
set res [dict create]
dict for {k v} [obj_val $ast] {
dict set res $k [EVAL $v $env]
}
return [hashmap_new $res]
}
default { return $ast }
}
}
proc EVAL {ast env} {
while {true} {
if {![list_q $ast]} {
return [eval_ast $ast $env]
}
lassign [obj_val $ast] a0 a1 a2 a3
switch [obj_val $a0] {
"def!" {
set varname [obj_val $a1]
set value [EVAL $a2 $env]
return [$env set $varname $value]
}
"let*" {
set letenv [Env new $env]
set bindings_list [obj_val $a1]
foreach {varnameobj varvalobj} $bindings_list {
$letenv set [obj_val $varnameobj] [EVAL $varvalobj $letenv]
}
set ast $a2
set env $letenv
# TCO: Continue loop
}
"do" {
set el [list_new [lrange [obj_val $ast] 1 end-1]]
eval_ast $el $env
set ast [lindex [obj_val $ast] end]
# TCO: Continue loop
}
"if" {
set condval [EVAL $a1 $env]
if {[false_q $condval] || [nil_q $condval]} {
if {$a3 == ""} {
return $::mal_nil
}
set ast $a3
} else {
set ast $a2
}
# TCO: Continue loop
}
"fn*" {
set binds {}
foreach v [obj_val $a1] {
lappend binds [obj_val $v]
}
return [function_new $a2 $env $binds]
}
default {
set lst_obj [eval_ast $ast $env]
set lst [obj_val $lst_obj]
set f [lindex $lst 0]
set call_args [lrange $lst 1 end]
switch [obj_type $f] {
function {
set fn [obj_val $f]
set ast [dict get $fn body]
set env [Env new [dict get $fn env] [dict get $fn binds] $call_args]
# TCO: Continue loop
}
nativefunction {
set body [concat [list [obj_val $f]] {$a}]
set lambda [list {a} $body]
return [apply $lambda $call_args]
}
default {
error "Not a function"
}
}
}
}
}
}
proc PRINT exp {
pr_str $exp 1
}
proc REP {str env} {
PRINT [EVAL [READ $str] $env]
}
proc RE {str env} {
EVAL [READ $str] $env
}
proc mal_eval {a} {
global repl_env
EVAL [lindex $a 0] $repl_env
}
set repl_env [Env new]
dict for {k v} $core_ns {
$repl_env set $k $v
}
$repl_env set "eval" [nativefunction_new mal_eval]
set argv_list {}
foreach arg [lrange $argv 1 end] {
lappend argv_list [string_new $arg]
}
$repl_env set "*ARGV*" [list_new $argv_list]
# core.mal: defined using the language itself
RE "(def! not (fn* (a) (if a false true)))" $repl_env
RE "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" $repl_env
fconfigure stdout -translation binary
set DEBUG_MODE 0
if { [array names env DEBUG] != "" && $env(DEBUG) != "0" } {
set DEBUG_MODE 1
}
if {$argc > 0} {
REP "(load-file \"[lindex $argv 0]\")" $repl_env
exit
}
# repl loop
while {true} {
set res [_readline "user> "]
if {[lindex $res 0] == "EOF"} {
break
}
set line [lindex $res 1]
if {$line == ""} {
continue
}
if { [catch { puts [REP $line $repl_env] } exception] } {
puts "Error: $exception"
if { $DEBUG_MODE } {
puts $::errorInfo
}
}
}
puts ""

206
tcl/step7_quote.tcl Normal file
View File

@ -0,0 +1,206 @@
source mal_readline.tcl
source types.tcl
source reader.tcl
source printer.tcl
source env.tcl
source core.tcl
proc READ str {
read_str $str
}
proc is_pair {ast} {
expr {[sequential_q $ast] && [llength [obj_val $ast]] > 0}
}
proc quasiquote {ast} {
if {![is_pair $ast]} {
return [list_new [list [symbol_new "quote"] $ast]]
}
lassign [obj_val $ast] a0 a1
if {[symbol_q $a0] && [obj_val $a0] == "unquote"} {
return $a1
}
lassign [obj_val $a0] a00 a01
set rest [list_new [lrange [obj_val $ast] 1 end]]
if {[is_pair $a0] && [symbol_q $a00] && [obj_val $a00] == "splice-unquote"} {
return [list_new [list [symbol_new "concat"] $a01 [quasiquote $rest]]]
} else {
return [list_new [list [symbol_new "cons"] [quasiquote $a0] [quasiquote $rest]]]
}
}
proc eval_ast {ast env} {
switch [obj_type $ast] {
"symbol" {
set varname [obj_val $ast]
return [$env get $varname]
}
"list" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [list_new $res]
}
"vector" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [vector_new $res]
}
"hashmap" {
set res [dict create]
dict for {k v} [obj_val $ast] {
dict set res $k [EVAL $v $env]
}
return [hashmap_new $res]
}
default { return $ast }
}
}
proc EVAL {ast env} {
while {true} {
if {![list_q $ast]} {
return [eval_ast $ast $env]
}
lassign [obj_val $ast] a0 a1 a2 a3
switch [obj_val $a0] {
"def!" {
set varname [obj_val $a1]
set value [EVAL $a2 $env]
return [$env set $varname $value]
}
"let*" {
set letenv [Env new $env]
set bindings_list [obj_val $a1]
foreach {varnameobj varvalobj} $bindings_list {
$letenv set [obj_val $varnameobj] [EVAL $varvalobj $letenv]
}
set ast $a2
set env $letenv
# TCO: Continue loop
}
"quote" {
return $a1
}
"quasiquote" {
set ast [quasiquote $a1]
}
"do" {
set el [list_new [lrange [obj_val $ast] 1 end-1]]
eval_ast $el $env
set ast [lindex [obj_val $ast] end]
# TCO: Continue loop
}
"if" {
set condval [EVAL $a1 $env]
if {[false_q $condval] || [nil_q $condval]} {
if {$a3 == ""} {
return $::mal_nil
}
set ast $a3
} else {
set ast $a2
}
# TCO: Continue loop
}
"fn*" {
set binds {}
foreach v [obj_val $a1] {
lappend binds [obj_val $v]
}
return [function_new $a2 $env $binds]
}
default {
set lst_obj [eval_ast $ast $env]
set lst [obj_val $lst_obj]
set f [lindex $lst 0]
set call_args [lrange $lst 1 end]
switch [obj_type $f] {
function {
set fn [obj_val $f]
set ast [dict get $fn body]
set env [Env new [dict get $fn env] [dict get $fn binds] $call_args]
# TCO: Continue loop
}
nativefunction {
set body [concat [list [obj_val $f]] {$a}]
set lambda [list {a} $body]
return [apply $lambda $call_args]
}
default {
error "Not a function"
}
}
}
}
}
}
proc PRINT exp {
pr_str $exp 1
}
proc REP {str env} {
PRINT [EVAL [READ $str] $env]
}
proc RE {str env} {
EVAL [READ $str] $env
}
proc mal_eval {a} {
global repl_env
EVAL [lindex $a 0] $repl_env
}
set repl_env [Env new]
dict for {k v} $core_ns {
$repl_env set $k $v
}
$repl_env set "eval" [nativefunction_new mal_eval]
set argv_list {}
foreach arg [lrange $argv 1 end] {
lappend argv_list [string_new $arg]
}
$repl_env set "*ARGV*" [list_new $argv_list]
# core.mal: defined using the language itself
RE "(def! not (fn* (a) (if a false true)))" $repl_env
RE "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" $repl_env
fconfigure stdout -translation binary
set DEBUG_MODE 0
if { [array names env DEBUG] != "" && $env(DEBUG) != "0" } {
set DEBUG_MODE 1
}
if {$argc > 0} {
REP "(load-file \"[lindex $argv 0]\")" $repl_env
exit
}
# repl loop
while {true} {
set res [_readline "user> "]
if {[lindex $res 0] == "EOF"} {
break
}
set line [lindex $res 1]
if {$line == ""} {
continue
}
if { [catch { puts [REP $line $repl_env] } exception] } {
puts "Error: $exception"
if { $DEBUG_MODE } {
puts $::errorInfo
}
}
}
puts ""

258
tcl/step8_macros.tcl Normal file
View File

@ -0,0 +1,258 @@
source mal_readline.tcl
source types.tcl
source reader.tcl
source printer.tcl
source env.tcl
source core.tcl
proc READ str {
read_str $str
}
proc is_pair {ast} {
expr {[sequential_q $ast] && [llength [obj_val $ast]] > 0}
}
proc quasiquote {ast} {
if {![is_pair $ast]} {
return [list_new [list [symbol_new "quote"] $ast]]
}
lassign [obj_val $ast] a0 a1
if {[symbol_q $a0] && [obj_val $a0] == "unquote"} {
return $a1
}
lassign [obj_val $a0] a00 a01
set rest [list_new [lrange [obj_val $ast] 1 end]]
if {[is_pair $a0] && [symbol_q $a00] && [obj_val $a00] == "splice-unquote"} {
return [list_new [list [symbol_new "concat"] $a01 [quasiquote $rest]]]
} else {
return [list_new [list [symbol_new "cons"] [quasiquote $a0] [quasiquote $rest]]]
}
}
proc is_macro_call {ast env} {
if {![list_q $ast]} {
return 0
}
set a0 [lindex [obj_val $ast] 0]
if {![symbol_q $a0]} {
return 0
}
set varname [obj_val $a0]
set foundenv [$env find $varname]
if {$foundenv == 0} {
return 0
}
macro_q [$env get $varname]
}
proc macroexpand {ast env} {
while {[is_macro_call $ast $env]} {
set a0 [mal_first [list $ast]]
set macro_name [obj_val $a0]
set macro_obj [$env get $macro_name]
set macro_args [obj_val [mal_rest [list $ast]]]
set funcdict [obj_val $macro_obj]
set body [dict get $funcdict body]
set env [dict get $funcdict env]
set binds [dict get $funcdict binds]
set funcenv [Env new $env $binds $macro_args]
set ast [EVAL $body $funcenv]
}
return $ast
}
proc eval_ast {ast env} {
switch [obj_type $ast] {
"symbol" {
set varname [obj_val $ast]
return [$env get $varname]
}
"list" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [list_new $res]
}
"vector" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [vector_new $res]
}
"hashmap" {
set res [dict create]
dict for {k v} [obj_val $ast] {
dict set res $k [EVAL $v $env]
}
return [hashmap_new $res]
}
default { return $ast }
}
}
proc EVAL {ast env} {
while {true} {
if {![list_q $ast]} {
return [eval_ast $ast $env]
}
set ast [macroexpand $ast $env]
if {![list_q $ast]} {
return $ast
}
lassign [obj_val $ast] a0 a1 a2 a3
switch [obj_val $a0] {
"def!" {
set varname [obj_val $a1]
set value [EVAL $a2 $env]
return [$env set $varname $value]
}
"let*" {
set letenv [Env new $env]
set bindings_list [obj_val $a1]
foreach {varnameobj varvalobj} $bindings_list {
$letenv set [obj_val $varnameobj] [EVAL $varvalobj $letenv]
}
set ast $a2
set env $letenv
# TCO: Continue loop
}
"quote" {
return $a1
}
"quasiquote" {
set ast [quasiquote $a1]
}
"defmacro!" {
set varname [obj_val $a1]
set value [EVAL $a2 $env]
set fn [obj_val $value]
dict set fn is_macro 1
obj_set_val $value $fn
return [$env set $varname $value]
}
"macroexpand" {
return [macroexpand $a1 $env]
}
"do" {
set el [list_new [lrange [obj_val $ast] 1 end-1]]
eval_ast $el $env
set ast [lindex [obj_val $ast] end]
# TCO: Continue loop
}
"if" {
set condval [EVAL $a1 $env]
if {[false_q $condval] || [nil_q $condval]} {
if {$a3 == ""} {
return $::mal_nil
}
set ast $a3
} else {
set ast $a2
}
# TCO: Continue loop
}
"fn*" {
set binds {}
foreach v [obj_val $a1] {
lappend binds [obj_val $v]
}
return [function_new $a2 $env $binds]
}
default {
set lst_obj [eval_ast $ast $env]
set lst [obj_val $lst_obj]
set f [lindex $lst 0]
set call_args [lrange $lst 1 end]
switch [obj_type $f] {
function {
set fn [obj_val $f]
set ast [dict get $fn body]
set env [Env new [dict get $fn env] [dict get $fn binds] $call_args]
# TCO: Continue loop
}
nativefunction {
set body [concat [list [obj_val $f]] {$a}]
set lambda [list {a} $body]
return [apply $lambda $call_args]
}
default {
error "Not a function"
}
}
}
}
}
}
proc PRINT exp {
pr_str $exp 1
}
proc REP {str env} {
PRINT [EVAL [READ $str] $env]
}
proc RE {str env} {
EVAL [READ $str] $env
}
proc mal_eval {a} {
global repl_env
EVAL [lindex $a 0] $repl_env
}
set repl_env [Env new]
dict for {k v} $core_ns {
$repl_env set $k $v
}
$repl_env set "eval" [nativefunction_new mal_eval]
set argv_list {}
foreach arg [lrange $argv 1 end] {
lappend argv_list [string_new $arg]
}
$repl_env set "*ARGV*" [list_new $argv_list]
# core.mal: defined using the language itself
RE "(def! not (fn* (a) (if a false true)))" $repl_env
RE "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" $repl_env
RE "(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)))))))" $repl_env
RE "(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))))))))" $repl_env
fconfigure stdout -translation binary
set DEBUG_MODE 0
if { [array names env DEBUG] != "" && $env(DEBUG) != "0" } {
set DEBUG_MODE 1
}
if {$argc > 0} {
REP "(load-file \"[lindex $argv 0]\")" $repl_env
exit
}
# repl loop
while {true} {
set res [_readline "user> "]
if {[lindex $res 0] == "EOF"} {
break
}
set line [lindex $res 1]
if {$line == ""} {
continue
}
if { [catch { puts [REP $line $repl_env] } exception] } {
puts "Error: $exception"
if { $DEBUG_MODE } {
puts $::errorInfo
}
}
}
puts ""

273
tcl/step9_try.tcl Normal file
View File

@ -0,0 +1,273 @@
source mal_readline.tcl
source types.tcl
source reader.tcl
source printer.tcl
source env.tcl
source core.tcl
proc READ str {
read_str $str
}
proc is_pair {ast} {
expr {[sequential_q $ast] && [llength [obj_val $ast]] > 0}
}
proc quasiquote {ast} {
if {![is_pair $ast]} {
return [list_new [list [symbol_new "quote"] $ast]]
}
lassign [obj_val $ast] a0 a1
if {[symbol_q $a0] && [obj_val $a0] == "unquote"} {
return $a1
}
lassign [obj_val $a0] a00 a01
set rest [list_new [lrange [obj_val $ast] 1 end]]
if {[is_pair $a0] && [symbol_q $a00] && [obj_val $a00] == "splice-unquote"} {
return [list_new [list [symbol_new "concat"] $a01 [quasiquote $rest]]]
} else {
return [list_new [list [symbol_new "cons"] [quasiquote $a0] [quasiquote $rest]]]
}
}
proc is_macro_call {ast env} {
if {![list_q $ast]} {
return 0
}
set a0 [lindex [obj_val $ast] 0]
if {![symbol_q $a0]} {
return 0
}
set varname [obj_val $a0]
set foundenv [$env find $varname]
if {$foundenv == 0} {
return 0
}
macro_q [$env get $varname]
}
proc macroexpand {ast env} {
while {[is_macro_call $ast $env]} {
set a0 [mal_first [list $ast]]
set macro_name [obj_val $a0]
set macro_obj [$env get $macro_name]
set macro_args [obj_val [mal_rest [list $ast]]]
set funcdict [obj_val $macro_obj]
set body [dict get $funcdict body]
set env [dict get $funcdict env]
set binds [dict get $funcdict binds]
set funcenv [Env new $env $binds $macro_args]
set ast [EVAL $body $funcenv]
}
return $ast
}
proc eval_ast {ast env} {
switch [obj_type $ast] {
"symbol" {
set varname [obj_val $ast]
return [$env get $varname]
}
"list" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [list_new $res]
}
"vector" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [vector_new $res]
}
"hashmap" {
set res [dict create]
dict for {k v} [obj_val $ast] {
dict set res $k [EVAL $v $env]
}
return [hashmap_new $res]
}
default { return $ast }
}
}
proc EVAL {ast env} {
while {true} {
if {![list_q $ast]} {
return [eval_ast $ast $env]
}
set ast [macroexpand $ast $env]
if {![list_q $ast]} {
return $ast
}
lassign [obj_val $ast] a0 a1 a2 a3
switch [obj_val $a0] {
"def!" {
set varname [obj_val $a1]
set value [EVAL $a2 $env]
return [$env set $varname $value]
}
"let*" {
set letenv [Env new $env]
set bindings_list [obj_val $a1]
foreach {varnameobj varvalobj} $bindings_list {
$letenv set [obj_val $varnameobj] [EVAL $varvalobj $letenv]
}
set ast $a2
set env $letenv
# TCO: Continue loop
}
"quote" {
return $a1
}
"quasiquote" {
set ast [quasiquote $a1]
}
"defmacro!" {
set varname [obj_val $a1]
set value [EVAL $a2 $env]
set fn [obj_val $value]
dict set fn is_macro 1
obj_set_val $value $fn
return [$env set $varname $value]
}
"macroexpand" {
return [macroexpand $a1 $env]
}
"try*" {
set res {}
if { [catch { set res [EVAL $a1 $env] } exception] } {
set exc_var [obj_val [lindex [obj_val $a2] 1]]
if {$exception == "__MalException__"} {
set exc_value $::mal_exception_obj
} else {
set exc_value [string_new $exception]
}
set catch_env [Env new $env [list $exc_var] [list $exc_value]]
return [EVAL [lindex [obj_val $a2] 2] $catch_env]
} else {
return $res
}
}
"do" {
set el [list_new [lrange [obj_val $ast] 1 end-1]]
eval_ast $el $env
set ast [lindex [obj_val $ast] end]
# TCO: Continue loop
}
"if" {
set condval [EVAL $a1 $env]
if {[false_q $condval] || [nil_q $condval]} {
if {$a3 == ""} {
return $::mal_nil
}
set ast $a3
} else {
set ast $a2
}
# TCO: Continue loop
}
"fn*" {
set binds {}
foreach v [obj_val $a1] {
lappend binds [obj_val $v]
}
return [function_new $a2 $env $binds]
}
default {
set lst_obj [eval_ast $ast $env]
set lst [obj_val $lst_obj]
set f [lindex $lst 0]
set call_args [lrange $lst 1 end]
switch [obj_type $f] {
function {
set fn [obj_val $f]
set ast [dict get $fn body]
set env [Env new [dict get $fn env] [dict get $fn binds] $call_args]
# TCO: Continue loop
}
nativefunction {
set body [concat [list [obj_val $f]] {$a}]
set lambda [list {a} $body]
return [apply $lambda $call_args]
}
default {
error "Not a function"
}
}
}
}
}
}
proc PRINT exp {
pr_str $exp 1
}
proc REP {str env} {
PRINT [EVAL [READ $str] $env]
}
proc RE {str env} {
EVAL [READ $str] $env
}
proc mal_eval {a} {
global repl_env
EVAL [lindex $a 0] $repl_env
}
set repl_env [Env new]
dict for {k v} $core_ns {
$repl_env set $k $v
}
$repl_env set "eval" [nativefunction_new mal_eval]
set argv_list {}
foreach arg [lrange $argv 1 end] {
lappend argv_list [string_new $arg]
}
$repl_env set "*ARGV*" [list_new $argv_list]
# core.mal: defined using the language itself
RE "(def! not (fn* (a) (if a false true)))" $repl_env
RE "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" $repl_env
RE "(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)))))))" $repl_env
RE "(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))))))))" $repl_env
fconfigure stdout -translation binary
set DEBUG_MODE 0
if { [array names env DEBUG] != "" && $env(DEBUG) != "0" } {
set DEBUG_MODE 1
}
if {$argc > 0} {
REP "(load-file \"[lindex $argv 0]\")" $repl_env
exit
}
# repl loop
while {true} {
set res [_readline "user> "]
if {[lindex $res 0] == "EOF"} {
break
}
set line [lindex $res 1]
if {$line == ""} {
continue
}
if { [catch { puts [REP $line $repl_env] } exception] } {
puts "Error: $exception"
if { $DEBUG_MODE } {
puts $::errorInfo
}
}
}
puts ""

279
tcl/stepA_mal.tcl Normal file
View File

@ -0,0 +1,279 @@
source mal_readline.tcl
source types.tcl
source reader.tcl
source printer.tcl
source env.tcl
source core.tcl
proc READ str {
read_str $str
}
proc is_pair {ast} {
expr {[sequential_q $ast] && [llength [obj_val $ast]] > 0}
}
proc quasiquote {ast} {
if {![is_pair $ast]} {
return [list_new [list [symbol_new "quote"] $ast]]
}
lassign [obj_val $ast] a0 a1
if {[symbol_q $a0] && [obj_val $a0] == "unquote"} {
return $a1
}
lassign [obj_val $a0] a00 a01
set rest [list_new [lrange [obj_val $ast] 1 end]]
if {[is_pair $a0] && [symbol_q $a00] && [obj_val $a00] == "splice-unquote"} {
return [list_new [list [symbol_new "concat"] $a01 [quasiquote $rest]]]
} else {
return [list_new [list [symbol_new "cons"] [quasiquote $a0] [quasiquote $rest]]]
}
}
proc is_macro_call {ast env} {
if {![list_q $ast]} {
return 0
}
set a0 [lindex [obj_val $ast] 0]
if {![symbol_q $a0]} {
return 0
}
set varname [obj_val $a0]
set foundenv [$env find $varname]
if {$foundenv == 0} {
return 0
}
macro_q [$env get $varname]
}
proc macroexpand {ast env} {
while {[is_macro_call $ast $env]} {
set a0 [mal_first [list $ast]]
set macro_name [obj_val $a0]
set macro_obj [$env get $macro_name]
set macro_args [obj_val [mal_rest [list $ast]]]
set funcdict [obj_val $macro_obj]
set body [dict get $funcdict body]
set env [dict get $funcdict env]
set binds [dict get $funcdict binds]
set funcenv [Env new $env $binds $macro_args]
set ast [EVAL $body $funcenv]
}
return $ast
}
proc eval_ast {ast env} {
switch [obj_type $ast] {
"symbol" {
set varname [obj_val $ast]
return [$env get $varname]
}
"list" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [list_new $res]
}
"vector" {
set res {}
foreach element [obj_val $ast] {
lappend res [EVAL $element $env]
}
return [vector_new $res]
}
"hashmap" {
set res [dict create]
dict for {k v} [obj_val $ast] {
dict set res $k [EVAL $v $env]
}
return [hashmap_new $res]
}
default { return $ast }
}
}
proc EVAL {ast env} {
while {true} {
if {![list_q $ast]} {
return [eval_ast $ast $env]
}
set ast [macroexpand $ast $env]
if {![list_q $ast]} {
return $ast
}
lassign [obj_val $ast] a0 a1 a2 a3
switch [obj_val $a0] {
"def!" {
set varname [obj_val $a1]
set value [EVAL $a2 $env]
return [$env set $varname $value]
}
"let*" {
set letenv [Env new $env]
set bindings_list [obj_val $a1]
foreach {varnameobj varvalobj} $bindings_list {
$letenv set [obj_val $varnameobj] [EVAL $varvalobj $letenv]
}
set ast $a2
set env $letenv
# TCO: Continue loop
}
"quote" {
return $a1
}
"quasiquote" {
set ast [quasiquote $a1]
}
"defmacro!" {
set varname [obj_val $a1]
set value [EVAL $a2 $env]
set fn [obj_val $value]
dict set fn is_macro 1
obj_set_val $value $fn
return [$env set $varname $value]
}
"macroexpand" {
return [macroexpand $a1 $env]
}
"tcl*" {
return [string_new [eval [obj_val $a1]]]
}
"try*" {
set res {}
if { [catch { set res [EVAL $a1 $env] } exception] } {
set exc_var [obj_val [lindex [obj_val $a2] 1]]
if {$exception == "__MalException__"} {
set exc_value $::mal_exception_obj
} else {
set exc_value [string_new $exception]
}
set catch_env [Env new $env [list $exc_var] [list $exc_value]]
return [EVAL [lindex [obj_val $a2] 2] $catch_env]
} else {
return $res
}
}
"do" {
set el [list_new [lrange [obj_val $ast] 1 end-1]]
eval_ast $el $env
set ast [lindex [obj_val $ast] end]
# TCO: Continue loop
}
"if" {
set condval [EVAL $a1 $env]
if {[false_q $condval] || [nil_q $condval]} {
if {$a3 == ""} {
return $::mal_nil
}
set ast $a3
} else {
set ast $a2
}
# TCO: Continue loop
}
"fn*" {
set binds {}
foreach v [obj_val $a1] {
lappend binds [obj_val $v]
}
return [function_new $a2 $env $binds]
}
default {
set lst_obj [eval_ast $ast $env]
set lst [obj_val $lst_obj]
set f [lindex $lst 0]
set call_args [lrange $lst 1 end]
switch [obj_type $f] {
function {
set fn [obj_val $f]
set ast [dict get $fn body]
set env [Env new [dict get $fn env] [dict get $fn binds] $call_args]
# TCO: Continue loop
}
nativefunction {
set body [concat [list [obj_val $f]] {$a}]
set lambda [list {a} $body]
return [apply $lambda $call_args]
}
default {
error "Not a function"
}
}
}
}
}
}
proc PRINT exp {
pr_str $exp 1
}
proc REP {str env} {
PRINT [EVAL [READ $str] $env]
}
proc RE {str env} {
EVAL [READ $str] $env
}
proc mal_eval {a} {
global repl_env
EVAL [lindex $a 0] $repl_env
}
set repl_env [Env new]
dict for {k v} $core_ns {
$repl_env set $k $v
}
$repl_env set "eval" [nativefunction_new mal_eval]
set argv_list {}
foreach arg [lrange $argv 1 end] {
lappend argv_list [string_new $arg]
}
$repl_env set "*ARGV*" [list_new $argv_list]
# core.mal: defined using the language itself
RE "(def! *host-language* \"tcl\")" $repl_env
RE "(def! not (fn* (a) (if a false true)))" $repl_env
RE "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" $repl_env
RE "(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)))))))" $repl_env
RE "(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))))))))" $repl_env
fconfigure stdout -translation binary
set DEBUG_MODE 0
if { [array names env DEBUG] != "" && $env(DEBUG) != "0" } {
set DEBUG_MODE 1
}
if {$argc > 0} {
REP "(load-file \"[lindex $argv 0]\")" $repl_env
exit
}
REP "(println (str \"Mal \[\" *host-language* \"\]\"))" $repl_env
# repl loop
while {true} {
set res [_readline "user> "]
if {[lindex $res 0] == "EOF"} {
break
}
set line [lindex $res 1]
if {$line == ""} {
continue
}
if { [catch { puts [REP $line $repl_env] } exception] } {
puts "Error: $exception"
if { $DEBUG_MODE } {
puts $::errorInfo
}
}
}
puts ""

28
tcl/tests/stepA_mal.mal Normal file
View File

@ -0,0 +1,28 @@
;; Testing basic Tcl interop
;;
;; Note that in Tcl "everything is a string", so we don't have enough
;; information to convert the results to other Mal types.
(tcl* "expr {3 ** 4}")
;=>"81"
(tcl* "llength {a b c d}")
;=>"4"
(tcl* "concat {a b} c {d e} f g")
;=>"a b c d e f g"
(tcl* "puts \"hello [expr {5 + 6}] world\"")
; hello 11 world
;=>""
(tcl* "set ::foo 8")
(tcl* "expr {$::foo}")
;=>"8"
(tcl* "proc mult3 {x} { expr {$x * 3} }")
(tcl* "mult3 6")
;=>"18"
(tcl* "string range $::tcl_version 0 1")
;=>"8."

184
tcl/types.tcl Normal file
View File

@ -0,0 +1,184 @@
oo::class create MalObj {
variable type val meta
constructor {obj_type obj_val {obj_meta 0}} {
set type $obj_type
set val $obj_val
set meta $obj_meta
}
method get_type {} {
return $type
}
method get_val {} {
return $val
}
method get_meta {} {
return $meta
}
method set_val {new_val} {
set val $new_val
return $new_val
}
}
proc obj_new {obj_type obj_val {obj_meta 0}} {
MalObj new $obj_type $obj_val $obj_meta
}
proc obj_type {obj} {
$obj get_type
}
proc obj_val {obj} {
$obj get_val
}
proc obj_meta {obj} {
$obj get_meta
}
proc obj_set_val {obj new_val} {
$obj set_val $new_val
}
set ::mal_nil [obj_new "nil" {}]
set ::mal_true [obj_new "true" {}]
set ::mal_false [obj_new "false" {}]
proc nil_q {obj} {
expr {[obj_type $obj] == "nil"}
}
proc false_q {obj} {
expr {[obj_type $obj] == "false"}
}
proc true_q {obj} {
expr {[obj_type $obj] == "true"}
}
proc bool_new {val} {
if {$val == 0} {
return $::mal_false
} else {
return $::mal_true
}
}
proc integer_new {num} {
obj_new "integer" $num
}
proc symbol_new {name} {
obj_new "symbol" $name
}
proc symbol_q {obj} {
expr {[obj_type $obj] == "symbol"}
}
proc string_new {val} {
obj_new "string" $val
}
proc keyword_new {val} {
string_new "\u029E$val"
}
proc keyword_q {obj} {
expr {[obj_type $obj] == "string" && [string index [obj_val $obj] 0] == "\u029E"}
}
proc list_new {lst} {
obj_new "list" $lst $::mal_nil
}
proc list_q {obj} {
expr {[obj_type $obj] == "list"}
}
proc vector_new {lst} {
obj_new "vector" $lst $::mal_nil
}
proc vector_q {obj} {
expr {[obj_type $obj] == "vector"}
}
proc hashmap_new {lst} {
obj_new "hashmap" $lst $::mal_nil
}
proc hashmap_q {obj} {
expr {[obj_type $obj] == "hashmap"}
}
proc sequential_q {obj} {
expr {[list_q $obj] || [vector_q $obj]}
}
proc sequential_equal_q {seq_a seq_b} {
foreach obj_a [obj_val $seq_a] obj_b [obj_val $seq_b] {
if {$obj_a == "" || $obj_b == "" || ![equal_q $obj_a $obj_b]} {
return 0
}
}
return 1
}
proc hashmap_equal_q {hashmap_a hashmap_b} {
set dict_a [obj_val $hashmap_a]
set dict_b [obj_val $hashmap_b]
set keys_a [lsort [dict keys $dict_a]]
set keys_b [lsort [dict keys $dict_b]]
if {$keys_a != $keys_b} {
return 0
}
foreach key $keys_a {
set obj_a [dict get $dict_a $key]
set obj_b [dict get $dict_b $key]
if {![equal_q $obj_a $obj_b]} {
return 0
}
}
return 1
}
proc equal_q {a b} {
if {[sequential_q $a] && [sequential_q $b]} {
sequential_equal_q $a $b
} elseif {[hashmap_q $a] && [hashmap_q $b]} {
hashmap_equal_q $a $b
} else {
expr {[obj_type $a] == [obj_type $b] && [obj_val $a] == [obj_val $b]}
}
}
proc nativefunction_new {name} {
obj_new "nativefunction" $name $::mal_nil
}
proc function_new {body env binds} {
set funcdict [dict create body $body env $env binds $binds is_macro 0]
obj_new "function" $funcdict $::mal_nil
}
proc function_q {obj} {
expr {[obj_type $obj] == "function"}
}
proc macro_q {obj} {
expr {[obj_type $obj] == "function" && [dict get [obj_val $obj] is_macro]}
}
proc atom_new {val} {
obj_new "atom" $val $::mal_nil
}
proc atom_q {obj} {
expr {[obj_type $obj] == "atom"}
}