Module: BusScheme

Defined in:
lib/eval.rb,
lib/lambda.rb,
lib/parser.rb,
lib/bus_scheme.rb,
lib/definitions.rb

Defined Under Namespace

Classes: ArgumentError, BusSchemeError, EvalError, Lambda, ParseError, Scope

Constant Summary collapse

VERSION =
"0.7.1"
SYMBOL_TABLE =
{}.merge(PRIMITIVES).merge(SPECIAL_FORMS)
PROMPT =
'> '
PRIMITIVES =
{
  '#t'.intern => true, # :'#t' screws up emacs' ruby parser
  '#f'.intern => false,

  :+ => lambda { |*args| args.inject(0) { |sum, i| sum + i } },
  :- => lambda { |x, y| x - y },
  :* => lambda { |*args| args.inject(1) { |product, i| product * i } },
  '/'.intern => lambda { |x, y| x / y },

  :> => lambda { |x, y| x > y },
  :< => lambda { |x, y| x < y },
  :'=' => lambda { |x, y| x == y }, # may not honor scheme's equality notions
  :null? => lambda { |x| x.nil? },
  
  :intern => lambda { |x| x.intern },
  :substring => lambda { |x, from, to| x[from .. to] },

  :car => lambda { |list| list.car },
  :cdr => lambda { |list| list.cdr },

  :ruby => lambda { |code| eval(code) },
  :load => lambda { |filename| eval_string(File.read(filename)) },
  :exit => lambda { exit }, :quit => lambda { exit },
}
SPECIAL_FORMS =

if we add in macros, can some of these be defined in scheme?

{
  :quote => lambda { |arg| arg },
  # TODO: check that nil, () and #f all behave according to spec
  :if => lambda { |q, yes, *no| eval_form(q) ? eval_form(yes) : eval_form([:begin] + no) },
  :begin => lambda { |*args| args.map{ |arg| eval_form(arg) }.last },
  :set! => lambda { |sym, value| BusScheme[sym] and 
    BusScheme[sym] = eval_form(value); sym },
  :lambda => lambda { |args, *form| Lambda.new(args, form) },
  :define => lambda { |sym, definition| BusScheme[sym] = eval_form(definition); sym },
}

Class Method Summary collapse

Class Method Details

.[](symbol) ⇒ Object

symbol lookup

Raises:



29
30
31
32
33
# File 'lib/bus_scheme.rb', line 29

def self.[](symbol)
  scope = scope_of(symbol)
  raise EvalError.new("Undefined symbol: #{symbol}") unless scope
  scope && scope[symbol]
end

.[]=(symbol, value) ⇒ Object

symbol assignment to value



36
37
38
# File 'lib/bus_scheme.rb', line 36

def self.[]=(symbol, value)
  (scope_of(symbol) || Lambda.scope || SYMBOL_TABLE)[symbol] = value
end

.apply(function, *args) ⇒ Object

Call a function with given args



22
23
24
25
# File 'lib/eval.rb', line 22

def apply(function, *args)
  args.map!{ |arg| eval_form(arg) } unless special_form?(function)
  eval_form(function).call(*args)
end

.eval_form(form) ⇒ Object

Eval a form passed in as an array



9
10
11
12
13
14
15
16
17
18
19
# File 'lib/eval.rb', line 9

def eval_form(form)
  if form == []
    nil
  elsif form.is_a? Array
    apply(form.first, *form.rest)
  elsif form.is_a? Symbol
    BusScheme[form]
  else # well it must be a literal then
    form
  end
end

.eval_string(string) ⇒ Object

Parse a string, then eval the result



4
5
6
# File 'lib/eval.rb', line 4

def eval_string(string)
  eval_form(parse(string))
end

.normalize_whitespace(string) ⇒ Object

Treat all whitespace in a string as spaces



65
66
67
# File 'lib/parser.rb', line 65

def normalize_whitespace(string)
  string && string.gsub(/\t/, ' ').gsub(/\n/, ' ').gsub(/ +/, ' ')
end

.parse(input) ⇒ Object

Turn an input string into an S-expression



4
5
6
# File 'lib/parser.rb', line 4

def parse(input)
  parse_tokens tokenize(normalize_whitespace(input))
end

.parse_list(tokens) ⇒ Object

Nest a list from a 1-dimensional list of tokens



20
21
22
23
24
25
26
27
28
29
30
# File 'lib/parser.rb', line 20

def parse_list(tokens)
  [].affect do |list|
    while element = tokens.shift and element != :')'
      if element == :'('
        list << parse_list(tokens)
      else
        list << element
      end
    end
  end
end

.parse_tokens(tokens) ⇒ Object

Turn a list of tokens into a properly-nested S-expression



9
10
11
12
13
14
15
16
17
# File 'lib/parser.rb', line 9

def parse_tokens(tokens)
  token = tokens.shift
  if token == :'('
    parse_list(tokens)
  else
    raise BusScheme::ParseError unless tokens.empty?
    token # atom
  end
end

.pop_token(input) ⇒ Object

Take a token off the input string and return it



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/parser.rb', line 42

def pop_token(input)
  token = case input
          when /^ +/ # whitespace
            input[0 ... 1] = ''
            return pop_token(input)
          when /^\(/ # open paren
            :'('
          when /^\)/ # closing paren
            :')'
          when /^(\d+)/ # positive integer
            Regexp.last_match[1].to_i
          when /^"(.*?)"/ # string
            Regexp.last_match[1]
          when /^([^ \)]+)/ # symbol
            Regexp.last_match[1].intern
          end
  # compensate for quotation marks
  length = token.is_a?(String) ? token.length + 2 : token.to_s.length
  input[0 .. length - 1] = ''
  return token
end

.replObject

Read-Eval-Print-Loop



46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/bus_scheme.rb', line 46

def self.repl
  loop do
    puts begin
           BusScheme.eval_string(Readline.readline(PROMPT))
         rescue Interrupt
           'Type "(quit)" to leave Bus Scheme.'
         rescue BusSchemeError => e
           "Error: #{e}"
         rescue StandardError => e
           "You found a bug in Bus Scheme!\n" +
             "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
         end
  end
end

.scope_of(symbol) ⇒ Object

what scope is appropraite for this symbol



24
25
26
# File 'lib/bus_scheme.rb', line 24

def self.scope_of(symbol)
  [Lambda.scope, SYMBOL_TABLE].compact.detect { |scope| scope.has_key?(symbol) }
end

.special_form?(symbol) ⇒ Boolean

symbol special form predicate

Returns:

  • (Boolean)


41
42
43
# File 'lib/bus_scheme.rb', line 41

def self.special_form?(symbol)
  SPECIAL_FORMS.has_key?(symbol)
end

.tokenize(input) ⇒ Object

Split an input string into lexically valid tokens



33
34
35
36
37
38
39
# File 'lib/parser.rb', line 33

def tokenize(input)
  [].affect do |tokens|
    while token = pop_token(input)
      tokens << token
    end
  end
end