apricot 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +1 -0
  5. data/Gemfile.lock +229 -11
  6. data/README.md +46 -29
  7. data/Rakefile +1 -1
  8. data/apricot.gemspec +7 -3
  9. data/benchmarks/factorial.rb +51 -0
  10. data/benchmarks/interpolate.rb +20 -0
  11. data/bin/apricot +5 -23
  12. data/examples/bot.apr +1 -4
  13. data/examples/cinch-bot.apr +3 -3
  14. data/examples/sinatra.apr +9 -0
  15. data/kernel/core.apr +124 -75
  16. data/kernel/repl.apr +37 -0
  17. data/lib/apricot.rb +7 -26
  18. data/lib/apricot/boot.rb +24 -0
  19. data/lib/apricot/code_loader.rb +108 -0
  20. data/lib/apricot/compiler.rb +265 -32
  21. data/lib/apricot/generator.rb +10 -3
  22. data/lib/apricot/identifier.rb +25 -10
  23. data/lib/apricot/list.rb +28 -41
  24. data/lib/apricot/macroexpand.rb +14 -8
  25. data/lib/apricot/misc.rb +2 -1
  26. data/lib/apricot/namespace.rb +20 -3
  27. data/lib/apricot/{parser.rb → reader.rb} +221 -194
  28. data/lib/apricot/repl.rb +67 -24
  29. data/lib/apricot/ruby_ext.rb +27 -16
  30. data/lib/apricot/scopes.rb +159 -0
  31. data/lib/apricot/seq.rb +43 -1
  32. data/lib/apricot/special_forms.rb +16 -695
  33. data/lib/apricot/special_forms/def.rb +32 -0
  34. data/lib/apricot/special_forms/do.rb +23 -0
  35. data/lib/apricot/special_forms/dot.rb +112 -0
  36. data/lib/apricot/special_forms/fn.rb +342 -0
  37. data/lib/apricot/special_forms/if.rb +31 -0
  38. data/lib/apricot/special_forms/let.rb +8 -0
  39. data/lib/apricot/special_forms/loop.rb +10 -0
  40. data/lib/apricot/special_forms/quote.rb +9 -0
  41. data/lib/apricot/special_forms/recur.rb +26 -0
  42. data/lib/apricot/special_forms/try.rb +146 -0
  43. data/lib/apricot/variables.rb +65 -0
  44. data/lib/apricot/version.rb +1 -1
  45. data/spec/compiler_spec.rb +53 -450
  46. data/spec/fn_spec.rb +206 -0
  47. data/spec/list_spec.rb +1 -1
  48. data/spec/reader_spec.rb +349 -0
  49. data/spec/spec_helper.rb +40 -4
  50. data/spec/special_forms_spec.rb +203 -0
  51. metadata +99 -133
  52. data/lib/apricot/ast.rb +0 -3
  53. data/lib/apricot/ast/identifier.rb +0 -111
  54. data/lib/apricot/ast/list.rb +0 -99
  55. data/lib/apricot/ast/literals.rb +0 -240
  56. data/lib/apricot/ast/node.rb +0 -45
  57. data/lib/apricot/ast/scopes.rb +0 -147
  58. data/lib/apricot/ast/toplevel.rb +0 -66
  59. data/lib/apricot/ast/variables.rb +0 -64
  60. data/lib/apricot/printers.rb +0 -12
  61. data/lib/apricot/stages.rb +0 -60
  62. data/spec/parser_spec.rb +0 -312
@@ -1,7 +1,14 @@
1
1
  module Apricot
2
- class Generator < Rubinius::Generator
3
- def scopes
4
- @scopes ||= []
2
+ class Generator < Rubinius::ToolSet::Runtime::Generator
3
+ attr_reader :scopes
4
+ attr_accessor :tail_position
5
+
6
+ alias_method :tail_position?, :tail_position
7
+
8
+ def initialize
9
+ super
10
+ @scopes = []
11
+ @tail_position = false
5
12
  end
6
13
 
7
14
  def scope
@@ -1,6 +1,6 @@
1
1
  module Apricot
2
2
  class Identifier
3
- attr_reader :name, :ns, :unqualified_name
3
+ attr_reader :name, :unqualified_name
4
4
 
5
5
  @table = {}
6
6
 
@@ -19,16 +19,15 @@ module Apricot
19
19
  @const_names = @name.to_s.split('::').map(&:to_sym)
20
20
  elsif @name =~ /\A(.+?)\/(.+)\z/
21
21
  @qualified = true
22
- ns_id = Identifier.intern($1)
23
- raise 'namespace in identifier must be a constant' unless ns_id.constant?
22
+ qualifier_id = Identifier.intern($1)
23
+ raise 'Qualifier in qualified identifier must be a constant' unless qualifier_id.constant?
24
24
 
25
- @ns = ns_id.const_names.reduce(Object) do |mod, name|
25
+ @qualifier = qualifier_id.const_names.reduce(Object) do |mod, name|
26
26
  mod.const_get(name)
27
27
  end
28
28
 
29
29
  @unqualified_name = $2.to_sym
30
30
  else
31
- @ns = Apricot.current_namespace
32
31
  @unqualified_name = name
33
32
  end
34
33
  end
@@ -37,14 +36,30 @@ module Apricot
37
36
  @qualified
38
37
  end
39
38
 
40
- def unqualified_name
41
- @unqualified_name
42
- end
43
-
44
39
  def constant?
45
40
  @constant
46
41
  end
47
42
 
43
+ # Does the identifier reference a fn on a namespace?
44
+ def fn?
45
+ qualifier.is_a?(Namespace) && qualifier.fns.include?(@unqualified_name)
46
+ end
47
+
48
+ # Does the identifier reference a method on a module?
49
+ def method?
50
+ !qualifier.is_a?(Namespace) && qualifier.respond_to?(@unqualified_name)
51
+ end
52
+
53
+ # Get the metadata of the object this identifier references, or nil.
54
+ def meta
55
+ qualifier.is_a?(Namespace) && qualifier.vars[@unqualified_name] &&
56
+ qualifier.vars[@unqualified_name].apricot_meta
57
+ end
58
+
59
+ def qualifier
60
+ @qualifier ||= Apricot.current_namespace
61
+ end
62
+
48
63
  def const_names
49
64
  raise "#{@name} is not a constant" unless constant?
50
65
  @const_names
@@ -71,7 +86,7 @@ module Apricot
71
86
  # be parsed as keywords or numbers
72
87
  str = @name.to_s.gsub(/(\\.)|\|/) { $1 || '\|' }
73
88
  "#|#{str}|"
74
- when /\A#{Apricot::Parser::IDENTIFIER}+\z/
89
+ when /\A#{Reader::IDENTIFIER}+\z/
75
90
  @name.to_s
76
91
  else
77
92
  str = @name.to_s.inspect[1..-2]
@@ -4,59 +4,25 @@ module Apricot
4
4
  include Seq
5
5
 
6
6
  def self.[](*args)
7
- list = EmptyList
7
+ list = EMPTY_LIST
8
8
  args.reverse_each do |arg|
9
9
  list = list.cons(arg)
10
10
  end
11
11
  list
12
12
  end
13
13
 
14
- attr_reader :head, :tail
14
+ attr_reader :head, :tail, :count
15
15
 
16
16
  def initialize(head, tail)
17
17
  @head = head
18
- @tail = tail
18
+ @tail = tail || EMPTY_LIST
19
+ @count = tail ? tail.count + 1 : 1
19
20
  end
20
21
 
21
22
  def cons(x)
22
23
  List.new(x, self)
23
24
  end
24
25
 
25
- def each
26
- list = self
27
- until list.empty?
28
- yield list.head
29
- list = list.tail
30
- end
31
- end
32
-
33
- def ==(other)
34
- return true if self.equal? other
35
- return false unless other.is_a? List
36
-
37
- list = self
38
-
39
- until list.empty?
40
- return false if other.empty? || list.head != other.head
41
-
42
- list = list.tail
43
- other = other.tail
44
- end
45
-
46
- other.empty?
47
- end
48
-
49
- alias_method :eql?, :==
50
-
51
- def hash
52
- hashes = map {|x| x.hash }
53
- hashes.reduce(hashes.size) {|acc,hash| acc ^ hash }
54
- end
55
-
56
- def empty?
57
- !@tail
58
- end
59
-
60
26
  def initialize_copy(other)
61
27
  super
62
28
  @tail = other.tail.dup if other.tail && !other.tail.empty?
@@ -69,7 +35,7 @@ module Apricot
69
35
  end
70
36
 
71
37
  def first
72
- empty? ? nil : @head
38
+ @head
73
39
  end
74
40
 
75
41
  def next
@@ -77,7 +43,7 @@ module Apricot
77
43
  end
78
44
 
79
45
  def to_seq
80
- empty? ? nil : self
46
+ self
81
47
  end
82
48
 
83
49
  def inspect
@@ -91,6 +57,27 @@ module Apricot
91
57
 
92
58
  alias_method :to_s, :inspect
93
59
 
94
- EmptyList = new(nil, nil)
60
+ class EmptyList < List
61
+ def initialize
62
+ @count = 0
63
+ end
64
+
65
+ def each
66
+ end
67
+
68
+ def empty?
69
+ true
70
+ end
71
+
72
+ def first
73
+ nil
74
+ end
75
+
76
+ def next
77
+ nil
78
+ end
79
+ end
80
+
81
+ EMPTY_LIST = EmptyList.new
95
82
  end
96
83
  end
@@ -1,30 +1,36 @@
1
1
  module Apricot
2
2
  def self.macroexpand(form)
3
3
  ex = macroexpand_1(form)
4
- ex.equal?(form) ? ex : macroexpand(ex)
4
+
5
+ until ex.equal?(form)
6
+ form = ex
7
+ ex = macroexpand_1(form)
8
+ end
9
+
10
+ ex
5
11
  end
6
12
 
7
13
  def self.macroexpand_1(form)
8
- return form unless form.is_a? List
14
+ return form unless form.is_a? Seq
9
15
 
10
16
  callee = form.first
11
17
  return form unless callee.is_a?(Identifier) && !callee.constant?
12
18
 
13
19
  name = callee.name
14
20
  name_s = name.to_s
15
- args = form.tail
21
+ args = form.rest
16
22
 
17
23
  # Handle the (.method receiver args*) send expression form
18
- if name.length > 1 && name_s != '..' && name_s.start_with?('.')
24
+ if name.length > 1 && name != :'..' && name_s.start_with?('.')
19
25
  raise ArgumentError, "Too few arguments to send expression, expecting (.method receiver ...)" if args.empty?
20
26
 
21
27
  dot = Identifier.intern(:'.')
22
28
  method = Identifier.intern(name_s[1..-1])
23
- return List[dot, args.first, method, *args.tail]
29
+ return List[dot, args.first, method, *args.rest]
24
30
  end
25
31
 
26
32
  # Handle the (Class. args*) shorthand new form
27
- if name.length > 1 && name_s != '..' && name_s.end_with?('.')
33
+ if name.length > 1 && name != :'..' && name_s.end_with?('.')
28
34
  dot = Identifier.intern(:'.')
29
35
  klass = Identifier.intern(name_s[0..-2])
30
36
  new = Identifier.intern(:new)
@@ -32,8 +38,8 @@ module Apricot
32
38
  end
33
39
 
34
40
  # Handle defined macros
35
- if callee.ns.is_a?(Namespace) && callee.ns.vars.include?(callee.unqualified_name)
36
- potential_macro = callee.ns.get_var(callee.unqualified_name)
41
+ if callee.qualifier.is_a?(Namespace) && callee.qualifier.vars.include?(callee.unqualified_name)
42
+ potential_macro = callee.qualifier.get_var(callee.unqualified_name)
37
43
  meta = potential_macro.apricot_meta
38
44
 
39
45
  if meta && meta[:macro]
@@ -6,6 +6,7 @@ module Apricot
6
6
  @gensym = 0
7
7
 
8
8
  def self.gensym(prefix = 'g')
9
- :"#{prefix}__#{@gensym += 1}"
9
+ @gensym += 1
10
+ Identifier.intern("#{prefix}__#{@gensym}")
10
11
  end
11
12
  end
@@ -16,11 +16,12 @@ module Apricot
16
16
  ns
17
17
  end
18
18
 
19
- attr_reader :vars, :fns
19
+ attr_reader :vars, :fns, :aliases
20
20
 
21
21
  def initialize
22
22
  @vars = {}
23
23
  @fns = Set[]
24
+ @aliases = {}
24
25
  end
25
26
 
26
27
  def set_var(name, val)
@@ -42,8 +43,24 @@ module Apricot
42
43
  def get_var(name)
43
44
  # raise may be a function defined on the namespace so we need to
44
45
  # explicitly call the Ruby raise method.
45
- Kernel.raise NameError, "Undefined variable '#{name}' on #{self}" unless @vars.include? name
46
- @vars[name]
46
+ Kernel.raise NameError,
47
+ "Undefined variable '#{name}' on #{self}" unless has_var? name
48
+
49
+ if var = @vars[name]
50
+ var
51
+ elsif ns = @aliases[name]
52
+ ns.get_var(name)
53
+ else
54
+ nil
55
+ end
56
+ end
57
+
58
+ def add_alias(name, ns)
59
+ @aliases[name] = ns
60
+ end
61
+
62
+ def has_var?(name)
63
+ @vars.has_key?(name) || @aliases.has_key?(name)
47
64
  end
48
65
  end
49
66
 
@@ -1,63 +1,82 @@
1
1
  require 'stringio'
2
2
 
3
3
  module Apricot
4
- class Parser
4
+ class Reader
5
5
  IDENTIFIER = /[^'`~()\[\]{}";,\s]/
6
6
  OCTAL = /[0-7]/
7
7
  HEX = /[0-9a-fA-F]/
8
8
  DIGITS = ('0'..'9').to_a + ('a'..'z').to_a
9
- CHAR_ESCAPES = {"a" => "\a", "b" => "\b", "t" => "\t", "n" => "\n",
10
- "v" => "\v", "f" => "\f", "r" => "\r", "e" => "\e"}
11
- REGEXP_OPTIONS = {'i' => Regexp::IGNORECASE, 'x' => Regexp::EXTENDED,
12
- 'm' => Regexp::MULTILINE}
9
+
10
+ CHAR_ESCAPES = {
11
+ "a" => "\a", "b" => "\b", "t" => "\t", "n" => "\n",
12
+ "v" => "\v", "f" => "\f", "r" => "\r", "e" => "\e"
13
+ }
14
+
15
+ REGEXP_OPTIONS = {
16
+ 'i' => Regexp::IGNORECASE,
17
+ 'x' => Regexp::EXTENDED,
18
+ 'm' => Regexp::MULTILINE
19
+ }
20
+
21
+ QUOTE = Identifier.intern(:quote)
22
+ UNQUOTE = Identifier.intern(:unquote)
23
+ UNQUOTE_SPLICING = Identifier.intern(:'unquote-splicing')
24
+ CONCAT = Identifier.intern(:concat)
25
+ APPLY = Identifier.intern(:apply)
26
+ LIST = Identifier.intern(:list)
27
+ FN = Identifier.intern(:fn)
13
28
 
14
29
  FnState = Struct.new(:args, :rest)
15
30
 
16
31
  # @param [IO] io an input stream object to read forms from
17
- def initialize(io, filename = "(none)", line = 1)
32
+ def initialize(io, filename = '(none)', line = 1)
18
33
  @filename = filename
19
34
  @io = io
20
35
  @location = 0
21
36
  @line = line
22
37
 
23
38
  @fn_state = []
24
- @syntax_quote_gensyms = []
39
+
40
+ # Read the first character
41
+ next_char
25
42
  end
26
43
 
27
- def self.parse_file(filename)
28
- File.open(filename) {|f| new(f, filename).parse(true) }
44
+ def self.read_file(filename)
45
+ File.open(filename) {|f| new(f, filename).read }
29
46
  end
30
47
 
31
- def self.parse_string(source, filename = "(none)", line = 1)
32
- new(StringIO.new(source, "r"), filename, line).parse
48
+ def self.read_string(source, filename = '(none)', line = 1)
49
+ new(StringIO.new(source), filename, line).read
33
50
  end
34
51
 
35
- # @return [Array<AST::Node>] a list of the forms in the program
36
- def parse(evaluate = false)
37
- program = []
38
- next_char
52
+ # Return a list of the forms that were read.
53
+ def read
54
+ forms = []
39
55
 
40
56
  skip_whitespace
41
57
  while @char
42
- program << parse_form
58
+ forms << read_form
43
59
  skip_whitespace
44
60
  end
45
61
 
46
- Apricot::AST::TopLevel.new(program, @filename, 1, evaluate)
62
+ forms
47
63
  end
48
64
 
49
- # @return AST::Node an AST node representing the form read
50
- def parse_one
51
- next_char
65
+ def read_one
52
66
  skip_whitespace
53
- parse_form
67
+ form = read_form
68
+
69
+ # Unget the last character because the reader always reads one character
70
+ # ahead.
71
+ @io.ungetc(@char) if @char
72
+
73
+ form
54
74
  end
55
75
 
56
76
  private
57
- # Parse Lisp forms until the given character is encountered
58
- # @param [String] terminator the character to stop parsing at
59
- # @return [Array<AST::Node>] a list of the Lisp forms parsed
60
- def parse_forms_until(terminator)
77
+
78
+ # Read forms until the given character is encountered
79
+ def read_forms_until(terminator)
61
80
  skip_whitespace
62
81
  forms = []
63
82
 
@@ -67,7 +86,7 @@ module Apricot
67
86
  return forms
68
87
  end
69
88
 
70
- forms << parse_form
89
+ forms << read_form
71
90
  skip_whitespace
72
91
  end
73
92
 
@@ -75,39 +94,38 @@ module Apricot
75
94
  incomplete_error "Unexpected end of program, expected #{terminator}"
76
95
  end
77
96
 
78
- # Parse a single Lisp form
79
- # @return [AST::Node] an AST node representing the form
80
- def parse_form
97
+ # Read a single Lisp form
98
+ def read_form
81
99
  case @char
82
- when '#' then parse_dispatch
83
- when "'" then parse_quote
84
- when "`" then parse_syntax_quote
85
- when "~" then parse_unquote
86
- when '(' then parse_list
87
- when '[' then parse_array
88
- when '{' then parse_hash
89
- when '"' then parse_string
90
- when ':' then parse_symbol
91
- when /\d/ then parse_number
100
+ when '#' then read_dispatch
101
+ when "'" then read_quote
102
+ when "`" then read_syntax_quote
103
+ when "~" then read_unquote
104
+ when '(' then read_list
105
+ when '[' then read_array
106
+ when '{' then read_hash
107
+ when '"' then read_string
108
+ when ':' then read_symbol
109
+ when /\d/ then read_number
92
110
  when IDENTIFIER
93
111
  if @char =~ /[+-]/ && peek_char =~ /\d/
94
- parse_number
112
+ read_number
95
113
  else
96
- parse_identifier
114
+ read_identifier
97
115
  end
98
116
  else syntax_error "Unexpected character: #{@char}"
99
117
  end
100
118
  end
101
119
 
102
- def parse_dispatch
120
+ def read_dispatch
103
121
  next_char # skip #
104
122
  case @char
105
- when '|' then parse_pipe_identifier
106
- when '{' then parse_set
107
- when '(' then parse_fn
108
- when 'r' then parse_regex
109
- when 'q' then parse_quotation(false)
110
- when 'Q' then parse_quotation(true)
123
+ when '|' then read_pipe_identifier
124
+ when '{' then read_set
125
+ when '(' then read_fn
126
+ when 'r' then read_regex
127
+ when 'q' then read_quotation(false)
128
+ when 'Q' then read_quotation(true)
111
129
  else syntax_error "Unknown reader macro: ##{@char}"
112
130
  end
113
131
  end
@@ -126,163 +144,144 @@ module Apricot
126
144
  next_char; next_char # skip #_
127
145
  skip_whitespace
128
146
  incomplete_error "Unexpected end of program after #_, expected a form" unless @char
129
- parse_form # discard next form
147
+ read_form # discard next form
130
148
  else
131
149
  next_char
132
150
  end
133
151
  end
134
152
  end
135
153
 
136
- def parse_quote
154
+ def read_quote
155
+ line = @line
137
156
  next_char # skip the '
138
157
  skip_whitespace
139
158
  incomplete_error "Unexpected end of program after quote ('), expected a form" unless @char
140
159
 
141
- form = parse_form
142
- quote = AST::Identifier.new(@line, :quote)
143
- AST::List.new(@line, [quote, form])
160
+ with_location List[QUOTE, read_form], line
144
161
  end
145
162
 
146
- def parse_syntax_quote
163
+ def read_syntax_quote
164
+ line = @line
147
165
  next_char # skip the `
148
166
  skip_whitespace
149
167
  incomplete_error "Unexpected end of program after syntax quote (`), expected a form" unless @char
150
168
 
151
- @syntax_quote_gensyms << {}
152
- form = syntax_quote(parse_form)
153
- @syntax_quote_gensyms.pop
154
-
155
- form
169
+ with_location syntax_quote(read_form, {}), line
156
170
  end
157
171
 
158
- def syntax_quote(form)
159
- quote = AST::Identifier.new(@line, :quote)
160
-
172
+ def syntax_quote(form, gensyms)
161
173
  case form
162
- when AST::List
163
- if is_unquote?(form)
164
- form[1]
165
- elsif is_unquote_splicing?(form)
174
+ when Seq
175
+ if is_unquote? form
176
+ form.rest.first
177
+ elsif is_unquote_splicing? form
166
178
  syntax_error "splicing unquote (~@) not in list"
167
179
  else
168
- concat = AST::Identifier.new(@line, :concat)
169
- AST::List.new(@line, [concat] + syntax_quote_list(form.elements))
180
+ syntax_quote_list(form, gensyms)
170
181
  end
171
- when AST::ArrayLiteral
172
- syntax_quote_coll(:array, form.elements)
173
- when AST::SetLiteral
174
- syntax_quote_coll(:set, form.elements)
175
- when AST::HashLiteral
176
- syntax_quote_coll(:hash, form.elements)
177
- when AST::Identifier
182
+ when Array
183
+ syntax_quote_coll(:array, form, gensyms)
184
+ when Set
185
+ syntax_quote_coll(:'hash-set', form, gensyms)
186
+ when Hash
187
+ syntax_quote_coll(:hash, form.to_a.flatten(1), gensyms)
188
+ when Identifier
178
189
  name = form.name
190
+
179
191
  if name.to_s.end_with?('#')
180
- @syntax_quote_gensyms.last[name] ||= Apricot.gensym(name)
181
- id = AST::Identifier.new(@line, @syntax_quote_gensyms.last[name])
182
- AST::List.new(@line, [quote, id])
192
+ gensyms[name] ||= Apricot.gensym(name)
193
+ List[QUOTE, gensyms[name]]
183
194
  else
184
- AST::List.new(@line, [quote, form])
195
+ List[QUOTE, form]
185
196
  end
186
- when AST::BasicLiteral
187
- form
188
197
  else
189
- AST::List.new(@line, [quote, form])
198
+ form
190
199
  end
191
200
  end
192
201
 
193
- def syntax_quote_coll(creator_name, elements)
194
- apply = AST::Identifier.new(@line, :apply)
195
- concat = AST::Identifier.new(@line, :concat)
196
- creator = AST::Identifier.new(@line, creator_name)
197
- list = AST::List.new(@line, [concat] + syntax_quote_list(elements))
198
- AST::List.new(@line, [apply, creator, list])
202
+ def syntax_quote_coll(creator_name, elements, gensyms)
203
+ creator = Identifier.intern(creator_name)
204
+ list = syntax_quote_list(elements, gensyms)
205
+ List[APPLY, creator, list]
199
206
  end
200
207
 
201
- def syntax_quote_list(elements)
202
- list = AST::Identifier.new(@line, :list)
203
-
204
- elements.map do |form|
205
- if is_unquote?(form)
206
- AST::List.new(@line, [list, form[1]])
207
- elsif is_unquote_splicing?(form)
208
- form[1]
208
+ def syntax_quote_list(elements, gensyms)
209
+ parts = elements.map do |form|
210
+ if is_unquote? form
211
+ List[LIST, form.rest.first]
212
+ elsif is_unquote_splicing? form
213
+ form.rest.first
209
214
  else
210
- AST::List.new(@line, [list, syntax_quote(form)])
215
+ List[LIST, syntax_quote(form, gensyms)]
211
216
  end
212
217
  end
213
- end
214
218
 
215
- def is_unquote?(form)
216
- form.is_a?(AST::List) &&
217
- form[0].is_a?(AST::Identifier) &&
218
- form[0].name == :unquote
219
+ Cons.new(CONCAT, parts)
219
220
  end
220
221
 
221
- def is_unquote_splicing?(form)
222
- form.is_a?(AST::List) &&
223
- form[0].is_a?(AST::Identifier) &&
224
- form[0].name == :'unquote-splicing'
225
- end
226
-
227
- def parse_unquote
228
- unquote = :unquote
222
+ def read_unquote
223
+ line = @line
224
+ unquote_type = UNQUOTE
229
225
  next_char # skip the ~
230
226
 
231
227
  if @char == '@'
232
228
  next_char # skip the ~@
233
- unquote = :'unquote-splicing'
229
+ unquote_type = UNQUOTE_SPLICING
234
230
  end
235
231
 
236
232
  skip_whitespace
237
233
 
238
234
  unless @char
239
- syntax = unquote == :unquote ? '~' : '~@'
235
+ syntax = (unquote_type == UNQUOTE ? '~' : '~@')
240
236
  incomplete_error "Unexpected end of program after #{syntax}, expected a form"
241
237
  end
242
238
 
243
- form = parse_form
244
- unquote = AST::Identifier.new(@line, unquote)
245
- AST::List.new(@line, [unquote, form])
239
+ with_location List[unquote_type, read_form], line
246
240
  end
247
241
 
248
- def parse_fn
242
+ def read_fn
243
+ line = @line
244
+
249
245
  @fn_state << FnState.new([], nil)
250
- body = parse_list
246
+ body = read_list
251
247
  state = @fn_state.pop
252
248
 
253
- state.args << :'&' << state.rest if state.rest
249
+ state.args << Identifier.intern(:'&') << state.rest if state.rest
250
+
254
251
  args = state.args.map.with_index do |x, i|
255
- AST::Identifier.new(body.line, x || Apricot.gensym("p#{i + 1}"))
252
+ x || Apricot.gensym("p#{i + 1}")
256
253
  end
257
254
 
258
- AST::List.new(body.line, [AST::Identifier.new(body.line, :fn),
259
- AST::ArrayLiteral.new(body.line, args),
260
- body])
255
+ with_location List[FN, args, body], line
261
256
  end
262
257
 
263
- def parse_list
258
+ def read_list
259
+ line = @line
264
260
  next_char # skip the (
265
- AST::List.new(@line, parse_forms_until(')'))
261
+ with_location read_forms_until(')').to_list, line
266
262
  end
267
263
 
268
- def parse_array
264
+ def read_array
265
+ line = @line
269
266
  next_char # skip the [
270
- AST::ArrayLiteral.new(@line, parse_forms_until(']'))
267
+ with_location read_forms_until(']'), line
271
268
  end
272
269
 
273
- def parse_hash
270
+ def read_hash
271
+ line = @line
274
272
  next_char # skip the {
275
- forms = parse_forms_until('}')
273
+ forms = read_forms_until('}')
276
274
  syntax_error "Odd number of forms in key-value hash" if forms.count.odd?
277
- AST::HashLiteral.new(@line, forms)
275
+ with_location hashify(forms), line
278
276
  end
279
277
 
280
- def parse_set
278
+ def read_set
279
+ line = @line
281
280
  next_char # skip the {
282
- AST::SetLiteral.new(@line, parse_forms_until('}'))
281
+ with_location read_forms_until('}').to_set, line
283
282
  end
284
283
 
285
- def parse_string
284
+ def read_string
286
285
  line = @line
287
286
  next_char # skip the opening "
288
287
  string = ""
@@ -290,38 +289,41 @@ module Apricot
290
289
  while @char
291
290
  if @char == '"'
292
291
  next_char # consume the "
293
- return AST::StringLiteral.new(line, string)
292
+ return with_location string, line
294
293
  end
295
294
 
296
- string << parse_string_char
295
+ string << read_string_char
297
296
  end
298
297
 
299
298
  # Can only reach here if we run out of chars without getting a "
300
299
  incomplete_error "Unexpected end of program while parsing string"
301
300
  end
302
301
 
303
- def parse_string_char
304
- char = if @char == "\\"
305
- next_char
306
- if CHAR_ESCAPES.has_key?(@char)
307
- CHAR_ESCAPES[consume_char]
308
- elsif @char =~ OCTAL
309
- char_escape_helper(8, OCTAL, 3)
310
- elsif @char == 'x'
311
- next_char
312
- syntax_error "Invalid hex character escape" unless @char =~ HEX
313
- char_escape_helper(16, HEX, 2)
314
- else
315
- consume_char
316
- end
317
- else
318
- consume_char
319
- end
302
+ def read_string_char
303
+ char =
304
+ if @char == "\\"
305
+ next_char
306
+ if CHAR_ESCAPES.has_key?(@char)
307
+ CHAR_ESCAPES[consume_char]
308
+ elsif @char =~ OCTAL
309
+ char_escape_helper(8, OCTAL, 3)
310
+ elsif @char == 'x'
311
+ next_char
312
+ syntax_error "Invalid hex character escape" unless @char =~ HEX
313
+ char_escape_helper(16, HEX, 2)
314
+ else
315
+ consume_char
316
+ end
317
+ else
318
+ consume_char
319
+ end
320
+
320
321
  incomplete_error "Unexpected end of file while parsing character escape" unless char
322
+
321
323
  char
322
324
  end
323
325
 
324
- # Parse digits in a certain base for string character escapes
326
+ # Read digits in a certain base for string character escapes
325
327
  def char_escape_helper(base, regex, n)
326
328
  number = ""
327
329
 
@@ -334,20 +336,10 @@ module Apricot
334
336
  number.to_i(base).chr
335
337
  end
336
338
 
337
- def delimiter_helper(c)
338
- case c
339
- when '(' then ')'
340
- when '[' then ']'
341
- when '{' then '}'
342
- when '<' then '>'
343
- else c
344
- end
345
- end
346
-
347
- def parse_regex
339
+ def read_regex
348
340
  line = @line
349
341
  next_char # skip the r
350
- delimiter = delimiter_helper(@char)
342
+ delimiter = opposite_delimiter(@char)
351
343
  next_char # skip delimiter
352
344
  regex = ""
353
345
 
@@ -355,7 +347,7 @@ module Apricot
355
347
  if @char == delimiter
356
348
  next_char # consume delimiter
357
349
  options = regex_options_helper
358
- return AST::RegexLiteral.new(line, regex, options)
350
+ return with_location Regexp.new(regex, options), line
359
351
  elsif @char == "\\" && peek_char == delimiter
360
352
  next_char
361
353
  elsif @char == "\\" && peek_char == "\\"
@@ -383,21 +375,21 @@ module Apricot
383
375
  options
384
376
  end
385
377
 
386
- def parse_quotation(double_quote)
378
+ def read_quotation(double_quote)
387
379
  line = @line
388
380
  next_char # skip the prefix
389
- delimiter = delimiter_helper(@char)
381
+ delimiter = opposite_delimiter(@char)
390
382
  next_char # skip delimiter
391
383
  string = ""
392
384
 
393
385
  while @char
394
386
  if @char == delimiter
395
387
  next_char # consume delimiter
396
- return AST::StringLiteral.new(line, string)
388
+ return with_location string, line
397
389
  end
398
390
 
399
391
  if double_quote
400
- string << parse_string_char
392
+ string << read_string_char
401
393
  elsif @char == "\\" && (peek_char == delimiter || peek_char == "\\")
402
394
  next_char
403
395
  string << consume_char
@@ -409,7 +401,7 @@ module Apricot
409
401
  incomplete_error "Unexpected end of program while parsing quotation"
410
402
  end
411
403
 
412
- def parse_symbol
404
+ def read_symbol
413
405
  line = @line
414
406
  next_char # skip the :
415
407
  symbol = ""
@@ -418,7 +410,7 @@ module Apricot
418
410
  next_char # skip opening "
419
411
  while @char
420
412
  break if @char == '"'
421
- symbol << parse_string_char
413
+ symbol << read_string_char
422
414
  end
423
415
  incomplete_error "Unexpected end of program while parsing symbol" unless @char == '"'
424
416
  next_char # skip closing "
@@ -431,10 +423,10 @@ module Apricot
431
423
  syntax_error "Empty symbol name" if symbol.empty?
432
424
  end
433
425
 
434
- AST::SymbolLiteral.new(line, symbol.to_sym)
426
+ symbol.to_sym
435
427
  end
436
428
 
437
- def parse_number
429
+ def read_number
438
430
  number = ""
439
431
 
440
432
  while @char =~ IDENTIFIER
@@ -444,22 +436,23 @@ module Apricot
444
436
 
445
437
  case number
446
438
  when /^[+-]?\d+$/
447
- AST.new_integer(@line, number.to_i)
439
+ number.to_i
448
440
  when /^([+-]?)(\d+)r([a-zA-Z0-9]+)$/
449
441
  sign, radix, digits = $1, $2.to_i, $3
450
442
  syntax_error "Radix out of range: #{radix}" unless 2 <= radix && radix <= 36
451
443
  syntax_error "Invalid digits for radix in number: #{number}" unless digits.downcase.chars.all? {|d| DIGITS[0..radix-1].include?(d) }
452
- AST.new_integer(@line, (sign + digits).to_i(radix))
444
+ (sign + digits).to_i(radix)
453
445
  when /^[+-]?\d+\.?\d*(?:e[+-]?\d+)?$/
454
- AST::FloatLiteral.new(@line, number.to_f)
446
+ number.to_f
455
447
  when /^([+-]?\d+)\/(\d+)$/
456
- AST::RationalLiteral.new(@line, $1.to_i, $2.to_i)
448
+ Rational($1.to_i, $2.to_i)
457
449
  else
458
450
  syntax_error "Invalid number: #{number}"
459
451
  end
460
452
  end
461
453
 
462
- def parse_identifier
454
+ def read_identifier
455
+ line = @line
463
456
  identifier = ""
464
457
 
465
458
  while @char =~ IDENTIFIER
@@ -467,32 +460,33 @@ module Apricot
467
460
  next_char
468
461
  end
469
462
 
463
+ case identifier
464
+ when 'true' then return true
465
+ when 'false' then return false
466
+ when 'nil' then return nil
467
+ end
468
+
469
+ state = @fn_state.last
470
+
470
471
  # Handle % identifiers in #() syntax
471
- if (state = @fn_state.last) && identifier[0] == '%'
472
- identifier = case identifier[1..-1]
472
+ if state && identifier[0] == '%'
473
+ case identifier[1..-1]
473
474
  when '' # % is equivalent to %1
474
- state.args[0] ||= Apricot.gensym('p1')
475
+ state.args[0] ||= with_location Apricot.gensym('p1'), line
475
476
  when '&'
476
- state.rest ||= Apricot.gensym('rest')
477
+ state.rest ||= with_location Apricot.gensym('rest'), line
477
478
  when /^[1-9]\d*$/
478
479
  n = identifier[1..-1].to_i
479
- state.args[n - 1] ||= Apricot.gensym("p#{n}")
480
+ state.args[n - 1] ||= with_location Apricot.gensym("p#{n}"), line
480
481
  else
481
482
  syntax_error "arg literal must be %, %& or %integer"
482
483
  end
483
484
  else
484
- identifier = identifier.to_sym
485
- end
486
-
487
- case identifier
488
- when :true, :false, :nil
489
- AST::Literal.new(@line, identifier)
490
- else
491
- AST::Identifier.new(@line, identifier)
485
+ with_location Identifier.intern(identifier), line
492
486
  end
493
487
  end
494
488
 
495
- def parse_pipe_identifier
489
+ def read_pipe_identifier
496
490
  line = @line
497
491
  next_char # skip the |
498
492
  identifier = ""
@@ -500,10 +494,10 @@ module Apricot
500
494
  while @char
501
495
  if @char == '|'
502
496
  next_char # consume the |
503
- return AST::Identifier.new(line, identifier.to_sym)
497
+ return with_location Identifier.intern(identifier), line
504
498
  end
505
499
 
506
- identifier << parse_string_char
500
+ identifier << read_string_char
507
501
  end
508
502
 
509
503
  incomplete_error "Unexpected end of program while parsing pipe identifier"
@@ -537,5 +531,38 @@ module Apricot
537
531
  def incomplete_error(message)
538
532
  raise SyntaxError.new(@filename, @line, message, true)
539
533
  end
534
+
535
+ def with_location(obj, line)
536
+ obj.apricot_meta = {line: line}
537
+ obj
538
+ end
539
+
540
+ def cons(head, tail)
541
+ tail.cons(head)
542
+ end
543
+
544
+ def is_unquote?(form)
545
+ form.is_a?(Seq) && form.first == UNQUOTE
546
+ end
547
+
548
+ def is_unquote_splicing?(form)
549
+ form.is_a?(Seq) && form.first == UNQUOTE_SPLICING
550
+ end
551
+
552
+ def hashify(array)
553
+ array.each_slice(2).with_object({}) do |(key, value), hash|
554
+ hash[key] = value
555
+ end
556
+ end
557
+
558
+ def opposite_delimiter(c)
559
+ case c
560
+ when '(' then ')'
561
+ when '[' then ']'
562
+ when '{' then '}'
563
+ when '<' then '>'
564
+ else c
565
+ end
566
+ end
540
567
  end
541
568
  end