bus-scheme 0.6 → 0.7

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.
@@ -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