bus-scheme 0.7.1 → 0.7.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,13 +5,17 @@ Rakefile
5
5
  bin/bus
6
6
  lib/array_extensions.rb
7
7
  lib/bus_scheme.rb
8
+ lib/cons.rb
8
9
  lib/eval.rb
9
10
  lib/lambda.rb
10
- lib/definitions.rb
11
11
  lib/object_extensions.rb
12
12
  lib/parser.rb
13
+ lib/primitives.rb
14
+ lib/scheme/core.scm
13
15
  test/foo.scm
16
+ test/test_core.rb
14
17
  test/test_eval.rb
15
18
  test/test_helper.rb
16
19
  test/test_lambda.rb
17
20
  test/test_parser.rb
21
+ test/test_primitives.rb
data/README.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  Bus Scheme
2
- by Phil Hagelberg (c) 2007
2
+ by Phil Hagelberg (c) 2007 - 2008
3
3
  http://bus-scheme.rubyforge.org
4
4
 
5
5
  == Description
@@ -39,18 +39,27 @@ $ bus foo.scm # load a file
39
39
 
40
40
  Bus Scheme is currently missing pieces of functionality:
41
41
 
42
- * move as many definitions into Scheme as possible
43
- * compile to Rubinius bytecode
44
- * optimize tail call recursion
45
- * continuations (?!?)
46
- * check for memory leaks
47
- * parse cons cells
48
- * parse character literals
49
- * parse quote, unquote
42
+ === Parser
43
+ * parse character literals
44
+ ** alternate define lambda forms
45
+ ** parse dotted cons cells
46
+ *** look up and enforce rules for identifier names
47
+
48
+ === General
49
+ * lambda args should get passed in as lists by default, not vectors/arrays
50
+ * (lambda args body) for rest args
51
+ * stack traces on error plz
52
+ ** optimize tail call recursion
53
+ ** some kind of load path?
50
54
 
51
55
  Failing tests for some of these are already included (commented out,
52
56
  mostly) in the relevant test files.
53
57
 
58
+ === Long Term (post 1.0)
59
+ * continuations (?!?)
60
+ * compile to Rubinius bytecode
61
+ * parse rationals, scientific, complex, and polar complex numbers
62
+
54
63
  == Requirements
55
64
 
56
65
  Bus Scheme should run on (at least) Ruby 1.8, Ruby 1.9, and Rubinius.
data/Rakefile CHANGED
@@ -15,11 +15,25 @@ Hoe.new('bus-scheme', BusScheme::VERSION) do |p|
15
15
  p.remote_rdoc_dir = ''
16
16
  end
17
17
 
18
+ desc "Code statistics"
18
19
  task :stats do
19
20
  require 'code_statistics'
20
21
  CodeStatistics.new(['lib'], ['Unit tests', 'test']).to_s
21
22
  end
22
23
 
24
+ desc "Complexity statistics"
23
25
  task :flog do
24
26
  system "flog lib/*rb"
25
27
  end
28
+
29
+ desc "Show todo items"
30
+ task :todo do
31
+ puts File.read('README.txt').match(/== Todo(.*)== Requirements/m)[1].split("\n").grep(/^( \*|===)/).join("\n")
32
+ end
33
+
34
+ desc "Show tests that have been commented out"
35
+ task :commented_tests do
36
+ Dir.glob('test/test_*.rb').each do |file|
37
+ puts File.read(file).grep(/^\s*#\s*def (test_[^ ]*)/)
38
+ end
39
+ end
@@ -6,6 +6,16 @@ class Array
6
6
 
7
7
  alias_method :car, :first
8
8
  alias_method :cdr, :rest
9
+
10
+ def to_list
11
+ if self.cdr.empty?
12
+ BusScheme::Cons.new(self.car, nil)
13
+ else
14
+ BusScheme::Cons.new(self.car, self.cdr.to_list)
15
+ end
16
+ end
17
+
18
+ alias_method :to_sexp, :to_list
9
19
  end
10
20
 
11
21
  module Enumerable # for 1.9, zip is defined on Enumerable
@@ -11,32 +11,16 @@ require 'object_extensions'
11
11
  require 'array_extensions'
12
12
  require 'parser'
13
13
  require 'eval'
14
- require 'definitions'
14
+ require 'primitives'
15
+ require 'cons'
15
16
  require 'lambda'
16
17
 
17
18
  module BusScheme
18
- VERSION = "0.7.1"
19
+ VERSION = "0.7.5"
19
20
 
20
21
  SYMBOL_TABLE = {}.merge(PRIMITIVES).merge(SPECIAL_FORMS)
21
22
  PROMPT = '> '
22
23
 
23
- # what scope is appropraite for this symbol
24
- def self.scope_of(symbol)
25
- [Lambda.scope, SYMBOL_TABLE].compact.detect { |scope| scope.has_key?(symbol) }
26
- end
27
-
28
- # symbol lookup
29
- def self.[](symbol)
30
- scope = scope_of(symbol)
31
- raise EvalError.new("Undefined symbol: #{symbol}") unless scope
32
- scope && scope[symbol]
33
- end
34
-
35
- # symbol assignment to value
36
- def self.[]=(symbol, value)
37
- (scope_of(symbol) || Lambda.scope || SYMBOL_TABLE)[symbol] = value
38
- end
39
-
40
24
  # symbol special form predicate
41
25
  def self.special_form?(symbol)
42
26
  SPECIAL_FORMS.has_key?(symbol)
@@ -46,7 +30,9 @@ module BusScheme
46
30
  def self.repl
47
31
  loop do
48
32
  puts begin
49
- BusScheme.eval_string(Readline.readline(PROMPT))
33
+ input = Readline.readline(PROMPT)
34
+ exit if input.nil? # only Ctrl-D produces nil here it seems
35
+ BusScheme.eval_string input
50
36
  rescue Interrupt
51
37
  'Type "(quit)" to leave Bus Scheme.'
52
38
  rescue BusSchemeError => e
@@ -57,4 +43,6 @@ module BusScheme
57
43
  end
58
44
  end
59
45
  end
46
+
47
+ ['core'].each { |file| SYMBOL_TABLE[:load].call("#{File.dirname(__FILE__)}/scheme/#{file}.scm") }
60
48
  end
@@ -0,0 +1,41 @@
1
+ module BusScheme
2
+ class Cons
3
+ attr_accessor :car, :cdr
4
+
5
+ def initialize(car, cdr = nil)
6
+ @car, @cdr = [car, cdr]
7
+ end
8
+
9
+ def ==(other)
10
+ @car == other.car and @cdr == other.cdr
11
+ end
12
+
13
+ alias_method :first, :car
14
+ alias_method :rest, :cdr
15
+
16
+ def to_a
17
+ if @cdr.respond_to? :to_a
18
+ [@car] + @cdr.to_a
19
+ elsif !@cdr.nil?
20
+ [@car, @cdr]
21
+ else
22
+ [@car]
23
+ end
24
+ end
25
+
26
+ def inspect(open = '(', close = ')')
27
+ str = open + @car.inspect
28
+ if @cdr.nil?
29
+ str + close
30
+ elsif @cdr.is_a? Cons
31
+ str + ' ' + @cdr.inspect('', '') + close
32
+ else
33
+ str + ' . ' + @cdr.inspect + close
34
+ end
35
+ end
36
+ end
37
+
38
+ def cons(car, cdr = nil)
39
+ Cons.new(car, cdr)
40
+ end
41
+ end
@@ -7,12 +7,11 @@ module BusScheme
7
7
 
8
8
  # Eval a form passed in as an array
9
9
  def eval_form(form)
10
- if form == []
11
- nil
12
- elsif form.is_a? Array
10
+ if form.is_a? Array or form.is_a? Cons and form.first
13
11
  apply(form.first, *form.rest)
14
12
  elsif form.is_a? Symbol
15
- BusScheme[form]
13
+ raise EvalError.new("Undefined symbol: #{form}") unless Lambda.scope.has_key?(form)
14
+ Lambda.scope[form]
16
15
  else # well it must be a literal then
17
16
  form
18
17
  end
@@ -1,30 +1,34 @@
1
1
  module BusScheme
2
- class Scope < Hash
3
- def initialize(table, parent)
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)
4
6
  @parent = parent
5
- table.each { |k, v| self[k] = v }
7
+ hash.each { |k, v| self[k] = v }
6
8
  end
7
9
 
8
- alias_method :old_has_key?, :has_key?
10
+ alias_method :immediate_has_key?, :has_key?
11
+ alias_method :immediate_set, :[]=
12
+ alias_method :immediate_lookup, :[]
13
+
9
14
  def has_key?(symbol)
10
- old_has_key?(symbol) or @parent && @parent.has_key?(symbol)
15
+ immediate_has_key?(symbol) or @parent && @parent.has_key?(symbol)
11
16
  end
12
17
 
13
- alias_method :lookup, :[]
14
18
  def [](symbol)
15
- lookup(symbol) or @parent && @parent[symbol]
19
+ immediate_lookup(symbol) or @parent && @parent[symbol]
16
20
  end
17
21
 
18
- alias_method :old_set, :[]=
19
22
  def []=(symbol, value)
20
- if !old_has_key?(symbol) and @parent and @parent.has_key?(symbol)
23
+ if !immediate_has_key?(symbol) and @parent and @parent.has_key?(symbol)
21
24
  @parent[symbol] = value
22
25
  else
23
- old_set symbol, value
26
+ immediate_set symbol, value
24
27
  end
25
28
  end
26
29
  end
27
30
 
31
+ # Lambdas are closures.
28
32
  class Lambda
29
33
  @@stack = []
30
34
 
@@ -38,13 +42,14 @@ module BusScheme
38
42
  # execute lambda with given arg_values
39
43
  def call(*arg_values)
40
44
  raise BusScheme::ArgumentError if @arg_names.length != arg_values.length
41
- @scope = Scope.new(@arg_names.zip(arg_values).to_hash, @enclosing_scope)
45
+ @scope = RecursiveHash.new(@arg_names.zip(arg_values).to_hash, @enclosing_scope)
42
46
  @@stack << self
43
- BusScheme[:begin].call(*@body).affect { @@stack.pop }
47
+ BusScheme.eval_form(@body.unshift(:begin)).affect { @@stack.pop }
44
48
  end
45
49
 
50
+ # What's the current scope?
46
51
  def self.scope
47
- @@stack.empty? ? nil : @@stack.last.scope
52
+ @@stack.empty? ? SYMBOL_TABLE : @@stack.last.scope
48
53
  end
49
54
  end
50
55
  end
@@ -5,4 +5,8 @@ class Object
5
5
  yield self
6
6
  return self
7
7
  end
8
+
9
+ def to_sexp
10
+ self
11
+ end
8
12
  end
@@ -2,7 +2,7 @@ module BusScheme
2
2
  class << self
3
3
  # Turn an input string into an S-expression
4
4
  def parse(input)
5
- parse_tokens tokenize(normalize_whitespace(input))
5
+ parse_tokens tokenize(input).flatten
6
6
  end
7
7
 
8
8
  # Turn a list of tokens into a properly-nested S-expression
@@ -40,30 +40,38 @@ module BusScheme
40
40
 
41
41
  # Take a token off the input string and return it
42
42
  def pop_token(input)
43
+ # can't use ^ since it matches line beginnings in mid-string
43
44
  token = case input
44
- when /^ +/ # whitespace
45
- input[0 ... 1] = ''
45
+ when /\A(\s|;.*$)/ # ignore whitespace and comments
46
+ input[0 .. Regexp.last_match[1].length - 1] = ''
46
47
  return pop_token(input)
47
- when /^\(/ # open paren
48
- :'('
49
- when /^\)/ # closing paren
50
- :')'
51
- when /^(\d+)/ # positive integer
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
52
65
  Regexp.last_match[1].to_i
53
- when /^"(.*?)"/ # string
54
- Regexp.last_match[1]
55
- when /^([^ \)]+)/ # symbol
66
+ when /\A("(.*?)")/ # string
67
+ Regexp.last_match[2]
68
+ when /\A([^ \n\)]+)/ # symbol
56
69
  Regexp.last_match[1].intern
57
70
  end
58
- # compensate for quotation marks
59
- length = token.is_a?(String) ? token.length + 2 : token.to_s.length
60
- input[0 .. length - 1] = ''
61
- return token
62
- end
63
71
 
64
- # Treat all whitespace in a string as spaces
65
- def normalize_whitespace(string)
66
- string && string.gsub(/\t/, ' ').gsub(/\n/, ' ').gsub(/ +/, ' ')
72
+ # Remove the matched part from the string
73
+ input[0 .. Regexp.last_match[1].length - 1] = '' if token
74
+ return token
67
75
  end
68
76
  end
69
77
  end
@@ -0,0 +1,45 @@
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
+ }
45
+ end
@@ -0,0 +1,35 @@
1
+ (define intern
2
+ (lambda (sym) (send sym (quote intern))))
3
+
4
+ (define substring
5
+ (lambda (string to from) (send string (quote []) to from)))
6
+
7
+ (define null?
8
+ (lambda (expr) (= expr ())))
9
+
10
+ (define >
11
+ (lambda (x y) (send x (intern ">") y)))
12
+
13
+ (define <
14
+ (lambda (x y) (send x (intern "<") y)))
15
+
16
+ (define =
17
+ (lambda (x y) (send x (intern "==") y)))
18
+
19
+ (define and
20
+ (lambda (x y) (if x (if y y #f) #f)))
21
+
22
+ (define or
23
+ (lambda (x y) (if x x (if y y #f))))
24
+
25
+ (define not
26
+ (lambda (expr) (if expr #f #t)))
27
+
28
+ (define car
29
+ (lambda (lst) (send lst (quote first))))
30
+
31
+ (define cdr
32
+ (lambda (lst) (send lst (quote rest))))
33
+
34
+ (define cadr
35
+ (lambda (lst) (car (cdr lst))))
@@ -0,0 +1,43 @@
1
+ $LOAD_PATH << File.dirname(__FILE__)
2
+ require 'test_helper'
3
+
4
+ class CoreTest < Test::Unit::TestCase
5
+ def test_comparison
6
+ assert_evals_to true, "(null? ())"
7
+ assert_evals_to false, "(null? 43)"
8
+ assert_evals_to true, "(> 4 2)"
9
+ assert_evals_to false, "(> 9 13)"
10
+ assert_evals_to true, "(= 4 4)"
11
+ assert_evals_to false, "(= (+ 2 2) 5)"
12
+ assert_evals_to true, "(not #f)"
13
+ assert_evals_to false, "(not #t)"
14
+ end
15
+
16
+ def test_string_functions
17
+ assert_evals_to :hi, [:intern, 'hi']
18
+ assert_evals_to 'lo', [:substring, 'hello', 3, 5]
19
+ end
20
+
21
+ def test_list_functions
22
+ assert_evals_to :foo, "(car (cons (quote foo) (quote bar)))"
23
+ assert_evals_to :bar, "(cdr (cons (quote foo) (quote bar)))"
24
+ assert_equal(BusScheme::Cons.new(:foo, BusScheme::Cons.new(:bar, nil)),
25
+ [:foo, :bar].to_list)
26
+ assert_evals_to(BusScheme::Cons.new(2, BusScheme::Cons.new(3, nil)),
27
+ "(list 2 3)")
28
+ assert_evals_to "bar", "(cadr (list \"foo\" \"bar\")"
29
+ assert_evals_to [1, :foo].to_list, "(list 1 'foo)"
30
+ end
31
+
32
+ def test_boolean_logic
33
+ assert_evals_to true, "(and #t #t)"
34
+ assert_evals_to false, "(and #t #f)"
35
+ assert_evals_to false, "(and #f #t)"
36
+ assert_evals_to false, "(and #f #f)"
37
+
38
+ assert_evals_to true, "(or #t #t)"
39
+ assert_evals_to true, "(or #t #f)"
40
+ assert_evals_to true, "(or #f #t)"
41
+ assert_evals_to false, "(or #f #f)"
42
+ end
43
+ end
@@ -3,7 +3,8 @@ require 'test_helper'
3
3
 
4
4
  class BusSchemeEvalTest < Test::Unit::TestCase
5
5
  def test_eval_empty_list
6
- assert_evals_to nil, []
6
+ assert_evals_to [], []
7
+ assert_evals_to true, "(if () #t #f)"
7
8
  end
8
9
 
9
10
  def test_eval_number
@@ -11,12 +12,12 @@ class BusSchemeEvalTest < Test::Unit::TestCase
11
12
  end
12
13
 
13
14
  def test_set_symbol
14
- BusScheme[:hi] = 'hi'
15
- assert BusScheme::SYMBOL_TABLE[:hi]
15
+ BusScheme::Lambda.scope[:hi] = 'hi'
16
+ assert BusScheme::Lambda.scope[:hi]
16
17
  end
17
18
 
18
19
  def test_eval_symbol
19
- eval "(define hi 13)"
20
+ BusScheme::Lambda.scope[:hi] = 13
20
21
  assert_evals_to 13, :hi
21
22
  end
22
23
 
@@ -28,49 +29,6 @@ class BusSchemeEvalTest < Test::Unit::TestCase
28
29
  assert_evals_to 2, [:+, 1, 1]
29
30
  end
30
31
 
31
- def test_many_args_for_arithmetic
32
- assert_evals_to 4, [:+, 1, 1, 1, 1]
33
- assert_evals_to 2, [:*, 1, 2, 1, 1]
34
- end
35
-
36
- def test_arithmetic
37
- assert_evals_to 2, [:'-', 4, 2]
38
- assert_evals_to 2, [:'/', 4, 2]
39
- assert_evals_to 2, [:'*', 1, 2]
40
- end
41
-
42
- def test_define
43
- clear_symbols :foo
44
- eval("(define foo 5)")
45
- assert_equal 5, BusScheme[:foo]
46
- eval("(define foo (quote (5 5 5))")
47
- assert_evals_to [5, 5, 5], :foo
48
- end
49
-
50
- def test_define_returns_defined_term
51
- assert_evals_to :foo, "(define foo 2)"
52
- assert_equal 2, eval("foo")
53
- end
54
-
55
- def test_string_primitives
56
- assert_evals_to :hi, [:intern, 'hi']
57
- assert_evals_to 'lo', [:substring, 'hello', 3, -1]
58
- end
59
-
60
- def test_booleans
61
- assert BusScheme::PRIMITIVES.has_key? '#f'.intern
62
- assert_evals_to false, '#f'
63
- assert_evals_to true, '#t'
64
- end
65
-
66
- def test_eval_quote
67
- assert_evals_to [:'+', 2, 2], [:quote, [:'+', 2, 2]]
68
- end
69
-
70
- def test_quote
71
- assert_evals_to :hi, [:quote, :hi]
72
- end
73
-
74
32
  def test_nested_arithmetic
75
33
  assert_evals_to 6, [:+, 1, [:+, 1, [:*, 2, 2]]]
76
34
  end
@@ -85,33 +43,8 @@ class BusSchemeEvalTest < Test::Unit::TestCase
85
43
  assert_evals_to 21, [:*, 3, :foo]
86
44
  end
87
45
 
88
- def test_if
89
- assert_evals_to 7, [:if, '#f'.intern, 3, 7]
90
- assert_evals_to 3, [:if, [:>, 8, 2], 3, 7]
91
- end
92
-
93
- def test_begin
94
- eval([:begin,
95
- [:define, :foo, 779],
96
- 9])
97
- assert_equal 779, BusScheme[:foo]
98
- end
99
-
100
- def test_set!
101
- clear_symbols(:foo)
102
- # can only set! existing variables
103
- assert_raises(BusScheme::EvalError) { eval "(set! foo 7)" }
104
- eval "(define foo 3)"
105
- eval "(set! foo 7)"
106
- assert_evals_to 7, :foo
107
- end
108
-
109
- def test_load_file
110
- eval "(load \"#{File.dirname(__FILE__)}/foo.scm\")"
111
- assert_evals_to 3, :foo
112
- end
113
-
114
- def test_eval_ruby
115
- assert_evals_to "foofoofoo", "(ruby \"'foo' * 3\")"
46
+ def test_single_quote
47
+ assert_evals_to :foo, "'foo"
48
+ assert_evals_to [:foo, :biz, :bbb].to_list, "'(foo biz bbb)"
116
49
  end
117
50
  end
@@ -1,10 +1,3 @@
1
- begin
2
- require 'rubygems'
3
- gem 'miniunit'
4
- rescue LoadError
5
- puts "Proceeding with standard test/unit instead of miniunit."
6
- end
7
-
8
1
  $LOAD_PATH << File.dirname(__FILE__) + '/../lib/'
9
2
  require 'test/unit'
10
3
  require 'bus_scheme'
@@ -13,7 +13,7 @@ class BusSchemeLambdaTest < Test::Unit::TestCase
13
13
  assert_equal [], l.arg_names
14
14
 
15
15
  eval("(define foo (lambda () (+ 1 1)))")
16
- assert BusScheme[:foo].is_a?(BusScheme::Lambda)
16
+ assert BusScheme::Lambda.scope[:foo].is_a?(BusScheme::Lambda)
17
17
  assert_evals_to 2, [:foo]
18
18
  end
19
19
 
@@ -34,9 +34,9 @@ class BusSchemeLambdaTest < Test::Unit::TestCase
34
34
  def test_lambda_args_dont_stay_in_scope
35
35
  clear_symbols(:x, :foo)
36
36
  eval("(define foo (lambda (x) (+ x 1)))")
37
- assert_nil BusScheme.scope_of(:x)
37
+ assert_nil BusScheme::Lambda.scope[:x]
38
38
  assert_evals_to 2, [:foo, 1]
39
- assert_nil BusScheme.scope_of(:x)
39
+ assert_nil BusScheme::Lambda.scope[:x]
40
40
  end
41
41
 
42
42
  def test_lambda_calls_lambda
@@ -63,39 +63,61 @@ class BusSchemeParserTest < Test::Unit::TestCase
63
63
  end
64
64
 
65
65
  def test_whitespace_indifferent
66
- assert_parses_equal "(+ 2 2)", "(+ 2 \n \t 2)"
67
- end
68
-
69
- # def test_parses_dotted_cons
70
- # assert_parses_to "(22 . 11)", [:cons, 22, 11]
71
- # assert_parses_to "((+ 2 2) . 11)", [:cons, [:+, 2, 2], 11]
72
- # end
73
-
74
- # def test_floats
75
- # assert_parses_to "44.9", 44.9
76
- # assert_parses_to "0.22", 0.22
77
- # assert_parses_to ".22", 0.22
78
- # assert_parses_to "2.220", 2.22
79
- # end
80
-
81
- # def test_negative_numbers
82
- # assert_parses_to "-1", -1
83
- # assert_parses_to "-0", 0
84
- # assert_parses_to "-02", -2
85
- # end
86
-
87
- # def test_negative_floats
88
- # assert_parses_to "-0.22", -0.22
89
- # assert_parses_to "-.22", -0.22
90
- # assert_parses_to "-0.10", -0.1
91
- # end
92
-
93
- # def test_character_literals
94
- # assert_parses_to "?e", "e"
95
- # assert_parses_to "?A", "A"
96
- # # what else?
97
- # end
66
+ assert_equal 3, BusScheme.pop_token("3 2\n2")
67
+ assert_parses_equal "(+ 2 2)", "(+ 2 2)", "confused by spaces"
68
+ assert_parses_equal "(+ 2 2)", "(+ 2 \t 2)", "confused by tab"
69
+ assert_parses_equal "(+ 2 2)", "(+ 2\n2)", "confused by newline"
70
+ end
71
+
72
+ def test_parses_vectors
73
+ assert_equal [:'(', :vector, 1, 2, :')'], BusScheme::tokenize("#(1 2)").flatten
74
+ assert_parses_to "#(1 2)", [:vector, 1, 2]
75
+ assert_parses_to "#(1 (2 3 4)", [:vector, 1, [2, 3, 4]]
76
+ end
77
+
78
+ # def test_parses_dotted_cons
79
+ # assert_parses_to "(22 . 11)", [:cons, 22, 11]
80
+ # assert_parses_to "((+ 2 2) . 11)", [:cons, [:+, 2, 2], 11]
81
+ # end
82
+
83
+ def test_floats
84
+ assert_parses_to "44.9", 44.9
85
+ assert_parses_to "0.22", 0.22
86
+ assert_parses_to ".22", 0.22
87
+ assert_parses_to "2.220", 2.22
88
+ end
89
+
90
+ def test_negative_ints
91
+ assert_parses_to "-1", -1
92
+ assert_parses_to "-0", 0
93
+ assert_parses_to "-02", -2
94
+ end
98
95
 
96
+ def test_negative_floats
97
+ assert_parses_to "-0.22", -0.22
98
+ assert_parses_to "-.22", -0.22
99
+ assert_parses_to "-0.10", -0.1
100
+ end
101
+
102
+ # def test_character_literals
103
+ # assert_parses_to "?#e", "e"
104
+ # assert_parses_to "?#A", "A"
105
+ # end
106
+
107
+ def test_quote
108
+ assert_parses_to "'foo", [:quote, :foo]
109
+ assert_equal [:'(', :quote, :'(', :foo, :bar, :baz, :')', :')'], BusScheme::tokenize("'(foo bar baz)").flatten
110
+ assert_parses_to "'(foo bar baz)", [:quote, [:foo, :bar, :baz]]
111
+ end
112
+
113
+ # have to change normalize_whitespace to not turn newlines into spaces for this to work
114
+ def test_ignore_comments
115
+ assert_parses_to ";; hello", nil
116
+ assert_parses_to "12 ;; comment", 12
117
+ assert_parses_to "(+ 2;; this is a mid-sexp comment
118
+ 2)", [:+, 2, 2]
119
+ end
120
+
99
121
  def test_parse_random_elisp_form_from_my_dot_emacs
100
122
  lisp = "(let ((system-specific-config
101
123
  (concat \"~/.emacs.d/\"
@@ -108,7 +130,7 @@ class BusSchemeParserTest < Test::Unit::TestCase
108
130
  [:'shell-command-to-string', "hostname"]]]],
109
131
  [:if, [:'file-exists-p', :'system-specific-config'],
110
132
  [:load, :'system-specific-config']]])
111
- end
133
+ end
112
134
 
113
135
  private
114
136
 
@@ -116,7 +138,7 @@ class BusSchemeParserTest < Test::Unit::TestCase
116
138
  assert_equal expected, BusScheme.parse(actual_string)
117
139
  end
118
140
 
119
- def assert_parses_equal(one, two)
120
- assert_equal BusScheme.parse(one), BusScheme.parse(two)
141
+ def assert_parses_equal(one, two, message = nil)
142
+ assert_equal BusScheme.parse(one), BusScheme.parse(two), message
121
143
  end
122
144
  end
@@ -0,0 +1,118 @@
1
+ $LOAD_PATH << File.dirname(__FILE__)
2
+ require 'test_helper'
3
+
4
+ class PrimitivesTest < Test::Unit::TestCase
5
+ def test_booleans
6
+ assert BusScheme::PRIMITIVES.has_key? '#f'.intern
7
+ assert_evals_to false, '#f'
8
+ assert_evals_to true, '#t'
9
+ end
10
+
11
+ def test_arithmetic
12
+ assert_evals_to 2, [:'-', 4, 2]
13
+ assert_evals_to 2, [:'/', 4, 2]
14
+ assert_evals_to 2, [:'*', 1, 2]
15
+ end
16
+
17
+ def test_many_args_for_arithmetic
18
+ assert_evals_to 4, [:+, 1, 1, 1, 1]
19
+ assert_evals_to 2, [:*, 1, 2, 1, 1]
20
+ end
21
+
22
+ def test_concat
23
+ assert_evals_to "foobar", "(concat \"foo\" \"bar\")"
24
+ end
25
+
26
+ def test_eval
27
+ assert_evals_to 23, "(eval '(+ 20 3))"
28
+ end
29
+
30
+ def test_eval_ruby
31
+ assert_evals_to "foofoofoo", "(ruby \"'foo' * 3\")"
32
+ end
33
+
34
+ def test_message_passing
35
+ assert_evals_to 7, "(send 3 (intern \"+\") 4)"
36
+ end
37
+
38
+ def test_load_file
39
+ eval "(load \"#{File.dirname(__FILE__)}/foo.scm\")"
40
+ assert_evals_to 3, :foo
41
+ end
42
+
43
+ # special forms
44
+ def test_define
45
+ clear_symbols :foo
46
+ eval("(define foo 5)")
47
+ assert_equal 5, BusScheme::Lambda.scope[:foo]
48
+ eval("(define foo (quote (5 5 5))")
49
+ assert_evals_to [5, 5, 5].to_list, :foo
50
+ end
51
+
52
+ def test_define_returns_defined_term
53
+ assert_evals_to :foo, "(define foo 2)"
54
+ assert_equal 2, eval("foo")
55
+ end
56
+
57
+ def test_eval_quote
58
+ assert_evals_to [:'+', 2, 2].to_list, [:quote, [:'+', 2, 2]]
59
+ end
60
+
61
+ def test_quote
62
+ assert_evals_to :hi, [:quote, :hi]
63
+ assert_evals_to [:a, :b, :c].to_list, "'(a b c)"
64
+ assert_evals_to [:a].to_list, "(list 'a)"
65
+ assert_evals_to [:a, :b].to_list, "(list 'a 'b)"
66
+ assert_evals_to [:a, :b, :c].to_list, "(list 'a 'b 'c)"
67
+ end
68
+
69
+ def test_if
70
+ assert_evals_to 7, [:if, '#f'.intern, 3, 7]
71
+ assert_evals_to 3, [:if, [:>, 8, 2], 3, 7]
72
+ end
73
+
74
+ def test_begin
75
+ eval([:begin,
76
+ [:define, :foo, 779],
77
+ 9])
78
+ assert_equal 779, BusScheme::SYMBOL_TABLE[:foo]
79
+ end
80
+
81
+ def test_set!
82
+ clear_symbols(:foo)
83
+ # can only set! existing variables
84
+ assert_raises(BusScheme::EvalError) { eval "(set! foo 7)" }
85
+ eval "(define foo 3)"
86
+ eval "(set! foo 7)"
87
+ assert_evals_to 7, :foo
88
+ end
89
+
90
+ def test_consing
91
+ assert_evals_to BusScheme::Cons.new(:foo, :bar), "(cons (quote foo) (quote bar))"
92
+ end
93
+
94
+ def test_vectors
95
+ assert_evals_to [1, 2, 3], "#(1 2 3)"
96
+ end
97
+
98
+ def test_inspect
99
+ assert_equal "(1)", [1].to_list.inspect
100
+ assert_equal "(1 . 1)", BusScheme::Cons.new(1, 1).inspect
101
+ assert_equal "(1 1 1)", BusScheme::Cons.new(1, BusScheme::Cons.new(1, BusScheme::Cons.new(1))).inspect
102
+ end
103
+
104
+ def test_let
105
+ assert_evals_to 4, "(let ((x 2)
106
+ (y 2))
107
+ (+ x y))"
108
+
109
+ assert_evals_to 6, "(let ((doubler (lambda (x) (* 2 x)))
110
+ (x 3))
111
+ (doubler x))"
112
+
113
+ assert_evals_to 6, "(let ((doubler (lambda (x) (* 2 x)))
114
+ (x 3))
115
+ x
116
+ (doubler x))"
117
+ end
118
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bus-scheme
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Phil Hagelberg
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-01-17 00:00:00 -08:00
12
+ date: 2008-01-30 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -38,18 +38,22 @@ files:
38
38
  - bin/bus
39
39
  - lib/array_extensions.rb
40
40
  - lib/bus_scheme.rb
41
+ - lib/cons.rb
41
42
  - lib/eval.rb
42
43
  - lib/lambda.rb
43
- - lib/definitions.rb
44
44
  - lib/object_extensions.rb
45
45
  - lib/parser.rb
46
+ - lib/primitives.rb
47
+ - lib/scheme/core.scm
46
48
  - test/foo.scm
49
+ - test/test_core.rb
47
50
  - test/test_eval.rb
48
51
  - test/test_helper.rb
49
52
  - test/test_lambda.rb
50
53
  - test/test_parser.rb
54
+ - test/test_primitives.rb
51
55
  has_rdoc: true
52
- homepage: " by Phil Hagelberg (c) 2007"
56
+ homepage: " by Phil Hagelberg (c) 2007 - 2008"
53
57
  post_install_message:
54
58
  rdoc_options:
55
59
  - --main
@@ -77,7 +81,8 @@ specification_version: 2
77
81
  summary: Bus Scheme is a Scheme in Ruby, imlemented on the bus.
78
82
  test_files:
79
83
  - test/test_lambda.rb
80
- - test/test_compiler.rb
84
+ - test/test_core.rb
85
+ - test/test_primitives.rb
81
86
  - test/test_parser.rb
82
87
  - test/test_eval.rb
83
88
  - test/test_helper.rb
@@ -1,43 +0,0 @@
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
- '#t'.intern => true, # :'#t' screws up emacs' ruby parser
9
- '#f'.intern => false,
10
-
11
- :+ => lambda { |*args| args.inject(0) { |sum, i| sum + i } },
12
- :- => lambda { |x, y| x - y },
13
- :* => lambda { |*args| args.inject(1) { |product, i| product * i } },
14
- '/'.intern => lambda { |x, y| x / y },
15
-
16
- :> => lambda { |x, y| x > y },
17
- :< => lambda { |x, y| x < y },
18
- :'=' => lambda { |x, y| x == y }, # may not honor scheme's equality notions
19
- :null? => lambda { |x| x.nil? },
20
-
21
- :intern => lambda { |x| x.intern },
22
- :substring => lambda { |x, from, to| x[from .. to] },
23
-
24
- :car => lambda { |list| list.car },
25
- :cdr => lambda { |list| list.cdr },
26
-
27
- :ruby => lambda { |code| eval(code) },
28
- :load => lambda { |filename| eval_string(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 },
35
- # TODO: check that nil, () and #f all behave according to spec
36
- :if => lambda { |q, yes, *no| eval_form(q) ? eval_form(yes) : eval_form([:begin] + no) },
37
- :begin => lambda { |*args| args.map{ |arg| eval_form(arg) }.last },
38
- :set! => lambda { |sym, value| BusScheme[sym] and
39
- BusScheme[sym] = eval_form(value); sym },
40
- :lambda => lambda { |args, *form| Lambda.new(args, form) },
41
- :define => lambda { |sym, definition| BusScheme[sym] = eval_form(definition); sym },
42
- }
43
- end
@@ -1,19 +0,0 @@
1
- $LOAD_PATH << File.dirname(__FILE__)
2
- require 'test_helper'
3
-
4
- # must be run in Rubinius
5
- # how to check?
6
-
7
- class BusSchemeCompilerTest < Test::Unit::TestCase
8
- # def test_compiles_arithmetic
9
- # assert_equivalent "(+ 1 2)", "1 + 2"
10
- # end
11
-
12
- private
13
- def assert_equivalent(scheme, ruby)
14
- scheme = BusScheme.parse(scheme) if scheme.is_a? String # for convenience
15
- parsed_ruby = ruby.to_sexp.last
16
- compiled_scheme = BusScheme::Compiler.compile(scheme)
17
- assert_equal
18
- end
19
- end