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 CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2007, Phil Hagelberg
1
+ Copyright (c) 2007 - 2008, Phil Hagelberg
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without
@@ -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
@@ -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 all the implementation of Bus Scheme must be written
10
- while on a bus. Documentation, tests, and administrivia may be
11
- accomplished elsewhere, but all actual implementation code is strictly
12
- bus-driven. Patches are welcome as long as they were written while
13
- riding a bus. (If your daily commute does not involve a bus but you
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 not targeting R5RS or anything like that.
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
- == Install
22
+ == Usage
23
23
 
24
- * sudo gem install bus-scheme
24
+ $ bus # drop into the REPL
25
25
 
26
- For the source:
26
+ $ bus -e "(do some stuff)"
27
27
 
28
- * git clone git://git.caboo.se/bus_scheme.git
28
+ $ bus foo.scm # load a file
29
29
 
30
- == Usage
30
+ == Tutorial
31
31
 
32
- $ bus # drop into a repl
32
+ See http://technomancy.us/104 for a "Getting Started" tutorial.
33
33
 
34
- $ bus -e "(do some stuff)"
34
+ == What makes Bus Scheme different?
35
35
 
36
- $ bus foo.scm # load a file
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
- * parse character literals
44
- ** alternate define lambda forms
45
- ** parse dotted cons cells
46
- *** look up and enforce rules for identifier names
86
+ * character literals
87
+ * multiline strings
88
+ * regular expressions
47
89
 
48
90
  === General
49
- * lambda args should get passed in as lists by default, not vectors/arrays
50
- * (lambda args body) for rest args
51
- * stack traces on error plz
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
- * continuations (?!?)
60
- * compile to Rubinius bytecode
61
- * parse rationals, scientific, complex, and polar complex numbers
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 Rubinius.
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 = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
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(/^( \*|===)/).join("\n")
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*#\s*def (test_[^ ]*)/)
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.eval_form([:load, ARGV.first])
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\"]
@@ -0,0 +1,6 @@
1
+ ;; Fibonacci sequence
2
+ (define fib (lambda (x)
3
+ (if (< x 3)
4
+ 1
5
+ (+ (fib (- x 1))
6
+ (fib (- x 2))))))
@@ -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 self.cdr.empty?
12
- BusScheme::Cons.new(self.car, nil)
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(self.car, self.cdr.to_list)
16
+ BusScheme::Cons.new(car, self.cdr.sexp(recursive))
15
17
  end
16
18
  end
17
19
 
18
- alias_method :to_sexp, :to_list
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
@@ -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.5"
23
+ VERSION = "0.7.6"
20
24
 
21
- SYMBOL_TABLE = {}.merge(PRIMITIVES).merge(SPECIAL_FORMS)
22
25
  PROMPT = '> '
23
-
24
- # symbol special form predicate
25
- def self.special_form?(symbol)
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
- BusScheme.eval_string input
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
- ['core'].each { |file| SYMBOL_TABLE[:load].call("#{File.dirname(__FILE__)}/scheme/#{file}.scm") }
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
@@ -1,18 +1,43 @@
1
1
  module BusScheme
2
2
  class Cons
3
3
  attr_accessor :car, :cdr
4
-
5
- def initialize(car, cdr = nil)
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
- @car == other.car and @cdr == other.cdr
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