python: remove extraneous macroexpand call.
[jackhill/mal.git] / powershell / stepA_mal.ps1
CommitLineData
f6146aef
JM
1$ErrorActionPreference = "Stop"
2
3Import-Module $PSScriptRoot/types.psm1
4Import-Module $PSScriptRoot/reader.psm1
5Import-Module $PSScriptRoot/printer.psm1
6Import-Module $PSScriptRoot/env.psm1
7Import-Module $PSScriptRoot/core.psm1
8
9# READ
10function READ([String] $str) {
11 return read_str($str)
12}
13
14# EVAL
15function pair?($ast) {
16 (sequential? $ast) -and $ast.values.Count -gt 0
17}
18
19function quasiquote($ast) {
20 if (-not (pair? $ast)) {
21 return (new-list @((new-symbol "quote"), $ast))
22 } else {
23 $a0 = $ast.nth(0)
24 if ((symbol? $a0) -and $a0.value -ceq "unquote") {
25 return $ast.nth(1)
26 } elseif (pair? $a0) {
27 $a00 = $a0.nth(0)
28 if ((symbol? $a00) -and $a00.value -ceq "splice-unquote") {
29 return (new-list @((new-symbol "concat"),
30 $a0.nth(1),
31 (quasiquote $ast.rest())))
32 }
33 }
34 return (new-list @((new-symbol "cons"),
35 (quasiquote $a0),
36 (quasiquote $ast.rest())))
37 }
38}
39
40function macro?($ast, $env) {
41 return (list? $ast) -and
42 (symbol? $ast.nth(0)) -and
43 $env.find($ast.nth(0)) -and
44 $env.get($ast.nth(0)).macro
45}
46
47function macroexpand($ast, $env) {
48 while (macro? $ast $env) {
49 $mac = $env.get($ast.nth(0)).fn
50 $margs = $ast.rest().values
51 $ast = &$mac @margs
52 }
53 return $ast
54}
55
56function eval_ast($ast, $env) {
57 if ($ast -eq $null) { return $ast }
58 switch ($ast.GetType().Name) {
59 "Symbol" { return $env.get($ast) }
60 "List" { return new-list ($ast.values | ForEach { EVAL $_ $env }) }
61 "Vector" { return new-vector ($ast.values | ForEach { EVAL $_ $env }) }
62 "HashMap" {
63 $hm = new-hashmap @()
64 foreach ($k in $ast.values.Keys) {
65 $hm.values[$k] = EVAL $ast.values[$k] $env
66 }
67 return $hm
68 }
69 default { return $ast }
70 }
71}
72
73function EVAL($ast, $env) {
74 while ($true) {
75 #Write-Host "EVAL $(pr_str $ast)"
76 if (-not (list? $ast)) {
77 return (eval_ast $ast $env)
78 }
79
80 $ast = (macroexpand $ast $env)
81 if (-not (list? $ast)) {
82 return (eval_ast $ast $env)
83 }
84 if (empty? $ast) { return $ast }
85
86 $a0, $a1, $a2 = $ast.nth(0), $ast.nth(1), $ast.nth(2)
87 switch -casesensitive ($a0.value) {
88 "def!" {
89 return $env.set($a1, (EVAL $a2 $env))
90 }
91 "let*" {
92 $let_env = new-env $env
93 for ($i=0; $i -lt $a1.values.Count; $i+=2) {
94 $_ = $let_env.set($a1.nth($i), (EVAL $a1.nth(($i+1)) $let_env))
95 }
96 $env = $let_env
97 $ast = $a2 # TCO
98 }
99 "quote" {
100 return $a1
101 }
102 "quasiquote" {
103 $ast = quasiquote $a1
104 }
105 "defmacro!" {
106 $m = EVAL $a2 $env
107 $m.macro = $true
108 return $env.set($a1, $m)
109 }
110 "macroexpand" {
111 return (macroexpand $a1 $env)
112 }
113 "try*" {
114 try {
115 return EVAL $a1 $env
116 } catch {
117 if ($a2 -and ($a2.nth(0).value -ceq "catch*")) {
118 if ($_.Exception.GetType().Name -eq "MalException") {
119 $e = @($_.Exception.object)
120 } else {
121 $e = @($_.Exception.Message)
122 }
123 return (EVAL $a2.nth(2) (new-env $env @($a2.nth(1)) $e))
124 } else {
125 throw
126 }
127 }
128 }
129 "do" {
130 if ($ast.values.Count -gt 2) {
131 $middle = new-list $ast.values[1..($ast.values.Count-2)]
132 $_ = eval_ast $middle $env
133 }
134 $ast = $ast.last() # TCO
135 }
136 "if" {
137 $cond = (EVAL $a1 $env)
138 if ($cond -eq $null -or
139 ($cond -is [Boolean] -and $cond -eq $false)) {
140 $ast = $ast.nth(3) # TCO
141 } else {
142 $ast = $a2 # TCO
143 }
144 }
145 "fn*" {
146 # Save EVAL into a variable that will get closed over
147 $feval = Get-Command EVAL
148 $fn = {
149 return (&$feval $a2 (new-env $env $a1.values $args))
150 }.GetNewClosure()
151 return new-malfunc $a2 $a1.values $env $fn
152 }
153 default {
154 $el = (eval_ast $ast $env)
155 $f, $fargs = $el.first(), $el.rest().values
156 if (malfunc? $f) {
157 $env = (new-env $f.env $f.params $fargs)
158 $ast = $f.ast # TCO
159 } else {
160 return &$f @fargs
161 }
162 }
163 }
164 }
165}
166
167# PRINT
168function PRINT($exp) {
169 return pr_str $exp $true
170}
171
172# REPL
173$repl_env = new-env
174
175function REP([String] $str) {
176 return PRINT (EVAL (READ $str) $repl_env)
177}
178
179# core.EXT: defined using PowerShell
180foreach ($kv in $core_ns.GetEnumerator()) {
181 $_ = $repl_env.set((new-symbol $kv.Key), $kv.Value)
182}
183$_ = $repl_env.set((new-symbol "eval"), { param($a); (EVAL $a $repl_env) })
184$_ = $repl_env.set((new-symbol "*ARGV*"), (new-list $args[1..$args.Count]))
185
186# core.mal: defined using the language itself
187$_ = REP('(def! *host-language* "powershell")')
188$_ = REP('(def! not (fn* (a) (if a false true)))')
189$_ = REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))')
190$_ = REP("(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)))))))")
191$_ = REP('(def! *gensym-counter* (atom 0))')
192$_ = REP('(def! gensym (fn* [] (symbol (str "G__" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))')
193$_ = REP('(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)))))))))')
194
195
196if ($args.Count -gt 0) {
197 $_ = REP('(load-file "' + $args[0] + '")')
198 exit 0
199}
200
201$_ = REP('(println (str "Mal [" *host-language* "]"))')
202while ($true) {
203 Write-Host "user> " -NoNewline
204 $line = [Console]::ReadLine()
205 if ($line -eq $null) {
206 break
207 }
208 try {
209 Write-Host (REP($line))
210 } catch {
211 if ($_.Exception.GetType().Name -eq "MalException") {
212 Write-Host "Exception: $(pr_str $_.Exception.object)"
213 } else {
214 Write-Host "Exception: $($_.Exception.Message)"
215 }
216 }
217}