bus-scheme 0.6 → 0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,6 +6,8 @@ bin/bus
6
6
  lib/array_extensions.rb
7
7
  lib/bus_scheme.rb
8
8
  lib/eval.rb
9
+ lib/lambda.rb
10
+ lib/definitions.rb
9
11
  lib/object_extensions.rb
10
12
  lib/parser.rb
11
13
  test/foo.scm
data/README.txt CHANGED
@@ -37,17 +37,16 @@ $ bus foo.scm # load a file -- todo
37
37
 
38
38
  == Todo
39
39
 
40
- Bus Scheme is currently missing huge pieces of functionality:
40
+ Bus Scheme is currently missing pieces of functionality:
41
41
 
42
- * lexical scoping
43
- * closures
44
- * loading files
45
- * eval input from command line, stdin
42
+ * changes to closure variables don't affect original scope
43
+ * optimize tail call recursion
44
+ * continuations (?!?)
46
45
  * parse cons cells
47
46
  * parse character literals
48
- * numeric tower?
47
+ * parse quote, unquote
49
48
 
50
- Failing tests for most of these are already included (commented out,
49
+ Failing tests for some of these are already included (commented out,
51
50
  mostly) in the relevant test files.
52
51
 
53
52
  == Requirements
data/bin/bus CHANGED
@@ -7,8 +7,8 @@ if ARGV.empty?
7
7
  BusScheme.repl
8
8
  elsif ARGV.first == '-e' and ARGV.length == 2
9
9
  puts BusScheme.eval_string(ARGV[1])
10
- elsif ARGV.length == 1 and File.exist?(ARGV.first) # must be a file
11
- puts BusScheme.load(ARGV.first)
10
+ elsif ARGV.length == 1 and File.exist?(ARGV.first)
11
+ puts BusScheme.eval_form([:load, ARGV.first])
12
12
  else
13
13
  puts "Bus Scheme: a scheme interpreter written on the bus.
14
14
  Usage: bus [file | -e \"form\"]
@@ -1,4 +1,10 @@
1
1
  class Array
2
+ def to_hash
3
+ {}.affect do |hash|
4
+ self.each { |pair| hash[pair.first] = pair.last }
5
+ end
6
+ end
7
+
2
8
  # Lisp-style list access
3
9
  def rest
4
10
  self[1 .. -1]
@@ -6,14 +12,4 @@ class Array
6
12
 
7
13
  alias_method :car, :first
8
14
  alias_method :cdr, :rest
9
-
10
- # Treat the array as a lambda and call it with given args
11
- def call(*args)
12
- BusScheme::eval_lambda(self, args)
13
- end
14
-
15
- # Simple predicate for convenience
16
- def lambda?
17
- first == :lambda
18
- end
19
15
  end
@@ -11,71 +11,38 @@ require 'object_extensions'
11
11
  require 'array_extensions'
12
12
  require 'parser'
13
13
  require 'eval'
14
+ require 'definitions'
15
+ require 'lambda'
14
16
 
15
17
  module BusScheme
16
- class ParseError < StandardError; end
17
- class EvalError < StandardError; end
18
- class ArgumentError < StandardError; end
19
-
20
- VERSION = "0.6"
21
-
22
- PRIMITIVES = {
23
- '#t'.intern => true, # :'#t' screws up emacs' ruby parser
24
- '#f'.intern => false,
25
-
26
- :+ => lambda { |*args| args.inject(0) { |sum, i| sum + i } },
27
- :- => lambda { |x, y| x - y },
28
- '/'.intern => lambda { |x, y| x / y },
29
- :* => lambda { |*args| args.inject(1) { |product, i| product * i } },
30
-
31
- :> => lambda { |x, y| x > y },
32
- :< => lambda { |x, y| x < y },
33
-
34
- :intern => lambda { |x| x.intern },
35
- :concat => lambda { |x, y| x + y },
36
- :substring => lambda { |x, from, to| x[from .. to] },
37
-
38
- :exit => lambda { exit }, :quit => lambda { exit },
39
- }
40
-
41
- SPECIAL_FORMS = {
42
- :quote => lambda { |arg| arg },
43
- :if => lambda { |condition, yes, *no| eval_form(condition) ? eval_form(yes) : eval_form([:begin] + no) },
44
- :begin => lambda { |*args| args.map{ |arg| eval_form(arg) }.last },
45
- :set! => lambda { },
46
- :lambda => lambda { |args, *form| [:lambda, args] + form },
47
- :define => lambda { |sym, definition| BusScheme[sym] = eval_form(definition); sym },
48
- }
18
+ VERSION = "0.7"
49
19
 
50
20
  SYMBOL_TABLE = {}.merge(PRIMITIVES).merge(SPECIAL_FORMS)
51
- SCOPES = [SYMBOL_TABLE]
21
+ LOCAL_SCOPES = []
52
22
  PROMPT = '> '
53
23
 
54
- # symbol existence predicate
55
- def self.in_scope?(symbol)
56
- SCOPES.last.has_key?(symbol) or SCOPES.first.has_key?(symbol)
24
+ # what scope is appropraite for this symbol
25
+ def self.scope_of(symbol)
26
+ ([LOCAL_SCOPES.last] + Lambda.environment + [SYMBOL_TABLE]).compact.detect { |scope| scope.has_key?(symbol) }
57
27
  end
58
-
28
+
59
29
  # symbol lookup
60
30
  def self.[](symbol)
61
- SCOPES.last[symbol] or SCOPES.first[symbol]
31
+ scope = scope_of(symbol)
32
+ raise EvalError.new("Undefined symbol: #{symbol}") if scope.nil?
33
+ scope[symbol]
62
34
  end
63
35
 
64
36
  # symbol assignment to value
65
37
  def self.[]=(symbol, value)
66
- SCOPES.last[symbol] = value
67
- end
68
-
69
- # remove symbols from all scopes
70
- def self.clear_symbols(*symbols)
71
- SCOPES.map{ |scope| symbols.map{ |sym| scope.delete sym } }
38
+ (scope_of(symbol) || SYMBOL_TABLE)[symbol] = value
72
39
  end
73
40
 
74
41
  # symbol special form predicate
75
42
  def self.special_form?(symbol)
76
43
  SPECIAL_FORMS.has_key?(symbol)
77
44
  end
78
-
45
+
79
46
  # Read-Eval-Print-Loop
80
47
  def self.repl
81
48
  loop do
@@ -83,6 +50,11 @@ module BusScheme
83
50
  puts BusScheme.eval_string(Readline.readline(PROMPT))
84
51
  rescue Interrupt
85
52
  puts 'Type "(quit)" to leave Bus Scheme.'
53
+ rescue BusSchemeError => e
54
+ puts "Error: #{e}"
55
+ rescue StandardError => e
56
+ puts "You found a bug in Bus Scheme!"
57
+ puts "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
86
58
  end
87
59
  end
88
60
  end
@@ -0,0 +1,38 @@
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
+
19
+ :intern => lambda { |x| x.intern },
20
+ :concat => lambda { |x, y| x + y },
21
+ :substring => lambda { |x, from, to| x[from .. to] },
22
+
23
+ :load => lambda { |filename| eval_string(File.read(filename)) },
24
+ :exit => lambda { exit }, :quit => lambda { exit },
25
+ }
26
+
27
+ # if we add in macros, can some of these be defined in scheme?
28
+ SPECIAL_FORMS = {
29
+ :quote => lambda { |arg| arg },
30
+ # TODO: check that nil, () and #f all behave according to spec
31
+ :if => lambda { |q, yes, *no| eval_form(q) ? eval_form(yes) : eval_form([:begin] + no) },
32
+ :begin => lambda { |*args| args.map{ |arg| eval_form(arg) }.last },
33
+ :set! => lambda { |sym, value| BusScheme[sym] and
34
+ BusScheme[sym] = eval_form(value); sym },
35
+ :lambda => lambda { |args, *form| Lambda.new(args, form) },
36
+ :define => lambda { |sym, definition| BusScheme[sym] = eval_form(definition); sym },
37
+ }
38
+ end
@@ -12,9 +12,8 @@ module BusScheme
12
12
  elsif form.is_a? Array
13
13
  apply(form.first, *form.rest)
14
14
  elsif form.is_a? Symbol
15
- raise "Undefined symbol: #{form}" unless in_scope?(form)
16
15
  BusScheme[form]
17
- else
16
+ else # well it must be a literal then
18
17
  form
19
18
  end
20
19
  end
@@ -22,30 +21,7 @@ module BusScheme
22
21
  # Call a function with given args
23
22
  def apply(function, *args)
24
23
  args.map!{ |arg| eval_form(arg) } unless special_form?(function)
25
-
26
- # refactor me
27
- if function.is_a?(Array) and function.lambda?
28
- function.call(*args)
29
- else
30
- raise "Undefined symbol: #{function}" unless in_scope?(function)
31
- BusScheme[function].call(*args)
32
- end
33
- end
34
-
35
- # All the super lambda magic happens (or fails to happen) here
36
- def eval_lambda(lambda, args)
37
- raise BusScheme::EvalError unless lambda.shift == :lambda
38
-
39
- arg_list = lambda.shift
40
- raise BusScheme::ArgumentError if !arg_list.is_a?(Array) or arg_list.length != args.length
41
-
42
- SCOPES << {} # new scope
43
- until arg_list.empty?
44
- BusScheme[arg_list.shift] = args.shift
45
- end
46
-
47
- # using affect as a non-return-value-affecting callback
48
- BusScheme[:begin].call(*lambda).affect { SCOPES.pop }
24
+ eval_form(function).call(*args)
49
25
  end
50
26
  end
51
27
  end
@@ -0,0 +1,32 @@
1
+ module BusScheme
2
+ class Lambda
3
+ @@current = nil
4
+
5
+ # create new lambda object
6
+ def initialize(arg_names, body)
7
+ @arg_names, @body, @environment = [arg_names, body, LOCAL_SCOPES]
8
+ end
9
+
10
+ attr_reader :environment
11
+
12
+ # execute lambda with given arg_values
13
+ def call(*arg_values)
14
+ raise BusScheme::ArgumentError if @arg_names.length != arg_values.length
15
+ with_local_scope(@arg_names.zip(arg_values).to_hash) { BusScheme[:begin].call(*@body) }
16
+ end
17
+
18
+ def self.environment
19
+ @@current ? @@current.environment : []
20
+ end
21
+
22
+ # execute a block with a given local scope
23
+ def with_local_scope(scope, &block)
24
+ BusScheme::LOCAL_SCOPES << scope
25
+ @@current = self
26
+ block.call.affect do
27
+ BusScheme::LOCAL_SCOPES.delete(scope)
28
+ @@current = nil
29
+ end
30
+ end
31
+ end
32
+ end
@@ -35,7 +35,7 @@ class BusSchemeEvalTest < Test::Unit::TestCase
35
35
  end
36
36
 
37
37
  def test_define
38
- BusScheme.clear_symbols :foo
38
+ clear_symbols :foo
39
39
  eval("(define foo 5)")
40
40
  assert_equal 5, BusScheme[:foo]
41
41
  eval("(define foo (quote (5 5 5))")
@@ -44,7 +44,6 @@ class BusSchemeEvalTest < Test::Unit::TestCase
44
44
 
45
45
  def test_define_returns_defined_term
46
46
  assert_evals_to :foo, "(define foo 2)"
47
- # can't use the eval convenience testing method since it assumes strings are unparsed
48
47
  assert_equal 2, eval("foo")
49
48
  end
50
49
 
@@ -72,7 +71,7 @@ class BusSchemeEvalTest < Test::Unit::TestCase
72
71
  end
73
72
 
74
73
  def test_blows_up_with_undefined_symbol
75
- assert_raises(RuntimeError) { eval("undefined-symbol") }
74
+ assert_raises(BusScheme::EvalError) { eval("undefined-symbol") }
76
75
  end
77
76
 
78
77
  def test_variable_substitution
@@ -94,65 +93,16 @@ class BusSchemeEvalTest < Test::Unit::TestCase
94
93
  end
95
94
 
96
95
  def test_set!
97
- # i dunno... what does set! do? how's it different from define?
98
- end
99
-
100
- def test_simple_lambda
101
- assert_equal [:lambda, [], [:+, 1, 1]], eval("(lambda () (+ 1 1))")
102
- eval("(define foo (lambda () (+ 1 1)))")
103
- assert_equal :lambda, BusScheme[:foo].first
104
- assert_evals_to 2, [:foo]
105
- end
106
-
107
- def test_lambda_with_arg
108
- eval("(define foo (lambda (x) (+ x 1)))")
109
- assert_evals_to 2, [:foo, 1]
110
- end
111
-
112
- def test_eval_literal_lambda
113
- assert_evals_to 4, "((lambda (x) (* x x)) 2)"
114
- end
115
-
116
- def test_lambda_with_incorrect_arity
117
- eval("(define foo (lambda (x) (+ x 1)))")
118
- assert_raises(BusScheme::ArgumentError) { assert_evals_to 2, [:foo, 1, 3] }
119
- end
120
-
121
- def test_lambda_args_dont_stay_in_scope
122
- BusScheme.clear_symbols(:x, :foo)
123
- eval("(define foo (lambda (x) (+ x 1)))")
124
- assert !BusScheme.in_scope?(:x)
125
- assert_evals_to 2, [:foo, 1]
126
- assert !BusScheme.in_scope?(:x)
127
- end
128
-
129
- # def test_lexical_scoping
130
- # assert_raises(BusScheme::EvalError) do
131
- # eval "???"
132
- # end
133
- # end
134
-
135
- # def test_lambda_closures
136
- # eval "(define foo (lambda (x) ((lambda (y) (+ x y)) (* x 2))))"
137
- # assert_evals_to 3, [:foo, 1]
138
- # end
139
-
140
- # def test_load_file
141
- # eval "(load \"#{File.dirname(__FILE__)}/foo.scm\")"
142
- # assert_evals_to 3, :foo
143
- # end
144
-
145
- private
146
-
147
- def eval(form) # convenience method that accepts string or form
148
- if form.is_a?(String)
149
- BusScheme.eval_string(form)
150
- else
151
- BusScheme.eval_form(form)
152
- end
96
+ clear_symbols(:foo)
97
+ # can only set! existing variables
98
+ assert_raises(BusScheme::EvalError) { eval "(set! foo 7)" }
99
+ eval "(define foo 3)"
100
+ eval "(set! foo 7)"
101
+ assert_evals_to 7, :foo
153
102
  end
154
103
 
155
- def assert_evals_to(expected, form)
156
- assert_equal expected, eval(form)
104
+ def test_load_file
105
+ eval "(load \"#{File.dirname(__FILE__)}/foo.scm\")"
106
+ assert_evals_to 3, :foo
157
107
  end
158
108
  end
@@ -8,3 +8,20 @@ end
8
8
  $LOAD_PATH << File.dirname(__FILE__) + '/../lib/'
9
9
  require 'test/unit'
10
10
  require 'bus_scheme'
11
+
12
+ def eval(form) # convenience method that accepts string or form
13
+ if form.is_a?(String)
14
+ BusScheme.eval_string(form)
15
+ else
16
+ BusScheme.eval_form(form)
17
+ end
18
+ end
19
+
20
+ def assert_evals_to(expected, form)
21
+ assert_equal expected, eval(form)
22
+ end
23
+
24
+ # remove symbols from all scopes
25
+ def clear_symbols(*symbols)
26
+ (BusScheme::LOCAL_SCOPES << BusScheme::SYMBOL_TABLE).map{ |scope| symbols.map{ |sym| scope.delete sym } }
27
+ end
@@ -0,0 +1,60 @@
1
+ $LOAD_PATH << File.dirname(__FILE__)
2
+ require 'test_helper'
3
+
4
+ class BusScheme::Lambda
5
+ attr_accessor :body, :arg_names, :environment
6
+ end
7
+
8
+ class BusSchemeLambdaTest < Test::Unit::TestCase
9
+ def test_simple_lambda
10
+ l = eval("(lambda () (+ 1 1))")
11
+ assert l.is_a?(BusScheme::Lambda)
12
+ assert_equal [[:+, 1, 1]], l.body
13
+ assert_equal [], l.arg_names
14
+
15
+ eval("(define foo (lambda () (+ 1 1)))")
16
+ assert BusScheme[:foo].is_a?(BusScheme::Lambda)
17
+ assert_evals_to 2, [:foo]
18
+ end
19
+
20
+ def test_lambda_with_arg
21
+ eval("(define foo (lambda (x) (+ x 1)))")
22
+ assert_evals_to 2, [:foo, 1]
23
+ end
24
+
25
+ def test_eval_literal_lambda
26
+ assert_evals_to 4, "((lambda (x) (* x x)) 2)"
27
+ end
28
+
29
+ def test_lambda_with_incorrect_arity
30
+ eval("(define foo (lambda (x) (+ x 1)))")
31
+ assert_raises(BusScheme::ArgumentError) { assert_evals_to 2, [:foo, 1, 3] }
32
+ end
33
+
34
+ def test_lambda_args_dont_stay_in_scope
35
+ clear_symbols(:x, :foo)
36
+ eval("(define foo (lambda (x) (+ x 1)))")
37
+ assert_nil BusScheme.scope_of(:x)
38
+ assert_evals_to 2, [:foo, 1]
39
+ assert_nil BusScheme.scope_of(:x)
40
+ end
41
+
42
+ def test_lambda_calls_lambda
43
+ eval "(define f (lambda (x) (+ 3 x)))"
44
+ eval "(define g (lambda (y) (* 3 y)))"
45
+ assert_evals_to 12, "(f (g 3))"
46
+ end
47
+
48
+ def test_lambda_closures
49
+ eval "(define foo (lambda (x) ((lambda (y) (+ x y)) (* x 2))))"
50
+ assert_evals_to 3, [:foo, 1]
51
+ end
52
+
53
+ def test_changes_to_enclosed_variables_are_in_effect_after_lambda_execution
54
+ assert_evals_to 2, "((lambda (x) (begin ((lambda () (set! x 2))) x)) 1)"
55
+ end
56
+
57
+ def test_implicit_begin
58
+ assert_evals_to 3, "((lambda () (intern \"hi\") (+ 2 2) (* 1 3)))"
59
+ end
60
+ end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bus-scheme
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.6"
5
- platform: ""
4
+ version: "0.7"
5
+ platform: ruby
6
6
  authors:
7
7
  - Phil Hagelberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2007-12-17 00:00:00 -08:00
12
+ date: 2008-01-10 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -19,7 +19,7 @@ dependencies:
19
19
  requirements:
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 1.3.0
22
+ version: 1.4.0
23
23
  version:
24
24
  description: Bus Scheme is a Scheme written in Ruby, but implemented on the bus! Every programmer must implement Scheme as a rite of passage; this is mine. Note that all the implementation of Bus Scheme must be written while on a bus. Documentation, tests, and administrivia may be accomplished elsewhere, but all actual implementation code is strictly bus-driven. Patches are welcome as long as they were written while riding a bus. (If your daily commute does not involve a bus but you want to submit a patch, we may be able to work something out regarding code written on trains, ferries, or perhaps even carpool lanes.) Bus Scheme is primarily a toy; using it for anything serious is (right now) ill-advised. Bus Scheme aims for general Scheme usefulness optimized for learning and fun. It's not targeting R5RS or anything like that. == Install * sudo gem install bus-scheme
25
25
  email: technomancy@gmail.com
@@ -39,6 +39,8 @@ files:
39
39
  - lib/array_extensions.rb
40
40
  - lib/bus_scheme.rb
41
41
  - lib/eval.rb
42
+ - lib/lambda.rb
43
+ - lib/definitions.rb
42
44
  - lib/object_extensions.rb
43
45
  - lib/parser.rb
44
46
  - test/foo.scm
@@ -68,11 +70,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
70
  requirements: []
69
71
 
70
72
  rubyforge_project: bus-scheme
71
- rubygems_version: 0.9.5
73
+ rubygems_version: 1.0.0
72
74
  signing_key:
73
75
  specification_version: 2
74
76
  summary: Bus Scheme is a Scheme in Ruby, imlemented on the bus.
75
77
  test_files:
78
+ - test/test_lambda.rb
76
79
  - test/test_parser.rb
77
- - test/test_helper.rb
78
80
  - test/test_eval.rb
81
+ - test/test_helper.rb