bus-scheme 0.7.5 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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