bus-scheme 0.7.5 → 0.7.6
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +1 -1
- data/Manifest.txt +12 -0
- data/R5RS.diff +30 -0
- data/README.txt +91 -32
- data/Rakefile +30 -3
- data/bin/bus +1 -1
- data/examples/fib.scm +6 -0
- data/lib/array_extensions.rb +8 -5
- data/lib/bus_scheme.rb +58 -17
- data/lib/cons.rb +43 -3
- data/lib/eval.rb +53 -20
- data/lib/lambda.rb +51 -41
- data/lib/object_extensions.rb +58 -1
- data/lib/parser.rb +93 -64
- data/lib/primitives.rb +63 -43
- data/lib/scheme/core.scm +18 -15
- data/lib/scheme/list.scm +12 -0
- data/lib/scheme/predicates.scm +19 -0
- data/lib/scheme/test.scm +12 -0
- data/lib/stack_frame.rb +57 -0
- data/test/test_core.rb +9 -21
- data/test/test_eval.rb +56 -11
- data/test/test_helper.rb +26 -5
- data/test/test_lambda.rb +83 -21
- data/test/test_list_functions.scm +11 -0
- data/test/test_parser.rb +66 -31
- data/test/test_predicates.scm +24 -0
- data/test/test_primitives.rb +34 -88
- data/test/test_primitives.scm +55 -0
- data/test/test_stack_frame.rb +30 -0
- data/test/test_web.rb +116 -0
- data/test/test_xml.rb +69 -0
- data/test/tracer.scm +4 -0
- data/tutorials/getting_started.html +204 -0
- metadata +21 -6
data/COPYING
CHANGED
data/Manifest.txt
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
COPYING
|
2
2
|
Manifest.txt
|
3
|
+
R5RS.diff
|
3
4
|
README.txt
|
4
5
|
Rakefile
|
5
6
|
bin/bus
|
7
|
+
examples/fib.scm
|
6
8
|
lib/array_extensions.rb
|
7
9
|
lib/bus_scheme.rb
|
8
10
|
lib/cons.rb
|
@@ -12,10 +14,20 @@ lib/object_extensions.rb
|
|
12
14
|
lib/parser.rb
|
13
15
|
lib/primitives.rb
|
14
16
|
lib/scheme/core.scm
|
17
|
+
lib/scheme/list.scm
|
18
|
+
lib/scheme/predicates.scm
|
19
|
+
lib/scheme/test.scm
|
20
|
+
lib/stack_frame.rb
|
15
21
|
test/foo.scm
|
16
22
|
test/test_core.rb
|
17
23
|
test/test_eval.rb
|
18
24
|
test/test_helper.rb
|
19
25
|
test/test_lambda.rb
|
26
|
+
test/test_list_functions.scm
|
20
27
|
test/test_parser.rb
|
28
|
+
test/test_predicates.scm
|
21
29
|
test/test_primitives.rb
|
30
|
+
test/test_primitives.scm
|
31
|
+
test/test_stack_frame.rb
|
32
|
+
test/tracer.scm
|
33
|
+
tutorials/getting_started.html
|
data/R5RS.diff
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
= Differences between Bus Scheme and R5RS
|
2
|
+
|
3
|
+
== Parsing
|
4
|
+
|
5
|
+
Unsupported:
|
6
|
+
* Quasiquote, unquote, unquote-splice
|
7
|
+
* Backslash in strings
|
8
|
+
* Character literals
|
9
|
+
* Alternate-base numbers
|
10
|
+
* Alternate define lambda forms
|
11
|
+
|
12
|
+
* []{}| should be reserved
|
13
|
+
|
14
|
+
== Functions
|
15
|
+
|
16
|
+
=== Basic
|
17
|
+
Unsupported:
|
18
|
+
* cond
|
19
|
+
* case
|
20
|
+
* let*
|
21
|
+
* letrec
|
22
|
+
* do
|
23
|
+
* delay
|
24
|
+
* read - uses Ruby's gets
|
25
|
+
|
26
|
+
== General
|
27
|
+
Tail-recursion
|
28
|
+
|
29
|
+
Continuations
|
30
|
+
|
data/README.txt
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Bus Scheme
|
1
|
+
= Bus Scheme
|
2
2
|
by Phil Hagelberg (c) 2007 - 2008
|
3
3
|
http://bus-scheme.rubyforge.org
|
4
4
|
|
@@ -6,63 +6,122 @@ Bus Scheme
|
|
6
6
|
|
7
7
|
Bus Scheme is a Scheme written in Ruby, but implemented on the bus!
|
8
8
|
Every programmer must implement Scheme as a rite of passage; this is
|
9
|
-
mine. Note that
|
10
|
-
while on a bus. Documentation, tests, and administrivia may
|
11
|
-
accomplished elsewhere, but
|
12
|
-
bus-driven.
|
13
|
-
|
14
|
-
want to submit a patch, we may be able to work something out regarding
|
15
|
-
code written on trains, ferries, or perhaps even carpool lanes.) Bus
|
16
|
-
Scheme is primarily a toy; using it for anything serious is (right
|
17
|
-
now) ill-advised.
|
9
|
+
mine. Note that at least half of the implementation of Bus Scheme must
|
10
|
+
be written while on a bus. Documentation, tests, and administrivia may
|
11
|
+
be accomplished elsewhere, but the majority of actual implementation
|
12
|
+
code is strictly bus-driven. Bus Scheme is primarily a toy; using it
|
13
|
+
for anything serious is (right now) ill-advised.
|
18
14
|
|
19
15
|
Bus Scheme aims for general Scheme usefulness optimized for learning
|
20
|
-
and fun. It's
|
16
|
+
and fun. It's loosely targeting R5RS, but varies in huge ways. (For
|
17
|
+
the purposes of this project we pretend that R6RS never happened.) See
|
18
|
+
the file R5RS.diff for ways in which Bus Scheme differs from the
|
19
|
+
standard, both things that are yet unimplemented and things that are
|
20
|
+
intentionally different.
|
21
21
|
|
22
|
-
==
|
22
|
+
== Usage
|
23
23
|
|
24
|
-
|
24
|
+
$ bus # drop into the REPL
|
25
25
|
|
26
|
-
|
26
|
+
$ bus -e "(do some stuff)"
|
27
27
|
|
28
|
-
|
28
|
+
$ bus foo.scm # load a file
|
29
29
|
|
30
|
-
==
|
30
|
+
== Tutorial
|
31
31
|
|
32
|
-
|
32
|
+
See http://technomancy.us/104 for a "Getting Started" tutorial.
|
33
33
|
|
34
|
-
|
34
|
+
== What makes Bus Scheme different?
|
35
35
|
|
36
|
-
|
36
|
+
Well, for starters it's implemented on the bus. No other Scheme
|
37
|
+
implementation can claim this. Here are a few other things that set
|
38
|
+
Bus Scheme apart:
|
39
|
+
|
40
|
+
=== Flexible calling syntax
|
41
|
+
|
42
|
+
Taking a hint from Arc, Bus Scheme allows you to use the notation
|
43
|
+
(mylist n) to access the nth place of the mylist list instead of (nth
|
44
|
+
mylist n) or the (myhash key) notation to access the slot in myhash
|
45
|
+
corresponding to the value of key instead of (gethash myhash key).
|
46
|
+
TODO: This notation is flexible, and other data types may have
|
47
|
+
their own "call behaviour" specified.
|
48
|
+
|
49
|
+
=== Web functionality
|
50
|
+
|
51
|
+
Planned: Web and RESTful application development are part of the
|
52
|
+
package. Bus Scheme uses the Rack library to allow scheme programs to
|
53
|
+
serve web applications. Representations of data can be easily
|
54
|
+
translated between s-expressions, HTML, and JSON.
|
55
|
+
|
56
|
+
=== Written in a high-level language
|
57
|
+
|
58
|
+
Bus Scheme is written in Ruby, which means anyone with experience in
|
59
|
+
high-level dynamic languages (like, oh, I don't know... Scheme?)
|
60
|
+
should be right at home poking around at the implementation. Using
|
61
|
+
Ruby allows the implementation code to remain compact and concise. Bus
|
62
|
+
Scheme should run on Ruby 1.8, Ruby 1.9, and Rubinius at least. Bus
|
63
|
+
Scheme also allows you to drop into Ruby when that's convenient. TODO:
|
64
|
+
allow real inline Ruby blocks instead of access via a function call.
|
65
|
+
|
66
|
+
=== Test-Driven
|
67
|
+
|
68
|
+
Bus Scheme is written in an entirely test-driven manner. As much as
|
69
|
+
possible, it tries to keep its tests written in Scheme itself, so it
|
70
|
+
includes a fairly comprehensive testing suite and encourages programs
|
71
|
+
to be written test-first.
|
72
|
+
|
73
|
+
== Install
|
74
|
+
|
75
|
+
* sudo gem install bus-scheme
|
76
|
+
|
77
|
+
For the source:
|
78
|
+
|
79
|
+
* git clone git://github.com/technomancy/bus-scheme.git
|
37
80
|
|
38
81
|
== Todo
|
39
82
|
|
40
83
|
Bus Scheme is currently missing pieces of functionality:
|
41
84
|
|
42
85
|
=== Parser
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
*** look up and enforce rules for identifier names
|
86
|
+
* character literals
|
87
|
+
* multiline strings
|
88
|
+
* regular expressions
|
47
89
|
|
48
90
|
=== General
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
** optimize tail call recursion
|
53
|
-
** some kind of load path?
|
91
|
+
* filter stacktrace
|
92
|
+
* continuations
|
93
|
+
* macros
|
54
94
|
|
55
95
|
Failing tests for some of these are already included (commented out,
|
56
96
|
mostly) in the relevant test files.
|
57
97
|
|
58
98
|
=== Long Term (post 1.0)
|
59
|
-
|
60
|
-
|
61
|
-
|
99
|
+
* web functions (defresource and friends)
|
100
|
+
* (lambda (arg1 arg2 . args) body) for rest args
|
101
|
+
* string interpolation
|
102
|
+
* escape sequences in strings
|
103
|
+
* Ruby blocks inline?
|
104
|
+
* XML literals?
|
105
|
+
* optimize tail call recursion
|
106
|
+
* compile to Rubinius bytecode
|
107
|
+
* custom call behaviour
|
108
|
+
* parse non-decimal base numbers
|
109
|
+
* parse rationals, scientific, complex, and polar complex numbers
|
62
110
|
|
63
111
|
== Requirements
|
64
112
|
|
65
|
-
Bus Scheme should run on (at least) Ruby 1.8, Ruby 1.9, and
|
113
|
+
Bus Scheme should run on (at least) Ruby 1.8, Ruby 1.9, and
|
114
|
+
Rubinius. Any support for Windows is entirely accidental.
|
115
|
+
|
116
|
+
== Contributing
|
117
|
+
|
118
|
+
Patches are welcome especially if they were written while riding a
|
119
|
+
bus. If your daily commute does not involve a bus but you want to
|
120
|
+
submit a patch, we may be able to work something out regarding code
|
121
|
+
written on trains, ferries, or perhaps even carpool lanes.
|
122
|
+
|
123
|
+
Join the mailing list to ask questions and discuss:
|
124
|
+
http://rubyforge.org/mail/?group_id=5094
|
66
125
|
|
67
126
|
== Bonus Fact
|
68
127
|
|
data/Rakefile
CHANGED
@@ -5,13 +5,15 @@ require 'hoe'
|
|
5
5
|
require './lib/bus_scheme.rb'
|
6
6
|
require 'rake/testtask'
|
7
7
|
|
8
|
+
BIN = ENV['bin'] || "~/src/rubinius/shotgun/rubinius"
|
9
|
+
|
8
10
|
Hoe.new('bus-scheme', BusScheme::VERSION) do |p|
|
9
11
|
p.rubyforge_name = 'bus-scheme'
|
10
12
|
p.author = 'Phil Hagelberg'
|
11
13
|
p.email = 'technomancy@gmail.com'
|
12
14
|
p.summary = 'Bus Scheme is a Scheme in Ruby, imlemented on the bus.'
|
13
15
|
p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
|
14
|
-
p.url =
|
16
|
+
p.url = 'http://bus-scheme.rubyforge.org'
|
15
17
|
p.remote_rdoc_dir = ''
|
16
18
|
end
|
17
19
|
|
@@ -28,12 +30,37 @@ end
|
|
28
30
|
|
29
31
|
desc "Show todo items"
|
30
32
|
task :todo do
|
31
|
-
puts File.read('README.txt').match(/== Todo(.*)== Requirements/m)[1].split("\n").grep(/^(
|
33
|
+
puts File.read('README.txt').match(/== Todo(.*)== Requirements/m)[1].split("\n").grep(/^(\*|===)/).join("\n")
|
34
|
+
puts "Within the code:"
|
35
|
+
system "grep -r TODO lib"
|
32
36
|
end
|
33
37
|
|
34
38
|
desc "Show tests that have been commented out"
|
35
39
|
task :commented_tests do
|
36
40
|
Dir.glob('test/test_*.rb').each do |file|
|
37
|
-
puts File.read(file).grep(/^\s
|
41
|
+
puts File.read(file).grep(/^\s*#+\s*def (test_[^ ]*)/)
|
42
|
+
end
|
43
|
+
|
44
|
+
Dir.glob('test/test_*.scm').each do |file|
|
45
|
+
puts File.read(file).grep(/^\s*;+\s*\(assert/)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# TODO: use multiruby, duh
|
50
|
+
desc "Run ruby tests in Rubinius"
|
51
|
+
task :rbx_test do
|
52
|
+
if ENV['test']
|
53
|
+
system "#{BIN} test/test_#{ENV['test']}.rb"
|
54
|
+
else
|
55
|
+
system "#{BIN} -w -Ilib:test -e '#{Dir.glob('test/test_*.rb').map{ |f| "require \"" + f + "\" "}.join('; ')}'"
|
38
56
|
end
|
39
57
|
end
|
58
|
+
|
59
|
+
desc 'Run tests written in Scheme'
|
60
|
+
task :scheme_test do
|
61
|
+
Dir.glob('test/test_*.scm').each do |file|
|
62
|
+
BusScheme.load(file)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
task :default => [:test, :scheme_test]
|
data/bin/bus
CHANGED
@@ -8,7 +8,7 @@ if ARGV.empty?
|
|
8
8
|
elsif ARGV.first == '-e' and ARGV.length == 2
|
9
9
|
puts BusScheme.eval_string(ARGV[1])
|
10
10
|
elsif ARGV.length == 1 and File.exist?(ARGV.first)
|
11
|
-
puts BusScheme.
|
11
|
+
puts BusScheme.eval_string("(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\"]
|
data/examples/fib.scm
ADDED
data/lib/array_extensions.rb
CHANGED
@@ -7,15 +7,18 @@ class Array
|
|
7
7
|
alias_method :car, :first
|
8
8
|
alias_method :cdr, :rest
|
9
9
|
|
10
|
-
def to_list
|
11
|
-
if
|
12
|
-
|
10
|
+
def to_list(recursive = false)
|
11
|
+
self[0] = first.sexp(recursive) if recursive
|
12
|
+
|
13
|
+
if self.cdr.nil? or self.cdr.empty?
|
14
|
+
BusScheme::Cons.new(car, nil)
|
13
15
|
else
|
14
|
-
BusScheme::Cons.new(
|
16
|
+
BusScheme::Cons.new(car, self.cdr.sexp(recursive))
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
|
-
alias_method :
|
20
|
+
alias_method :sexp, :to_list
|
21
|
+
include Callable
|
19
22
|
end
|
20
23
|
|
21
24
|
module Enumerable # for 1.9, zip is defined on Enumerable
|
data/lib/bus_scheme.rb
CHANGED
@@ -1,40 +1,56 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
begin
|
4
|
-
require 'readline'
|
5
|
-
require 'yaml'
|
6
|
-
rescue LoadError
|
7
|
-
end
|
8
|
-
|
9
3
|
$LOAD_PATH << File.dirname(__FILE__)
|
4
|
+
require 'readline'
|
10
5
|
require 'object_extensions'
|
11
6
|
require 'array_extensions'
|
7
|
+
|
12
8
|
require 'parser'
|
13
9
|
require 'eval'
|
14
|
-
require 'primitives'
|
15
10
|
require 'cons'
|
16
11
|
require 'lambda'
|
12
|
+
require 'stack_frame'
|
13
|
+
require 'primitives'
|
14
|
+
|
15
|
+
begin
|
16
|
+
require 'web'
|
17
|
+
require 'xml'
|
18
|
+
rescue LoadError
|
19
|
+
puts "Could not load web functionality."
|
20
|
+
end
|
17
21
|
|
18
22
|
module BusScheme
|
19
|
-
VERSION = "0.7.
|
23
|
+
VERSION = "0.7.6"
|
20
24
|
|
21
|
-
SYMBOL_TABLE = {}.merge(PRIMITIVES).merge(SPECIAL_FORMS)
|
22
25
|
PROMPT = '> '
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
SPECIAL_FORMS.has_key?(symbol)
|
27
|
-
end
|
26
|
+
INCOMPLETE_PROMPT = ' ... '
|
27
|
+
BusScheme['load-path'.sym] = Cons.new("#{File.dirname(__FILE__)}/scheme/",
|
28
|
+
Cons.new(File.expand_path('.'), nil))
|
28
29
|
|
30
|
+
class BusSchemeError < StandardError; end
|
31
|
+
class ParseError < BusSchemeError; end
|
32
|
+
class EvalError < BusSchemeError; end
|
33
|
+
class LoadError < BusSchemeError; end
|
34
|
+
class IncompleteError < BusSchemeError; end
|
35
|
+
class ArgumentError < BusSchemeError; end
|
36
|
+
class AssertionFailed < BusSchemeError; end
|
37
|
+
|
29
38
|
# Read-Eval-Print-Loop
|
30
39
|
def self.repl
|
31
40
|
loop do
|
32
41
|
puts begin
|
33
42
|
input = Readline.readline(PROMPT)
|
34
43
|
exit if input.nil? # only Ctrl-D produces nil here it seems
|
35
|
-
|
44
|
+
begin # allow for multiline input
|
45
|
+
result = BusScheme.eval_string(input).inspect
|
46
|
+
rescue IncompleteError
|
47
|
+
input += "\n" + Readline.readline(INCOMPLETE_PROMPT)
|
48
|
+
retry
|
49
|
+
end
|
50
|
+
Readline::HISTORY.push(input)
|
51
|
+
result
|
36
52
|
rescue Interrupt
|
37
|
-
'Type "(quit)" to leave Bus Scheme.'
|
53
|
+
'Type "(quit)" or press Ctrl-D to leave Bus Scheme.'
|
38
54
|
rescue BusSchemeError => e
|
39
55
|
"Error: #{e}"
|
40
56
|
rescue StandardError => e
|
@@ -44,5 +60,30 @@ module BusScheme
|
|
44
60
|
end
|
45
61
|
end
|
46
62
|
|
47
|
-
|
63
|
+
# Load a file if on the load path or absolute
|
64
|
+
def self.load(filename)
|
65
|
+
begin
|
66
|
+
loaded_files.push filename
|
67
|
+
eval_string File.read(add_load_path(filename))
|
68
|
+
loaded_files.pop
|
69
|
+
rescue
|
70
|
+
loaded_files.pop
|
71
|
+
raise
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.add_load_path(filename, load_path = BusScheme['load-path'.sym])
|
76
|
+
return filename if filename.match(/^\//) or File.exist? filename
|
77
|
+
raise LoadError, "File not found: #{filename}" if load_path.empty?
|
78
|
+
return load_path.car + '/' + filename if File.exist? load_path.car + '/' + filename
|
79
|
+
return add_load_path(filename, load_path.cdr)
|
80
|
+
end
|
81
|
+
|
82
|
+
# For stack traces
|
83
|
+
def self.loaded_files
|
84
|
+
(@loaded_files ||= ["(eval)"])
|
85
|
+
end
|
86
|
+
|
87
|
+
['core.scm', 'test.scm', 'list.scm', 'predicates.scm'
|
88
|
+
].each { |f| load(f) }
|
48
89
|
end
|
data/lib/cons.rb
CHANGED
@@ -1,18 +1,43 @@
|
|
1
1
|
module BusScheme
|
2
2
|
class Cons
|
3
3
|
attr_accessor :car, :cdr
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
# TODO: figure out default values
|
6
|
+
def initialize(car, cdr)
|
6
7
|
@car, @cdr = [car, cdr]
|
7
8
|
end
|
8
9
|
|
9
10
|
def ==(other)
|
10
|
-
|
11
|
+
other.respond_to?(:car) and @car == other.car and
|
12
|
+
other.respond_to?(:cdr) and @cdr == other.cdr
|
11
13
|
end
|
12
14
|
|
13
15
|
alias_method :first, :car
|
14
16
|
alias_method :rest, :cdr
|
15
17
|
|
18
|
+
def length
|
19
|
+
return 0 if @car.nil? and @cdr.nil?
|
20
|
+
return 1 if @cdr.nil?
|
21
|
+
return 2 if !@cdr.respond_to? :length
|
22
|
+
1 + @cdr.length
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :size, :length
|
26
|
+
def last
|
27
|
+
# TODO: do this list-style
|
28
|
+
self.to_a.last
|
29
|
+
end
|
30
|
+
|
31
|
+
def map(mapper = nil, &block)
|
32
|
+
mapper ||= block
|
33
|
+
Cons.new(mapper.call(@car), @cdr ? @cdr.map(mapper) : @cdr)
|
34
|
+
end
|
35
|
+
|
36
|
+
def each(&block)
|
37
|
+
yield @car
|
38
|
+
@cdr.each(&block) if @cdr && @cdr.respond_to?(:each)
|
39
|
+
end
|
40
|
+
|
16
41
|
def to_a
|
17
42
|
if @cdr.respond_to? :to_a
|
18
43
|
[@car] + @cdr.to_a
|
@@ -23,6 +48,10 @@ module BusScheme
|
|
23
48
|
end
|
24
49
|
end
|
25
50
|
|
51
|
+
def empty?
|
52
|
+
@car.nil? && @cdr.nil?
|
53
|
+
end
|
54
|
+
|
26
55
|
def inspect(open = '(', close = ')')
|
27
56
|
str = open + @car.inspect
|
28
57
|
if @cdr.nil?
|
@@ -33,9 +62,20 @@ module BusScheme
|
|
33
62
|
str + ' . ' + @cdr.inspect + close
|
34
63
|
end
|
35
64
|
end
|
65
|
+
|
66
|
+
def to_list
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
# allows for (mylist 4) => (nth mylist 4)
|
71
|
+
def call(nth)
|
72
|
+
nth == 0 ? @car : @cdr.call(nth - 1)
|
73
|
+
end
|
74
|
+
include Callable
|
36
75
|
end
|
37
76
|
|
38
77
|
def cons(car, cdr = nil)
|
39
78
|
Cons.new(car, cdr)
|
40
79
|
end
|
80
|
+
module_function :cons
|
41
81
|
end
|