bus-scheme 0.7.5 → 0.7.6
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/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
|