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