bus-scheme 0.7.5 → 0.7.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,26 +1,59 @@
1
1
  module BusScheme
2
- class << self
3
- # Parse a string, then eval the result
4
- def eval_string(string)
5
- eval_form(parse(string))
6
- end
2
+ module_function # did not know about this until seeing it in Rubinius; handy!
3
+ SYMBOL_TABLE = {} # top-level scope
4
+ @@stack = []
5
+
6
+ # Parse a string, then eval the result
7
+ def eval_string(string)
8
+ eval(parse("(top-level #{string})"))
9
+ end
7
10
 
8
- # Eval a form passed in as an array
9
- def eval_form(form)
10
- if form.is_a? Array or form.is_a? Cons and form.first
11
- apply(form.first, *form.rest)
12
- elsif form.is_a? Symbol
13
- raise EvalError.new("Undefined symbol: #{form}") unless Lambda.scope.has_key?(form)
14
- Lambda.scope[form]
15
- else # well it must be a literal then
16
- form
17
- end
11
+ # Eval a form passed in as an array
12
+ def eval(form)
13
+ if (form.is_a?(Cons) or form.is_a?(Array)) and form.first
14
+ apply(form.first, form.rest)
15
+ elsif form.is_a? Sym
16
+ self[form.sym]
17
+ else # well it must be a literal then
18
+ form
18
19
  end
20
+ end
19
21
 
20
- # Call a function with given args
21
- def apply(function, *args)
22
- args.map!{ |arg| eval_form(arg) } unless special_form?(function)
23
- eval_form(function).call(*args)
24
- end
22
+ # Call a function with given args
23
+ def apply(function_sym, args)
24
+ args = args.to_a
25
+ function = eval(function_sym)
26
+ args.map!{ |arg| eval(arg) } unless function.special_form
27
+ puts ' ' * stack.length + Cons.new(function_sym, args.sexp).inspect if (@trace ||= false)
28
+
29
+ function.call_as(function_sym, *args)
30
+ end
31
+
32
+ # Scoping methods:
33
+ def current_scope
34
+ @@stack.empty? ? SYMBOL_TABLE : @@stack.last
35
+ end
36
+
37
+ def in_scope?(sym)
38
+ current_scope.has_key?(sym) or SYMBOL_TABLE.has_key?(sym)
39
+ end
40
+
41
+ def [](sym)
42
+ raise EvalError.new("Undefined symbol: #{sym.inspect}") unless in_scope?(sym)
43
+ current_scope[sym]
44
+ end
45
+
46
+ def []=(sym, val)
47
+ current_scope[sym] = val
48
+ end
49
+
50
+ # Tracing methods:
51
+ def stacktrace
52
+ # TODO: notrace is super-duper-hacky!
53
+ @@stack.reverse.map{ |frame| frame.trace if frame.respond_to? :trace }.compact
54
+ end
55
+
56
+ def stack
57
+ @@stack
25
58
  end
26
59
  end
@@ -1,55 +1,65 @@
1
1
  module BusScheme
2
- # The RecursiveHash is needed to store Lambda environments
3
- class RecursiveHash < Hash
4
- # takes a hash and a parent
5
- def initialize(hash, parent)
6
- @parent = parent
7
- hash.each { |k, v| self[k] = v }
2
+ # Lambdas are closures.
3
+ class Lambda < Cons
4
+ attr_reader :scope
5
+ attr_accessor :special_form
6
+
7
+ # create new Lambda object
8
+ def initialize(formals, body)
9
+ @special_form = false
10
+ @formals, @body, @enclosing_scope = [formals, body, BusScheme.current_scope]
11
+ @car = :lambda.sym
12
+ @cdr = Cons.new(@formals.sexp, @body.sexp)
13
+ @called_as = nil # avoid warnings
8
14
  end
9
15
 
10
- alias_method :immediate_has_key?, :has_key?
11
- alias_method :immediate_set, :[]=
12
- alias_method :immediate_lookup, :[]
13
-
14
- def has_key?(symbol)
15
- immediate_has_key?(symbol) or @parent && @parent.has_key?(symbol)
16
- end
16
+ # execute body with args bound to formals
17
+ def call(*args)
18
+ locals = if @formals.is_a? Sym # rest args
19
+ { @formals => args.to_list }
20
+ else # regular arg list
21
+ raise BusScheme::ArgumentError, "Wrong number of args:
22
+ expected #{@formals.size}, got #{args.size}
23
+ #{BusScheme.stacktrace.join("\n")}" if @formals.length != args.length
24
+ @formals.to_a.zip(args).to_hash
25
+ end
17
26
 
18
- def [](symbol)
19
- immediate_lookup(symbol) or @parent && @parent[symbol]
27
+ @frame = StackFrame.new(locals, @enclosing_scope, @called_as)
28
+
29
+ BusScheme.stack.push @frame
30
+ begin
31
+ val = @body.map{ |form| BusScheme.eval(form) }.last
32
+ rescue => e
33
+ raise e
34
+ BusScheme.stack.pop
35
+ end
36
+ BusScheme.stack.pop
37
+ return val
20
38
  end
21
39
 
22
- def []=(symbol, value)
23
- if !immediate_has_key?(symbol) and @parent and @parent.has_key?(symbol)
24
- @parent[symbol] = value
25
- else
26
- immediate_set symbol, value
27
- end
40
+ def call_as(called_as, *args)
41
+ @called_as = called_as
42
+ call(*args)
28
43
  end
29
44
  end
30
45
 
31
- # Lambdas are closures.
32
- class Lambda
33
- @@stack = []
34
-
35
- attr_reader :scope
36
-
37
- # create new lambda object
38
- def initialize(arg_names, body)
39
- @arg_names, @body, @enclosing_scope = [arg_names, body, Lambda.scope]
40
- end
41
-
42
- # execute lambda with given arg_values
43
- def call(*arg_values)
44
- raise BusScheme::ArgumentError if @arg_names.length != arg_values.length
45
- @scope = RecursiveHash.new(@arg_names.zip(arg_values).to_hash, @enclosing_scope)
46
- @@stack << self
47
- BusScheme.eval_form(@body.unshift(:begin)).affect { @@stack.pop }
46
+ class Primitive < Lambda
47
+ def initialize body
48
+ @car = @cdr = nil # avoid "Not initialized" warnings
49
+ @body = body
48
50
  end
49
51
 
50
- # What's the current scope?
51
- def self.scope
52
- @@stack.empty? ? SYMBOL_TABLE : @@stack.last.scope
52
+ def call(*args)
53
+ BusScheme.stack.push StackFrame.new({}, BusScheme.current_scope, @called_as)
54
+ begin
55
+ val = @body.call(*args)
56
+ rescue => e
57
+ BusScheme.stack.pop
58
+ raise e
59
+ end
60
+ BusScheme.stack.pop
61
+ return val
53
62
  end
54
63
  end
55
64
  end
65
+
@@ -1,3 +1,25 @@
1
+ class Sym < String
2
+ attr_accessor :file, :line
3
+
4
+ # TODO: refactor?
5
+ def special_form
6
+ BusScheme[self].special_form
7
+ end
8
+
9
+ def inspect
10
+ self
11
+ end
12
+
13
+ def to_s
14
+ self
15
+ end
16
+
17
+ def sym
18
+ self
19
+ end
20
+ end
21
+
22
+
1
23
  class Object
2
24
  # Return self after evaling block
3
25
  # see http://www.ruby-forum.com/topic/131340
@@ -6,7 +28,42 @@ class Object
6
28
  return self
7
29
  end
8
30
 
9
- def to_sexp
31
+ def sexp(r = false)
10
32
  self
11
33
  end
34
+
35
+ def special_form
36
+ false
37
+ end
38
+ end
39
+
40
+ module Callable
41
+ # allows for (mylist 4) => mylist[4]
42
+ def call_as(sym, *args)
43
+ self.call(*args)
44
+ end
45
+ def call(*args)
46
+ self.[](*args)
47
+ end
48
+ end
49
+
50
+ class String
51
+ include Callable
52
+ def sym
53
+ Sym.new(self)
54
+ end
55
+
56
+ def to_html
57
+ self
58
+ end
59
+ end
60
+
61
+ class Symbol
62
+ def sym
63
+ Sym.new(self.to_s)
64
+ end
65
+ end
66
+
67
+ class Hash
68
+ include Callable
12
69
  end
@@ -1,77 +1,106 @@
1
1
  module BusScheme
2
- class << self
3
- # Turn an input string into an S-expression
4
- def parse(input)
5
- parse_tokens tokenize(input).flatten
6
- end
2
+ INVALID_IDENTIFER_BEGIN = ('0' .. '9').to_a + ['+', '-', '.']
3
+
4
+ module_function
5
+
6
+ # Turn an input string into an S-expression
7
+ def parse(input)
8
+ @@lines = 1
9
+ # TODO: should sexp it as it's being constructed, not after
10
+ parse_tokens(tokenize(input).flatten).sexp(true)
11
+ end
7
12
 
8
- # Turn a list of tokens into a properly-nested S-expression
9
- def parse_tokens(tokens)
10
- token = tokens.shift
11
- if token == :'('
12
- parse_list(tokens)
13
+ # Turn a list of tokens into a properly-nested array
14
+ def parse_tokens(tokens)
15
+ token = tokens.shift
16
+ if token == :'('
17
+ parse_list(tokens)
18
+ else
19
+ raise ParseError unless tokens.empty?
20
+ token # atom
21
+ end
22
+ end
23
+
24
+ # Nest a list from a 1-dimensional list of tokens
25
+ def parse_list(tokens)
26
+ list = []
27
+ while element = tokens.shift and element != :')'
28
+ if element == :'('
29
+ list << parse_list(tokens)
13
30
  else
14
- raise BusScheme::ParseError unless tokens.empty?
15
- token # atom
31
+ list << element
16
32
  end
17
33
  end
34
+ raise IncompleteError unless element == :')'
18
35
 
19
- # Nest a list from a 1-dimensional list of tokens
20
- def parse_list(tokens)
21
- [].affect do |list|
22
- while element = tokens.shift and element != :')'
23
- if element == :'('
24
- list << parse_list(tokens)
25
- else
26
- list << element
27
- end
28
- end
29
- end
30
- end
36
+ parse_dots_into_cons list
37
+ end
31
38
 
32
- # Split an input string into lexically valid tokens
33
- def tokenize(input)
34
- [].affect do |tokens|
35
- while token = pop_token(input)
36
- tokens << token
37
- end
39
+ # Parse a "dotted cons" (1 . 2) into (cons 1 2)
40
+ def parse_dots_into_cons(list)
41
+ if(list && list.length > 0 && list[1] == :'.')
42
+ [:cons.sym, list.first, *list[2 .. -1]]
43
+ else
44
+ list
45
+ end
46
+ end
47
+
48
+ # Split an input string into lexically valid tokens
49
+ def tokenize(input)
50
+ [].affect do |tokens|
51
+ while token = pop_token(input)
52
+ tokens << token
38
53
  end
39
54
  end
55
+ end
40
56
 
41
- # Take a token off the input string and return it
42
- def pop_token(input)
43
- # can't use ^ since it matches line beginnings in mid-string
44
- token = case input
45
- when /\A(\s|;.*$)/ # ignore whitespace and comments
46
- input[0 .. Regexp.last_match[1].length - 1] = ''
47
- return pop_token(input)
48
- when /\A(\(|\))/ # parens
49
- Regexp.last_match[1].intern
50
- when /\A#\(/ # vector
51
- input[0 ... 2] = ''
52
- return [:'(', :vector, tokenize(input)]
53
- when /\A'/ # single-quote
54
- input[0 ... 1] = ''
55
- return [:'(', :quote,
56
- if input[0 ... 1] == '('
57
- tokenize(input)
58
- else
59
- pop_token(input)
60
- end,
61
- :')']
62
- when /\A(-?[0-9]*\.[0-9]+)/ # float
63
- Regexp.last_match[1].to_f
64
- when /\A(-?[0-9]+)/ # integer
65
- Regexp.last_match[1].to_i
66
- when /\A("(.*?)")/ # string
67
- Regexp.last_match[2]
68
- when /\A([^ \n\)]+)/ # symbol
69
- Regexp.last_match[1].intern
70
- end
57
+ # Take a token off the input string and return it
58
+ def pop_token(input)
59
+ # can't use ^ since it matches line beginnings in mid-string
60
+ token = case input
61
+ when /\A(\s|;.*$)/ # ignore whitespace and comments
62
+ @@lines += Regexp.last_match[1].count("\n")
63
+ input[0 .. Regexp.last_match[1].length - 1] = ''
64
+ return pop_token(input)
65
+ when /\A(\(|\))/ # parens
66
+ Regexp.last_match[1].intern
67
+ # when /\A#([^\)])/
68
+ when /\A#\(/ # vector
69
+ input[0 ... 2] = ''
70
+ return [:'(', :vector.sym, tokenize(input)]
71
+ when /\A'/ # single-quote
72
+ input[0 ... 1] = ''
73
+ return [:'(', :quote.sym,
74
+ if input[0 ... 1] == '('
75
+ tokenize(input)
76
+ else
77
+ pop_token(input)
78
+ end,
79
+ :')']
80
+ when /\A(-?\+?[0-9]*\.[0-9]+)/ # float
81
+ Regexp.last_match[1].to_f
82
+ when /\A(\.)/ # dot (for pair notation), comes after float to pick up any dots that float doesn't accept
83
+ :'.'
84
+ when /\A(-?[0-9]+)/ # integer
85
+ Regexp.last_match[1].to_i
86
+ when /\A("(.*?)")/m # string
87
+ Regexp.last_match[2]
88
+ # Official Scheme valid identifiers:
89
+ # when /\A([A-Za-z!\$%&\*\.\/:<=>\?@\^_~][A-Za-z0-9!\$%&\*\+\-\.\/:<=>\?@\^_~]*)/ # symbol
90
+ # when /\A([^-0-9\. \n\)][^ \n\)]*)/
91
+ when /\A([^ \n\)]+)/ # symbols
92
+ # puts "#{Regexp.last_match[1]} - #{@@lines}"
93
+ # cannot begin with a character that may begin a number
94
+ sym = Regexp.last_match[1].sym
95
+ sym.file, sym.line = [BusScheme.loaded_files.last, @@lines]
96
+ raise ParseError, "Invalid identifier: #{sym}" if INVALID_IDENTIFER_BEGIN.include? sym[0 .. 0] and sym.size > 1
97
+ sym
98
+ else
99
+ raise ParseError if input =~ /[^\s ]/
100
+ end
71
101
 
72
- # Remove the matched part from the string
73
- input[0 .. Regexp.last_match[1].length - 1] = '' if token
74
- return token
75
- end
102
+ # Remove the matched part from the string
103
+ input[0 .. Regexp.last_match[1].length - 1] = '' if token
104
+ return token
76
105
  end
77
106
  end
@@ -1,45 +1,65 @@
1
1
  module BusScheme
2
- class BusSchemeError < StandardError; end
3
- class ParseError < BusSchemeError; end
4
- class EvalError < BusSchemeError; end
5
- class ArgumentError < BusSchemeError; end
6
-
7
- PRIMITIVES = {
8
- # right now I believe there are as few things implemented primitively as possible
9
- # except for functions that require splat args. do we need something like &rest?
10
-
11
- '#t'.intern => true, # :'#t' screws up emacs' ruby parser
12
- '#f'.intern => false,
13
-
14
- :+ => lambda { |*args| args.inject { |sum, i| sum + i } },
15
- :- => lambda { |x, y| x - y },
16
- :* => lambda { |*args| args.inject { |product, i| product * i } },
17
- '/'.intern => lambda { |x, y| x / y },
18
-
19
- :concat => lambda { |*args| args.join('') },
20
- :cons => lambda { |car, cdr| Cons.new(car, cdr) },
21
- # todo: lambda args should come as lists by default, not vectors/arrays
22
- :list => lambda { |*members| members.to_list },
23
- :vector => lambda { |*members| members },
24
-
25
- :ruby => lambda { |*code| eval(code.join('')) },
26
- :eval => lambda { |code| eval_form(code) },
27
- :send => lambda { |obj, *message| obj.send(*message) },
28
- :load => lambda { |filename| eval_string("(begin #{File.read(filename)} )") },
29
- :exit => lambda { exit }, :quit => lambda { exit },
30
- }
31
-
32
- # if we add in macros, can some of these be defined in scheme?
33
- SPECIAL_FORMS = {
34
- :quote => lambda { |arg| arg.to_sexp },
35
- :if => lambda { |q, yes, *no| eval_form(q) ? eval_form(yes) : eval_form([:begin] + no) },
36
- :begin => lambda { |*args| args.map{ |arg| eval_form(arg) }.last },
37
- :set! => lambda { |sym, value| raise EvalError.new unless Lambda.scope.has_key?(sym) and
38
- Lambda.scope[sym] = eval_form(value); sym },
39
- :lambda => lambda { |args, *form| Lambda.new(args, form) },
40
- :define => lambda { |sym, definition| Lambda.scope[sym] = eval_form(definition); sym },
41
-
42
- # once we have macros, this can be defined in scheme
43
- :let => lambda { |defs, *body| Lambda.new(defs.map{ |d| d.car }, body).call(*defs.map{ |d| eval_form d.last }) }
44
- }
2
+ def self.define(identifier, value)
3
+ # TODO: fix if this turns out to be a good idea
4
+ value = Primitive.new(value) if value.is_a? Proc
5
+ BusScheme[identifier.sym] = value
6
+ end
7
+
8
+ def self.special_form(identifier, value)
9
+ # TODO: fix if this turns out to be a good idea
10
+ value = Primitive.new(value) if value.is_a? Proc
11
+ value.special_form = true
12
+ BusScheme[identifier.sym] = value
13
+ end
14
+
15
+ define '#t', true
16
+ define '#f', false
17
+
18
+ define '+', lambda { |*args| args.inject { |sum, i| sum + i } }
19
+ define '-', lambda { |x, y| x - y }
20
+ define '*', lambda { |*args| args.inject { |product, i| product * i } }
21
+ define '/', lambda { |x, y| x / y }
22
+
23
+ define 'concat', lambda { |*args| args.join('') }
24
+ define 'cons', lambda { |*args| Cons.new(*args) }
25
+ define 'list', lambda { |*members| members.to_list }
26
+ define 'vector', lambda { |*members| members.to_a }
27
+ define 'map', lambda { |fn, list| list.map(lambda { |n| fn.call(n) }).sexp }
28
+ # TODO: test these
29
+ define 'now', lambda { Time.now }
30
+ define 'regex', lambda { |r| Regexp.new(Regexp.escape(r)) }
31
+
32
+ define 'read', lambda { gets }
33
+ define 'write', lambda { |obj| puts obj.inspect; 0 }
34
+ define 'display', lambda { |obj| puts obj }
35
+
36
+ define 'eval', lambda { |code| eval(code) }
37
+ define 'stacktrace', lambda { BusScheme.stacktrace }
38
+ define 'trace', lambda { @trace = !@trace }
39
+ define 'fail', lambda { |message| raise AssertionFailed, "#{message}\n #{BusScheme.stacktrace.join("\n ")}" }
40
+
41
+ define 'ruby', lambda { |*code| Kernel.eval code.join('') }
42
+ define 'send', lambda { |obj, message, *args| obj.send(message.to_sym, *args) }
43
+
44
+ define 'load', lambda { |filename| BusScheme.load filename }
45
+ define 'exit', lambda { exit }
46
+ define 'quit', BusScheme['exit'.sym]
47
+
48
+ # TODO: hacky to coerce everything to sexps... won't work once we start using vectors
49
+ special_form 'quote', lambda { |arg| arg.sexp }
50
+ special_form 'if', lambda { |q, yes, *no| eval(eval(q) ? yes : [:begin.sym] + no) }
51
+ special_form 'begin', lambda { |*args| args.map{ |arg| eval(arg) }.last }
52
+ special_form 'top-level', BusScheme[:begin.sym]
53
+ special_form 'begin-notrace', lambda { |*args| args.map{ |arg| eval(arg) }.last }
54
+ special_form 'lambda', lambda { |args, *form| Lambda.new(args, form) }
55
+ # TODO: does define always create top-level bindings, or local?
56
+ special_form 'define', lambda { |sym, value| BusScheme::SYMBOL_TABLE[sym] = eval(value); sym }
57
+ special_form 'set!', lambda { |sym, value| raise EvalError.new unless BusScheme.in_scope?(sym)
58
+ BusScheme[sym.sym] = value }
59
+
60
+ # TODO: once we have macros, this can be defined in scheme
61
+ special_form 'and', lambda { |*args| args.all? { |x| eval(x) } }
62
+ special_form 'or', lambda { |*args| args.any? { |x| eval(x) } }
63
+ special_form 'let', lambda { |defs, *body| Lambda.new(defs.map{ |d| d.car }, body).call(*defs.map{ |d| eval d.last }) }
64
+ special_form 'hash', lambda { |*args| args.to_hash } # accepts an alist
45
65
  end