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
-
.[](symbol) ⇒ Object
symbol lookup.
-
.[]=(symbol, value) ⇒ Object
symbol assignment to value.
-
.apply(function, *args) ⇒ Object
Call a function with given args.
-
.clear_symbols(*symbols) ⇒ Object
remove symbols from all scopes.
-
.eval_form(form) ⇒ Object
Eval a form passed in as an array.
-
.eval_lambda(lambda, args) ⇒ Object
All the super lambda magic happens (or fails to happen) here.
-
.eval_string(string) ⇒ Object
Parse a string, then eval the result.
-
.in_scope?(symbol) ⇒ Boolean
symbol existence predicate.
-
.normalize_whitespace(string) ⇒ Object
Treat all whitespace in a string as spaces.
-
.parse(input) ⇒ Object
Turn an input string into an S-expression.
-
.parse_list(tokens) ⇒ Object
Nest a list from a 1-dimensional list of tokens.
-
.parse_tokens(tokens) ⇒ Object
Turn a list of tokens into a properly-nested S-expression.
-
.pop_token(input) ⇒ Object
Take a token off the input string and return it.
-
.repl ⇒ Object
Read-Eval-Print-Loop.
-
.special_form?(symbol) ⇒ Boolean
symbol special form predicate.
-
.tokenize(input) ⇒ Object
Split an input string into lexically valid tokens.
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
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 |
.repl ⇒ Object
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
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 |