Module: BusScheme

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

Defined Under Namespace

Classes: ArgumentError, EvalError, ParseError

Constant Summary collapse

VERSION =
"0.6"
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 },
  '/'.intern => lambda { |x, y| x / y },
  :* => lambda { |*args| args.inject(1) { |product, i| product * i } },

  :> => lambda { |x, y| x > y },
  :< => lambda { |x, y| x < y },

  :intern => lambda { |x| x.intern },
  :concat => lambda { |x, y| x + y },
  :substring => lambda { |x, from, to| x[from .. to] },

  :exit => lambda { exit }, :quit => lambda { exit },
}
SPECIAL_FORMS =
{
  :quote => lambda { |arg| arg },
  :if => lambda { |condition, yes, *no| eval_form(condition) ? eval_form(yes) : eval_form([:begin] + no) },
  :begin => lambda { |*args| args.map{ |arg| eval_form(arg) }.last },
  :set! => lambda { },
  :lambda => lambda { |args, *form| [:lambda, args] + form },
  :define => lambda { |sym, definition| BusScheme[sym] = eval_form(definition); sym },
}
SYMBOL_TABLE =
{}.merge(PRIMITIVES).merge(SPECIAL_FORMS)
SCOPES =
[SYMBOL_TABLE]
PROMPT =
'> '

Class Method Summary collapse

Class Method Details

.[](symbol) ⇒ Object

symbol lookup



60
61
62
# File 'lib/bus_scheme.rb', line 60

def self.[](symbol)
  SCOPES.last[symbol] or SCOPES.first[symbol]
end

.[]=(symbol, value) ⇒ Object

symbol assignment to value



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

def self.[]=(symbol, value)
  SCOPES.last[symbol] = value
end

.apply(function, *args) ⇒ Object

Call a function with given args



23
24
25
26
27
28
29
30
31
32
33
# File 'lib/eval.rb', line 23

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

  # refactor me
  if function.is_a?(Array) and function.lambda?
    function.call(*args)
  else
    raise "Undefined symbol: #{function}" unless in_scope?(function)
    BusScheme[function].call(*args)
  end
end

.clear_symbols(*symbols) ⇒ Object

remove symbols from all scopes



70
71
72
# File 'lib/bus_scheme.rb', line 70

def self.clear_symbols(*symbols)
  SCOPES.map{ |scope| symbols.map{ |sym| scope.delete sym } }
end

.eval_form(form) ⇒ Object

Eval a form passed in as an array



9
10
11
12
13
14
15
16
17
18
19
20
# 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
    raise "Undefined symbol: #{form}" unless in_scope?(form)
    BusScheme[form]
  else
    form
  end
end

.eval_lambda(lambda, args) ⇒ Object

All the super lambda magic happens (or fails to happen) here



36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/eval.rb', line 36

def eval_lambda(lambda, args)
  raise BusScheme::EvalError unless lambda.shift == :lambda

  arg_list = lambda.shift
  raise BusScheme::ArgumentError if !arg_list.is_a?(Array) or arg_list.length != args.length

  SCOPES << {} # new scope
  until arg_list.empty?
    BusScheme[arg_list.shift] = args.shift
  end

  # using affect as a non-return-value-affecting callback
  BusScheme[:begin].call(*lambda).affect { SCOPES.pop }
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

.in_scope?(symbol) ⇒ Boolean

symbol existence predicate

Returns:

  • (Boolean)


55
56
57
# File 'lib/bus_scheme.rb', line 55

def self.in_scope?(symbol)
  SCOPES.last.has_key?(symbol) or SCOPES.first.has_key?(symbol)
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



80
81
82
83
84
85
86
87
88
# File 'lib/bus_scheme.rb', line 80

def self.repl
  loop do
    begin
      puts BusScheme.eval_string(Readline.readline(PROMPT))
    rescue Interrupt
      puts 'Type "(quit)" to leave Bus Scheme.'
    end
  end
end

.special_form?(symbol) ⇒ Boolean

symbol special form predicate

Returns:

  • (Boolean)


75
76
77
# File 'lib/bus_scheme.rb', line 75

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