11require_relative "parser"
22
33module Scheme
4+ # Macro table to store defined macros
5+ MACRO_TABLE = { }
6+
47 class Environment
58 def initialize ( params , args , outer_environment = nil )
69 @outer_environment = outer_environment
@@ -29,6 +32,14 @@ def find(key)
2932 raise "Lookup error for #{ key } "
3033 end
3134 end
35+
36+ def unset ( key )
37+ if @env . key? ( key )
38+ @env . delete ( key )
39+ else
40+ raise "Cannot unset non-existent variable #{ key } "
41+ end
42+ end
3243 end
3344
3445 class Procedure
@@ -52,21 +63,223 @@ def call(*args)
5263 :>= => proc { |a , b | a >= b } ,
5364 :< => proc { |a , b | a < b } ,
5465 :<= => proc { |a , b | a <= b } ,
66+ :"=" => proc { |a , b | a == b } ,
5567 :list => proc { |*args | args } ,
5668 :cons => proc { |a , b | [ a ] + b } ,
5769 :car => :first . to_proc ,
5870 :cdr => proc { |a | a [ 1 ..] } ,
59- :append => proc { |*args | args . flatten } ,
71+ :append => proc { |*args | args . flatten ( 1 ) } ,
72+ :display => proc { |a | print a } ,
73+ :inexact => proc { |a | a . to_f } ,
6074 :length => :length . to_proc ,
61- :null? => proc { |a | a . nil? || a == [ ] }
75+ :not => proc { |a | !a } ,
76+ :null? => proc { |a | a . nil? || a . empty? } ,
77+ :pair? => proc { |x | x . is_a? ( Array ) && !x . empty? } ,
78+ :sqrt => proc { |a | Math . sqrt ( a ) }
6279 } . entries . transpose
6380 GLOBAL_ENV = Environment . new ( GLOBAL_DICT [ 0 ] , GLOBAL_DICT [ 1 ] )
6481
82+ # Helper methods for expand
83+ def is_pair ( x )
84+ x . is_a? ( Array ) && !x . empty?
85+ end
86+
87+ def to_string ( x )
88+ case x
89+ when true then "#t"
90+ when false then "#f"
91+ when Symbol then x . to_s
92+ when String then "\" #{ x . gsub ( '"' , '\"' ) } \" "
93+ when Array then "(#{ x . map { |e | to_string ( e ) } . join ( " " ) } )"
94+ else x . to_s
95+ end
96+ end
97+
98+ def require_syntax ( x , predicate , msg = "wrong length" )
99+ raise "#{ to_string ( x ) } : #{ msg } " unless predicate
100+ end
101+
102+ # Main expand function
103+ def expand ( x , toplevel = false )
104+ # Check for empty list
105+ require_syntax ( x , !( x . is_a? ( Array ) && x . empty? ) )
106+
107+ # Constant/non-list => unchanged
108+ return x unless x . is_a? ( Array )
109+
110+ case x . first
111+ when :include
112+ # (include string1 string2 ...)
113+ require_syntax ( x , x . length > 1 )
114+ expand_include ( x )
115+ when :quote
116+ # (quote exp)
117+ require_syntax ( x , x . length == 2 )
118+ x
119+ when :if
120+ # (if test conseq) => (if test conseq nil)
121+ if x . length == 3
122+ x += [ nil ]
123+ end
124+ require_syntax ( x , x . length == 4 )
125+ x . map { |xi | expand ( xi ) }
126+ when :set
127+ # (set! var exp)
128+ require_syntax ( x , x . length == 3 )
129+ var = x [ 1 ]
130+ require_syntax ( x , var . is_a? ( Symbol ) , "can set! only a symbol" )
131+ [ :set! , var , expand ( x [ 2 ] ) ]
132+ when :unset
133+ # (variable-unset! var)
134+ require_syntax ( x , x . length == 2 )
135+ var = x [ 1 ]
136+ require_syntax ( x , var . is_a? ( Symbol ) , "can unset! only a symbol" )
137+ [ :"variable-unset!" , var ]
138+ when :define , :"define-macro"
139+ # Check correct length
140+ require_syntax ( x , x . length >= 3 )
141+
142+ _def , v , body = x [ 0 ] , x [ 1 ] , x [ 2 ..]
143+
144+ if v . is_a? ( Array ) && !v . empty?
145+ # (define (f args) body) => (define f (lambda (args) body))
146+ f , *args = v
147+ expand ( [ _def , f , [ :lambda , args ] + body ] )
148+ else
149+ require_syntax ( x , x . length == 3 , "wrong length in definition" )
150+ require_syntax ( x , v . is_a? ( Symbol ) , "can define only a symbol" )
151+ exp = expand ( x [ 2 ] )
152+
153+ if _def == :"define-macro"
154+ require_syntax ( x , toplevel , "define-macro only allowed at top level" )
155+ proc = evaluate ( exp )
156+ require_syntax ( x , proc . respond_to? ( :call ) , "macro must be a procedure" )
157+ MACRO_TABLE [ v ] = proc
158+ return nil
159+ end
160+
161+ [ :define , v , exp ]
162+ end
163+ when :begin
164+ # (begin exp*)
165+ return nil if x . length == 1
166+ x . map { |xi | expand ( xi , toplevel ) }
167+ when :lambda
168+ # (lambda (vars) exp1 exp2...)
169+ require_syntax ( x , x . length >= 3 )
170+
171+ vars , *body = x [ 1 ..]
172+
173+ # Check that vars is a symbol or list of symbols
174+ is_valid_vars = vars . is_a? ( Symbol ) ||
175+ ( vars . is_a? ( Array ) && vars . all? { |v | v . is_a? ( Symbol ) } )
176+ require_syntax ( x , is_valid_vars , "illegal lambda argument list" )
177+
178+ # Wrap multiple expressions in begin
179+ exp = ( body . length == 1 ) ? body [ 0 ] : [ :begin ] + body
180+ [ :lambda , vars , expand ( exp ) ]
181+ when :quasiquote
182+ # `x => expand_quasiquote(x)
183+ require_syntax ( x , x . length == 2 )
184+ expand_quasiquote ( x [ 1 ] )
185+ when :cond
186+ # (cond (test exp) ...)
187+ expanded_clauses = x [ 1 ..] . map do |clause |
188+ require_syntax ( x , clause . is_a? ( Array ) && clause . length == 2 ,
189+ "Invalid cond clause format" )
190+ [ expand ( clause [ 0 ] ) , expand ( clause [ 1 ] ) ]
191+ end
192+ [ :cond ] + expanded_clauses
193+ else
194+ # Check for macro expansion
195+ if x . first . is_a? ( Symbol ) && MACRO_TABLE . key? ( x . first )
196+ # (m arg...) => macroexpand if m is a macro
197+ expand ( MACRO_TABLE [ x . first ] . call ( *x [ 1 ..] ) , toplevel )
198+ else
199+ # (f arg...) => expand each
200+ x . map { |xi | expand ( xi ) }
201+ end
202+ end
203+ end
204+
205+ # Expand quasiquote expression
206+ def expand_quasiquote ( x )
207+ # 'x => 'x
208+ unless is_pair ( x )
209+ return [ :quote , x ]
210+ end
211+
212+ # Check for invalid splicing
213+ require_syntax ( x , x [ 0 ] != :"unquote-splicing" , "can't splice here" )
214+
215+ if x [ 0 ] == :unquote
216+ # ,x => x
217+ require_syntax ( x , x . length == 2 )
218+ x [ 1 ]
219+ elsif is_pair ( x [ 0 ] ) && x [ 0 ] [ 0 ] == :"unquote-splicing"
220+ # (,@x y) => (append x y)
221+ require_syntax ( x [ 0 ] , x [ 0 ] . length == 2 )
222+ [ :append , x [ 0 ] [ 1 ] , expand_quasiquote ( x [ 1 ..] ) ]
223+ else
224+ # `(x . y) => (cons `x `y)
225+ [ :cons , expand_quasiquote ( x [ 0 ] ) , expand_quasiquote ( x [ 1 ..] ) ]
226+ end
227+ end
228+
229+ # Expand include directive
230+ def expand_include ( x )
231+ result = [ :begin ]
232+
233+ x [ 1 ..] . each do |file_name |
234+ File . open ( file_name , "r" ) do |include_file |
235+ content = include_file . read
236+ include_result = Parser . parse_string ( content )
237+
238+ if include_result
239+ result << expand ( include_result , true )
240+ else
241+ raise SchemeException , "Could not include content of #{ file_name } "
242+ end
243+ end
244+ end
245+
246+ result
247+ end
248+
249+ # Let macro implementation
250+ def let_macro ( *args )
251+ args = [ :let ] + args . to_a
252+ require_syntax ( args , args . length > 2 )
253+
254+ bindings , *body = args [ 1 ..]
255+
256+ # Validate bindings format
257+ valid_bindings = bindings . is_a? ( Array ) &&
258+ bindings . all? { |b | b . is_a? ( Array ) && b . length == 2 && b [ 0 ] . is_a? ( Symbol ) }
259+ require_syntax ( args , valid_bindings , "illegal binding list" )
260+
261+ # Extract variables and values
262+ vars = bindings . map { |b | b [ 0 ] }
263+ vals = bindings . map { |b | b [ 1 ] }
264+
265+ # Create lambda expression
266+ lambda_expr = [ [ :lambda , vars ] + body . map { |b | expand ( b ) } ]
267+
268+ # Add expanded values
269+ lambda_expr + vals . map { |v | expand ( v ) }
270+ end
271+
272+ # Register the let macro
273+ MACRO_TABLE [ :let ] = proc { |*args | let_macro ( *args ) }
274+
275+ # Main evaluation function
65276 def evaluate ( tokens , environment = GLOBAL_ENV )
66277 if tokens . is_a? ( String )
67278 tokens
68279 elsif tokens . is_a? ( Numeric )
69280 tokens
281+ elsif tokens == true || tokens == false
282+ tokens
70283 elsif tokens . is_a? ( Array )
71284 case tokens . first
72285 when :if
@@ -90,6 +303,10 @@ def evaluate(tokens, environment = GLOBAL_ENV)
90303 Procedure . new ( tokens [ 1 ] , tokens [ 2 ] , environment )
91304 when :begin
92305 tokens [ 1 ..] . map { |t | evaluate ( t , environment ) } . last
306+ when :set!
307+ raise "`set!` expected 2 argument, got #{ tokens . length - 1 } " unless tokens . length == 3
308+
309+ environment . find ( tokens [ 1 ] ) [ tokens [ 1 ] ] = evaluate ( tokens [ 2 ] , environment )
93310 else
94311 token = tokens . first
95312 procedure = evaluate ( token , environment )
@@ -102,24 +319,21 @@ def evaluate(tokens, environment = GLOBAL_ENV)
102319 end
103320 end
104321
105- def expand ( tokens )
106- return tokens unless tokens . is_a? ( Array )
107-
108- case tokens . first
109- when :if
110- if tokens . length == 3
111- tokens + [ nil ]
112- else
113- tokens
114- end
115- else
116- tokens . map { |t | expand ( t ) }
117- end
118- end
119-
120322 def evaluate_string ( source )
121- Scheme . evaluate ( Scheme . expand ( Parser . parse_string ( source ) ) )
323+ Scheme . evaluate ( Scheme . expand ( Parser . parse_string ( source ) , true ) )
122324 end
123325
124- module_function :evaluate_string , :evaluate , :expand
326+ module_function :evaluate_string , :evaluate , :expand , :is_pair , :require_syntax ,
327+ :expand_quasiquote , :expand_include , :let_macro , :to_string
328+
329+ # Get current directory
330+ dirname = File . dirname ( __FILE__ )
331+
332+ begin
333+ stdlib = File . join ( dirname , "../ports/stdlib.scm" )
334+ evaluate_string ( File . read ( stdlib ) )
335+ rescue => error
336+ puts "Error loading standard library: #{ error . message } "
337+ exit ( 1 )
338+ end
125339end
0 commit comments