apricot 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/.travis.yml +1 -0
- data/Gemfile.lock +229 -11
- data/README.md +46 -29
- data/Rakefile +1 -1
- data/apricot.gemspec +7 -3
- data/benchmarks/factorial.rb +51 -0
- data/benchmarks/interpolate.rb +20 -0
- data/bin/apricot +5 -23
- data/examples/bot.apr +1 -4
- data/examples/cinch-bot.apr +3 -3
- data/examples/sinatra.apr +9 -0
- data/kernel/core.apr +124 -75
- data/kernel/repl.apr +37 -0
- data/lib/apricot.rb +7 -26
- data/lib/apricot/boot.rb +24 -0
- data/lib/apricot/code_loader.rb +108 -0
- data/lib/apricot/compiler.rb +265 -32
- data/lib/apricot/generator.rb +10 -3
- data/lib/apricot/identifier.rb +25 -10
- data/lib/apricot/list.rb +28 -41
- data/lib/apricot/macroexpand.rb +14 -8
- data/lib/apricot/misc.rb +2 -1
- data/lib/apricot/namespace.rb +20 -3
- data/lib/apricot/{parser.rb → reader.rb} +221 -194
- data/lib/apricot/repl.rb +67 -24
- data/lib/apricot/ruby_ext.rb +27 -16
- data/lib/apricot/scopes.rb +159 -0
- data/lib/apricot/seq.rb +43 -1
- data/lib/apricot/special_forms.rb +16 -695
- data/lib/apricot/special_forms/def.rb +32 -0
- data/lib/apricot/special_forms/do.rb +23 -0
- data/lib/apricot/special_forms/dot.rb +112 -0
- data/lib/apricot/special_forms/fn.rb +342 -0
- data/lib/apricot/special_forms/if.rb +31 -0
- data/lib/apricot/special_forms/let.rb +8 -0
- data/lib/apricot/special_forms/loop.rb +10 -0
- data/lib/apricot/special_forms/quote.rb +9 -0
- data/lib/apricot/special_forms/recur.rb +26 -0
- data/lib/apricot/special_forms/try.rb +146 -0
- data/lib/apricot/variables.rb +65 -0
- data/lib/apricot/version.rb +1 -1
- data/spec/compiler_spec.rb +53 -450
- data/spec/fn_spec.rb +206 -0
- data/spec/list_spec.rb +1 -1
- data/spec/reader_spec.rb +349 -0
- data/spec/spec_helper.rb +40 -4
- data/spec/special_forms_spec.rb +203 -0
- metadata +99 -133
- data/lib/apricot/ast.rb +0 -3
- data/lib/apricot/ast/identifier.rb +0 -111
- data/lib/apricot/ast/list.rb +0 -99
- data/lib/apricot/ast/literals.rb +0 -240
- data/lib/apricot/ast/node.rb +0 -45
- data/lib/apricot/ast/scopes.rb +0 -147
- data/lib/apricot/ast/toplevel.rb +0 -66
- data/lib/apricot/ast/variables.rb +0 -64
- data/lib/apricot/printers.rb +0 -12
- data/lib/apricot/stages.rb +0 -60
- data/spec/parser_spec.rb +0 -312
data/lib/apricot/generator.rb
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
module Apricot
|
2
|
-
class Generator < Rubinius::Generator
|
3
|
-
|
4
|
-
|
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
|
data/lib/apricot/identifier.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Apricot
|
2
2
|
class Identifier
|
3
|
-
attr_reader :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
|
-
|
23
|
-
raise '
|
22
|
+
qualifier_id = Identifier.intern($1)
|
23
|
+
raise 'Qualifier in qualified identifier must be a constant' unless qualifier_id.constant?
|
24
24
|
|
25
|
-
@
|
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#{
|
89
|
+
when /\A#{Reader::IDENTIFIER}+\z/
|
75
90
|
@name.to_s
|
76
91
|
else
|
77
92
|
str = @name.to_s.inspect[1..-2]
|
data/lib/apricot/list.rb
CHANGED
@@ -4,59 +4,25 @@ module Apricot
|
|
4
4
|
include Seq
|
5
5
|
|
6
6
|
def self.[](*args)
|
7
|
-
list =
|
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
|
-
|
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
|
-
|
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
|
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
|
data/lib/apricot/macroexpand.rb
CHANGED
@@ -1,30 +1,36 @@
|
|
1
1
|
module Apricot
|
2
2
|
def self.macroexpand(form)
|
3
3
|
ex = macroexpand_1(form)
|
4
|
-
|
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?
|
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.
|
21
|
+
args = form.rest
|
16
22
|
|
17
23
|
# Handle the (.method receiver args*) send expression form
|
18
|
-
if name.length > 1 &&
|
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.
|
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 &&
|
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.
|
36
|
-
potential_macro = callee.
|
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]
|
data/lib/apricot/misc.rb
CHANGED
data/lib/apricot/namespace.rb
CHANGED
@@ -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,
|
46
|
-
|
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
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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 =
|
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
|
-
|
39
|
+
|
40
|
+
# Read the first character
|
41
|
+
next_char
|
25
42
|
end
|
26
43
|
|
27
|
-
def self.
|
28
|
-
File.open(filename) {|f| new(f, filename).
|
44
|
+
def self.read_file(filename)
|
45
|
+
File.open(filename) {|f| new(f, filename).read }
|
29
46
|
end
|
30
47
|
|
31
|
-
def self.
|
32
|
-
new(StringIO.new(source
|
48
|
+
def self.read_string(source, filename = '(none)', line = 1)
|
49
|
+
new(StringIO.new(source), filename, line).read
|
33
50
|
end
|
34
51
|
|
35
|
-
#
|
36
|
-
def
|
37
|
-
|
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
|
-
|
58
|
+
forms << read_form
|
43
59
|
skip_whitespace
|
44
60
|
end
|
45
61
|
|
46
|
-
|
62
|
+
forms
|
47
63
|
end
|
48
64
|
|
49
|
-
|
50
|
-
def parse_one
|
51
|
-
next_char
|
65
|
+
def read_one
|
52
66
|
skip_whitespace
|
53
|
-
|
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
|
-
|
58
|
-
#
|
59
|
-
|
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 <<
|
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
|
-
#
|
79
|
-
|
80
|
-
def parse_form
|
97
|
+
# Read a single Lisp form
|
98
|
+
def read_form
|
81
99
|
case @char
|
82
|
-
when '#' then
|
83
|
-
when "'" then
|
84
|
-
when "`" then
|
85
|
-
when "~" then
|
86
|
-
when '(' then
|
87
|
-
when '[' then
|
88
|
-
when '{' then
|
89
|
-
when '"' then
|
90
|
-
when ':' then
|
91
|
-
when /\d/ then
|
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
|
-
|
112
|
+
read_number
|
95
113
|
else
|
96
|
-
|
114
|
+
read_identifier
|
97
115
|
end
|
98
116
|
else syntax_error "Unexpected character: #{@char}"
|
99
117
|
end
|
100
118
|
end
|
101
119
|
|
102
|
-
def
|
120
|
+
def read_dispatch
|
103
121
|
next_char # skip #
|
104
122
|
case @char
|
105
|
-
when '|' then
|
106
|
-
when '{' then
|
107
|
-
when '(' then
|
108
|
-
when 'r' then
|
109
|
-
when 'q' then
|
110
|
-
when 'Q' then
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
163
|
-
if is_unquote?
|
164
|
-
form
|
165
|
-
elsif is_unquote_splicing?
|
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
|
-
|
169
|
-
AST::List.new(@line, [concat] + syntax_quote_list(form.elements))
|
180
|
+
syntax_quote_list(form, gensyms)
|
170
181
|
end
|
171
|
-
when
|
172
|
-
syntax_quote_coll(:array, form
|
173
|
-
when
|
174
|
-
syntax_quote_coll(:set, form
|
175
|
-
when
|
176
|
-
syntax_quote_coll(:hash, form.
|
177
|
-
when
|
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
|
-
|
181
|
-
|
182
|
-
AST::List.new(@line, [quote, id])
|
192
|
+
gensyms[name] ||= Apricot.gensym(name)
|
193
|
+
List[QUOTE, gensyms[name]]
|
183
194
|
else
|
184
|
-
|
195
|
+
List[QUOTE, form]
|
185
196
|
end
|
186
|
-
when AST::BasicLiteral
|
187
|
-
form
|
188
197
|
else
|
189
|
-
|
198
|
+
form
|
190
199
|
end
|
191
200
|
end
|
192
201
|
|
193
|
-
def syntax_quote_coll(creator_name, elements)
|
194
|
-
|
195
|
-
|
196
|
-
creator
|
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
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
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
|
-
|
215
|
+
List[LIST, syntax_quote(form, gensyms)]
|
211
216
|
end
|
212
217
|
end
|
213
|
-
end
|
214
218
|
|
215
|
-
|
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
|
222
|
-
|
223
|
-
|
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
|
-
|
229
|
+
unquote_type = UNQUOTE_SPLICING
|
234
230
|
end
|
235
231
|
|
236
232
|
skip_whitespace
|
237
233
|
|
238
234
|
unless @char
|
239
|
-
syntax =
|
235
|
+
syntax = (unquote_type == UNQUOTE ? '~' : '~@')
|
240
236
|
incomplete_error "Unexpected end of program after #{syntax}, expected a form"
|
241
237
|
end
|
242
238
|
|
243
|
-
|
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
|
242
|
+
def read_fn
|
243
|
+
line = @line
|
244
|
+
|
249
245
|
@fn_state << FnState.new([], nil)
|
250
|
-
body =
|
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
|
-
|
252
|
+
x || Apricot.gensym("p#{i + 1}")
|
256
253
|
end
|
257
254
|
|
258
|
-
|
259
|
-
AST::ArrayLiteral.new(body.line, args),
|
260
|
-
body])
|
255
|
+
with_location List[FN, args, body], line
|
261
256
|
end
|
262
257
|
|
263
|
-
def
|
258
|
+
def read_list
|
259
|
+
line = @line
|
264
260
|
next_char # skip the (
|
265
|
-
|
261
|
+
with_location read_forms_until(')').to_list, line
|
266
262
|
end
|
267
263
|
|
268
|
-
def
|
264
|
+
def read_array
|
265
|
+
line = @line
|
269
266
|
next_char # skip the [
|
270
|
-
|
267
|
+
with_location read_forms_until(']'), line
|
271
268
|
end
|
272
269
|
|
273
|
-
def
|
270
|
+
def read_hash
|
271
|
+
line = @line
|
274
272
|
next_char # skip the {
|
275
|
-
forms =
|
273
|
+
forms = read_forms_until('}')
|
276
274
|
syntax_error "Odd number of forms in key-value hash" if forms.count.odd?
|
277
|
-
|
275
|
+
with_location hashify(forms), line
|
278
276
|
end
|
279
277
|
|
280
|
-
def
|
278
|
+
def read_set
|
279
|
+
line = @line
|
281
280
|
next_char # skip the {
|
282
|
-
|
281
|
+
with_location read_forms_until('}').to_set, line
|
283
282
|
end
|
284
283
|
|
285
|
-
def
|
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
|
292
|
+
return with_location string, line
|
294
293
|
end
|
295
294
|
|
296
|
-
string <<
|
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
|
304
|
-
char =
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
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
|
-
#
|
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
|
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 =
|
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
|
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
|
378
|
+
def read_quotation(double_quote)
|
387
379
|
line = @line
|
388
380
|
next_char # skip the prefix
|
389
|
-
delimiter =
|
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
|
388
|
+
return with_location string, line
|
397
389
|
end
|
398
390
|
|
399
391
|
if double_quote
|
400
|
-
string <<
|
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
|
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 <<
|
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
|
-
|
426
|
+
symbol.to_sym
|
435
427
|
end
|
436
428
|
|
437
|
-
def
|
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
|
-
|
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
|
-
|
444
|
+
(sign + digits).to_i(radix)
|
453
445
|
when /^[+-]?\d+\.?\d*(?:e[+-]?\d+)?$/
|
454
|
-
|
446
|
+
number.to_f
|
455
447
|
when /^([+-]?\d+)\/(\d+)$/
|
456
|
-
|
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
|
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
|
472
|
-
|
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
|
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
|
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
|
497
|
+
return with_location Identifier.intern(identifier), line
|
504
498
|
end
|
505
499
|
|
506
|
-
identifier <<
|
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
|