bus-scheme 0.7 → 0.7.1
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.
- data/Manifest.txt +1 -0
- data/README.txt +4 -2
- data/lib/array_extensions.rb +8 -6
- data/lib/bus_scheme.rb +15 -16
- data/lib/definitions.rb +7 -2
- data/lib/lambda.rb +35 -17
- data/test/test_compiler.rb +19 -0
- data/test/test_eval.rb +10 -1
- data/test/test_helper.rb +15 -12
- data/test/test_lambda.rb +5 -3
- metadata +4 -2
data/Manifest.txt
CHANGED
data/README.txt
CHANGED
@@ -33,15 +33,17 @@ $ bus # drop into a repl
|
|
33
33
|
|
34
34
|
$ bus -e "(do some stuff)"
|
35
35
|
|
36
|
-
$ bus foo.scm # load a file
|
36
|
+
$ bus foo.scm # load a file
|
37
37
|
|
38
38
|
== Todo
|
39
39
|
|
40
40
|
Bus Scheme is currently missing pieces of functionality:
|
41
41
|
|
42
|
-
*
|
42
|
+
* move as many definitions into Scheme as possible
|
43
|
+
* compile to Rubinius bytecode
|
43
44
|
* optimize tail call recursion
|
44
45
|
* continuations (?!?)
|
46
|
+
* check for memory leaks
|
45
47
|
* parse cons cells
|
46
48
|
* parse character literals
|
47
49
|
* parse quote, unquote
|
data/lib/array_extensions.rb
CHANGED
@@ -1,10 +1,4 @@
|
|
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
|
-
|
8
2
|
# Lisp-style list access
|
9
3
|
def rest
|
10
4
|
self[1 .. -1]
|
@@ -13,3 +7,11 @@ class Array
|
|
13
7
|
alias_method :car, :first
|
14
8
|
alias_method :cdr, :rest
|
15
9
|
end
|
10
|
+
|
11
|
+
module Enumerable # for 1.9, zip is defined on Enumerable
|
12
|
+
def to_hash
|
13
|
+
{}.affect do |hash|
|
14
|
+
self.each { |pair| hash[pair.first] = pair.last }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/bus_scheme.rb
CHANGED
@@ -15,27 +15,26 @@ require 'definitions'
|
|
15
15
|
require 'lambda'
|
16
16
|
|
17
17
|
module BusScheme
|
18
|
-
VERSION = "0.7"
|
18
|
+
VERSION = "0.7.1"
|
19
19
|
|
20
20
|
SYMBOL_TABLE = {}.merge(PRIMITIVES).merge(SPECIAL_FORMS)
|
21
|
-
LOCAL_SCOPES = []
|
22
21
|
PROMPT = '> '
|
23
22
|
|
24
23
|
# what scope is appropraite for this symbol
|
25
24
|
def self.scope_of(symbol)
|
26
|
-
|
25
|
+
[Lambda.scope, SYMBOL_TABLE].compact.detect { |scope| scope.has_key?(symbol) }
|
27
26
|
end
|
28
27
|
|
29
28
|
# symbol lookup
|
30
29
|
def self.[](symbol)
|
31
30
|
scope = scope_of(symbol)
|
32
|
-
raise EvalError.new("Undefined symbol: #{symbol}")
|
33
|
-
scope[symbol]
|
31
|
+
raise EvalError.new("Undefined symbol: #{symbol}") unless scope
|
32
|
+
scope && scope[symbol]
|
34
33
|
end
|
35
34
|
|
36
35
|
# symbol assignment to value
|
37
36
|
def self.[]=(symbol, value)
|
38
|
-
(scope_of(symbol) || SYMBOL_TABLE)[symbol] = value
|
37
|
+
(scope_of(symbol) || Lambda.scope || SYMBOL_TABLE)[symbol] = value
|
39
38
|
end
|
40
39
|
|
41
40
|
# symbol special form predicate
|
@@ -46,16 +45,16 @@ module BusScheme
|
|
46
45
|
# Read-Eval-Print-Loop
|
47
46
|
def self.repl
|
48
47
|
loop do
|
49
|
-
begin
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
48
|
+
puts begin
|
49
|
+
BusScheme.eval_string(Readline.readline(PROMPT))
|
50
|
+
rescue Interrupt
|
51
|
+
'Type "(quit)" to leave Bus Scheme.'
|
52
|
+
rescue BusSchemeError => e
|
53
|
+
"Error: #{e}"
|
54
|
+
rescue StandardError => e
|
55
|
+
"You found a bug in Bus Scheme!\n" +
|
56
|
+
"#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
57
|
+
end
|
59
58
|
end
|
60
59
|
end
|
61
60
|
end
|
data/lib/definitions.rb
CHANGED
@@ -15,11 +15,16 @@ module BusScheme
|
|
15
15
|
|
16
16
|
:> => lambda { |x, y| x > y },
|
17
17
|
:< => lambda { |x, y| x < y },
|
18
|
-
|
18
|
+
:'=' => lambda { |x, y| x == y }, # may not honor scheme's equality notions
|
19
|
+
:null? => lambda { |x| x.nil? },
|
20
|
+
|
19
21
|
:intern => lambda { |x| x.intern },
|
20
|
-
:concat => lambda { |x, y| x + y },
|
21
22
|
:substring => lambda { |x, from, to| x[from .. to] },
|
22
23
|
|
24
|
+
:car => lambda { |list| list.car },
|
25
|
+
:cdr => lambda { |list| list.cdr },
|
26
|
+
|
27
|
+
:ruby => lambda { |code| eval(code) },
|
23
28
|
:load => lambda { |filename| eval_string(File.read(filename)) },
|
24
29
|
:exit => lambda { exit }, :quit => lambda { exit },
|
25
30
|
}
|
data/lib/lambda.rb
CHANGED
@@ -1,32 +1,50 @@
|
|
1
1
|
module BusScheme
|
2
|
+
class Scope < Hash
|
3
|
+
def initialize(table, parent)
|
4
|
+
@parent = parent
|
5
|
+
table.each { |k, v| self[k] = v }
|
6
|
+
end
|
7
|
+
|
8
|
+
alias_method :old_has_key?, :has_key?
|
9
|
+
def has_key?(symbol)
|
10
|
+
old_has_key?(symbol) or @parent && @parent.has_key?(symbol)
|
11
|
+
end
|
12
|
+
|
13
|
+
alias_method :lookup, :[]
|
14
|
+
def [](symbol)
|
15
|
+
lookup(symbol) or @parent && @parent[symbol]
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :old_set, :[]=
|
19
|
+
def []=(symbol, value)
|
20
|
+
if !old_has_key?(symbol) and @parent and @parent.has_key?(symbol)
|
21
|
+
@parent[symbol] = value
|
22
|
+
else
|
23
|
+
old_set symbol, value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
2
28
|
class Lambda
|
3
|
-
@@
|
29
|
+
@@stack = []
|
30
|
+
|
31
|
+
attr_reader :scope
|
4
32
|
|
5
33
|
# create new lambda object
|
6
34
|
def initialize(arg_names, body)
|
7
|
-
@arg_names, @body, @
|
35
|
+
@arg_names, @body, @enclosing_scope = [arg_names, body, Lambda.scope]
|
8
36
|
end
|
9
|
-
|
10
|
-
attr_reader :environment
|
11
37
|
|
12
38
|
# execute lambda with given arg_values
|
13
39
|
def call(*arg_values)
|
14
40
|
raise BusScheme::ArgumentError if @arg_names.length != arg_values.length
|
15
|
-
|
41
|
+
@scope = Scope.new(@arg_names.zip(arg_values).to_hash, @enclosing_scope)
|
42
|
+
@@stack << self
|
43
|
+
BusScheme[:begin].call(*@body).affect { @@stack.pop }
|
16
44
|
end
|
17
45
|
|
18
|
-
def self.
|
19
|
-
@@
|
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
|
46
|
+
def self.scope
|
47
|
+
@@stack.empty? ? nil : @@stack.last.scope
|
30
48
|
end
|
31
49
|
end
|
32
50
|
end
|
@@ -0,0 +1,19 @@
|
|
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
|
data/test/test_eval.rb
CHANGED
@@ -10,6 +10,11 @@ class BusSchemeEvalTest < Test::Unit::TestCase
|
|
10
10
|
assert_evals_to 2, 2
|
11
11
|
end
|
12
12
|
|
13
|
+
def test_set_symbol
|
14
|
+
BusScheme[:hi] = 'hi'
|
15
|
+
assert BusScheme::SYMBOL_TABLE[:hi]
|
16
|
+
end
|
17
|
+
|
13
18
|
def test_eval_symbol
|
14
19
|
eval "(define hi 13)"
|
15
20
|
assert_evals_to 13, :hi
|
@@ -49,11 +54,11 @@ class BusSchemeEvalTest < Test::Unit::TestCase
|
|
49
54
|
|
50
55
|
def test_string_primitives
|
51
56
|
assert_evals_to :hi, [:intern, 'hi']
|
52
|
-
assert_evals_to 'helloworld', [:concat, 'hello', 'world']
|
53
57
|
assert_evals_to 'lo', [:substring, 'hello', 3, -1]
|
54
58
|
end
|
55
59
|
|
56
60
|
def test_booleans
|
61
|
+
assert BusScheme::PRIMITIVES.has_key? '#f'.intern
|
57
62
|
assert_evals_to false, '#f'
|
58
63
|
assert_evals_to true, '#t'
|
59
64
|
end
|
@@ -105,4 +110,8 @@ class BusSchemeEvalTest < Test::Unit::TestCase
|
|
105
110
|
eval "(load \"#{File.dirname(__FILE__)}/foo.scm\")"
|
106
111
|
assert_evals_to 3, :foo
|
107
112
|
end
|
113
|
+
|
114
|
+
def test_eval_ruby
|
115
|
+
assert_evals_to "foofoofoo", "(ruby \"'foo' * 3\")"
|
116
|
+
end
|
108
117
|
end
|
data/test/test_helper.rb
CHANGED
@@ -9,19 +9,22 @@ $LOAD_PATH << File.dirname(__FILE__) + '/../lib/'
|
|
9
9
|
require 'test/unit'
|
10
10
|
require 'bus_scheme'
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
class Test::Unit::TestCase
|
13
|
+
# convenience method that accepts string or form
|
14
|
+
def eval(form)
|
15
|
+
if form.is_a?(String)
|
16
|
+
BusScheme.eval_string(form)
|
17
|
+
else
|
18
|
+
BusScheme.eval_form(form)
|
19
|
+
end
|
17
20
|
end
|
18
|
-
end
|
19
21
|
|
20
|
-
def assert_evals_to(expected, form)
|
21
|
-
|
22
|
-
end
|
22
|
+
def assert_evals_to(expected, form)
|
23
|
+
assert_equal expected, eval(form)
|
24
|
+
end
|
23
25
|
|
24
|
-
# remove symbols from all scopes
|
25
|
-
def clear_symbols(*symbols)
|
26
|
-
|
26
|
+
# remove symbols from all scopes
|
27
|
+
def clear_symbols(*symbols)
|
28
|
+
[BusScheme::Lambda.scope, BusScheme::SYMBOL_TABLE].compact.map{ |scope| symbols.map{ |sym| scope.delete sym } }
|
29
|
+
end
|
27
30
|
end
|
data/test/test_lambda.rb
CHANGED
@@ -48,13 +48,15 @@ class BusSchemeLambdaTest < Test::Unit::TestCase
|
|
48
48
|
def test_lambda_closures
|
49
49
|
eval "(define foo (lambda (x) ((lambda (y) (+ x y)) (* x 2))))"
|
50
50
|
assert_evals_to 3, [:foo, 1]
|
51
|
+
eval "(define holder ((lambda (x) (lambda () x)) 2))"
|
52
|
+
assert_evals_to 2, "(holder)"
|
51
53
|
end
|
52
54
|
|
53
55
|
def test_changes_to_enclosed_variables_are_in_effect_after_lambda_execution
|
54
56
|
assert_evals_to 2, "((lambda (x) (begin ((lambda () (set! x 2))) x)) 1)"
|
55
57
|
end
|
56
58
|
|
57
|
-
|
58
|
-
|
59
|
-
|
59
|
+
def test_implicit_begin
|
60
|
+
assert_evals_to 3, "((lambda () (intern \"hi\") (+ 2 2) (* 1 3)))"
|
61
|
+
end
|
60
62
|
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:
|
4
|
+
version: 0.7.1
|
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-
|
12
|
+
date: 2008-01-17 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -46,6 +46,7 @@ files:
|
|
46
46
|
- test/foo.scm
|
47
47
|
- test/test_eval.rb
|
48
48
|
- test/test_helper.rb
|
49
|
+
- test/test_lambda.rb
|
49
50
|
- test/test_parser.rb
|
50
51
|
has_rdoc: true
|
51
52
|
homepage: " by Phil Hagelberg (c) 2007"
|
@@ -76,6 +77,7 @@ specification_version: 2
|
|
76
77
|
summary: Bus Scheme is a Scheme in Ruby, imlemented on the bus.
|
77
78
|
test_files:
|
78
79
|
- test/test_lambda.rb
|
80
|
+
- test/test_compiler.rb
|
79
81
|
- test/test_parser.rb
|
80
82
|
- test/test_eval.rb
|
81
83
|
- test/test_helper.rb
|