fusion-lang 0.0.1.alpha2 → 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 +4 -4
- data/.mutant.yml +24 -0
- data/.simplecov +11 -0
- data/CHANGELOG.md +42 -0
- data/README.md +11 -9
- data/Rakefile +8 -0
- data/docs/lang/design.md +289 -51
- data/docs/lang/implementation.md +279 -0
- data/docs/lang/roadmap.md +20 -36
- data/docs/user/explanation.md +5 -10
- data/docs/user/how-to-guides.md +145 -15
- data/docs/user/reference.md +365 -140
- data/docs/user/tutorial.md +22 -19
- data/examples/double.fsn +4 -1
- data/examples/factorial.fsn +6 -3
- data/examples/fizzbuzz.fsn +1 -4
- data/examples/gcd.fsn +9 -0
- data/examples/json_test.fsn +4 -0
- data/examples/matrix/OP.fsn +2 -0
- data/examples/matrix/average.fsn +2 -0
- data/examples/matrix/solve.fsn +2 -0
- data/examples/palindrome.fsn +1 -1
- data/exe/fusion +12 -12
- data/lib/fusion/ast.rb +76 -27
- data/lib/fusion/atom.rb +1 -1
- data/lib/fusion/cli/decoder.rb +13 -8
- data/lib/fusion/cli/encoder.rb +2 -2
- data/lib/fusion/cli/options.rb +134 -61
- data/lib/fusion/cli/parser.rb +4 -4
- data/lib/fusion/cli/repl.rb +32 -27
- data/lib/fusion/cli/serializer.rb +11 -11
- data/lib/fusion/cli.rb +120 -49
- data/lib/fusion/interpreter/builtins.rb +298 -160
- data/lib/fusion/interpreter/env.rb +42 -12
- data/lib/fusion/interpreter/error_val.rb +42 -20
- data/lib/fusion/interpreter/thunk.rb +53 -0
- data/lib/fusion/interpreter.rb +263 -98
- data/lib/fusion/lexer.rb +125 -37
- data/lib/fusion/parser.rb +245 -70
- data/lib/fusion/version.rb +3 -1
- data/lib/fusion.rb +0 -1
- data/stdlib/all.fsn +13 -0
- data/stdlib/any.fsn +12 -0
- data/stdlib/chars.fsn +5 -0
- data/stdlib/compact.fsn +5 -0
- data/stdlib/concat.fsn +6 -0
- data/stdlib/entries.fsn +6 -0
- data/stdlib/falsey.fsn +6 -0
- data/stdlib/filter.fsn +12 -0
- data/stdlib/flatten.fsn +7 -0
- data/stdlib/map.fsn +7 -4
- data/stdlib/matrix/Matrix.fsn +8 -0
- data/stdlib/matrix/OP.fsn +18 -0
- data/stdlib/matrix/add.fsn +7 -0
- data/stdlib/matrix/column.fsn +6 -0
- data/stdlib/matrix/determinant.fsn +16 -0
- data/stdlib/matrix/dimensions.fsn +5 -0
- data/stdlib/matrix/identity.fsn +6 -0
- data/stdlib/matrix/invert.fsn +26 -0
- data/stdlib/matrix/minor.fsn +15 -0
- data/stdlib/matrix/multiply.fsn +10 -0
- data/stdlib/matrix/negate.fsn +5 -0
- data/stdlib/matrix/product.fsn +11 -0
- data/stdlib/matrix/rotate.fsn +10 -0
- data/stdlib/matrix/row.fsn +6 -0
- data/stdlib/matrix/scale.fsn +6 -0
- data/stdlib/matrix/subtract.fsn +7 -0
- data/stdlib/matrix/sum.fsn +14 -0
- data/stdlib/matrix/transpose.fsn +5 -0
- data/stdlib/range.fsn +2 -2
- data/stdlib/reduce.fsn +8 -0
- data/stdlib/safe.fsn +7 -0
- data/stdlib/sanitize.fsn +2 -3
- data/stdlib/toObject.fsn +7 -0
- data/stdlib/truthy.fsn +7 -0
- data/stdlib/vector/Vector.fsn +7 -0
- data/stdlib/vector/add.fsn +6 -0
- data/stdlib/vector/cross.fsn +6 -0
- data/stdlib/vector/dot.fsn +6 -0
- data/stdlib/vector/norm.fsn +6 -0
- data/stdlib/vector/scale.fsn +5 -0
- data/stdlib/vector/subtract.fsn +6 -0
- data/stdlib/zip.fsn +6 -0
- metadata +50 -4
- data/lib/fusion/interpreter/file_thunk.rb +0 -39
- data/stdlib/mapValues.fsn +0 -5
- data/stdlib/math/square.fsn +0 -4
data/lib/fusion/lexer.rb
CHANGED
|
@@ -15,9 +15,12 @@ module Fusion
|
|
|
15
15
|
"[" => :lbracket, "]" => :rbracket,
|
|
16
16
|
"{" => :lbrace, "}" => :rbrace,
|
|
17
17
|
"," => :comma, ":" => :colon,
|
|
18
|
-
"
|
|
18
|
+
"?" => :question, "." => :dot,
|
|
19
19
|
"@" => :at, "/" => :slash,
|
|
20
20
|
"=" => :equals,
|
|
21
|
+
"+" => :plus, "-" => :minus, "*" => :star,
|
|
22
|
+
"%" => :percent, "~" => :tilde,
|
|
23
|
+
"<" => :lt, ">" => :gt,
|
|
21
24
|
}.freeze
|
|
22
25
|
|
|
23
26
|
def initialize(src)
|
|
@@ -31,6 +34,11 @@ module Fusion
|
|
|
31
34
|
loop do
|
|
32
35
|
t = next_token
|
|
33
36
|
out << t
|
|
37
|
+
# A file-reference path is one tight token, lexed only right after `@`/`@@`.
|
|
38
|
+
if [:at, :atat].include?(t.type)
|
|
39
|
+
p = try_lex_path
|
|
40
|
+
out << p unless p.nil?
|
|
41
|
+
end
|
|
34
42
|
break if t.type == :eof
|
|
35
43
|
end
|
|
36
44
|
out
|
|
@@ -55,6 +63,54 @@ module Fusion
|
|
|
55
63
|
@i += 3
|
|
56
64
|
return Token.new(type: :spread, value: "...", pos: start)
|
|
57
65
|
end
|
|
66
|
+
if c == "@" && peek(1) == "@"
|
|
67
|
+
@i += 2
|
|
68
|
+
return Token.new(type: :atat, value: "@@", pos: start)
|
|
69
|
+
end
|
|
70
|
+
# Two-character operators, matched before their single-char prefixes.
|
|
71
|
+
if c == "=" && peek(1) == "="
|
|
72
|
+
@i += 2
|
|
73
|
+
return Token.new(type: :eqeq, value: "==", pos: start)
|
|
74
|
+
end
|
|
75
|
+
if c == "/" && peek(1) == "/"
|
|
76
|
+
@i += 2
|
|
77
|
+
return Token.new(type: :slashslash, value: "//", pos: start)
|
|
78
|
+
end
|
|
79
|
+
if c == "?" && peek(1) == "?"
|
|
80
|
+
@i += 2
|
|
81
|
+
return Token.new(type: :qq, value: "??", pos: start)
|
|
82
|
+
end
|
|
83
|
+
if c == "<" && peek(1) == "="
|
|
84
|
+
@i += 2
|
|
85
|
+
return Token.new(type: :lte, value: "<=", pos: start)
|
|
86
|
+
end
|
|
87
|
+
if c == ">" && peek(1) == "="
|
|
88
|
+
@i += 2
|
|
89
|
+
return Token.new(type: :gte, value: ">=", pos: start)
|
|
90
|
+
end
|
|
91
|
+
if c == "&" && peek(1) == "&"
|
|
92
|
+
@i += 2
|
|
93
|
+
return Token.new(type: :andand, value: "&&", pos: start)
|
|
94
|
+
end
|
|
95
|
+
if c == "|"
|
|
96
|
+
case peek(1)
|
|
97
|
+
when "|"
|
|
98
|
+
@i += 2
|
|
99
|
+
return Token.new(type: :oror, value: "||", pos: start)
|
|
100
|
+
when ":"
|
|
101
|
+
@i += 2
|
|
102
|
+
return Token.new(type: :pipemap, value: "|:", pos: start)
|
|
103
|
+
when "?"
|
|
104
|
+
@i += 2
|
|
105
|
+
return Token.new(type: :pipefilter, value: "|?", pos: start)
|
|
106
|
+
when "+"
|
|
107
|
+
@i += 2
|
|
108
|
+
return Token.new(type: :pipereduce, value: "|+", pos: start)
|
|
109
|
+
else
|
|
110
|
+
@i += 1
|
|
111
|
+
return Token.new(type: :pipe, value: "|", pos: start)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
58
114
|
if c == "!"
|
|
59
115
|
@i += 1
|
|
60
116
|
return Token.new(type: :bang, value: "!", pos: start)
|
|
@@ -62,12 +118,13 @@ module Fusion
|
|
|
62
118
|
if c == '"'
|
|
63
119
|
return lex_string(start)
|
|
64
120
|
end
|
|
65
|
-
if digit?(c)
|
|
121
|
+
if digit?(c)
|
|
66
122
|
return lex_number(start)
|
|
67
123
|
end
|
|
68
124
|
if ident_start?(c)
|
|
69
125
|
return lex_word(start)
|
|
70
126
|
end
|
|
127
|
+
|
|
71
128
|
if (type = PUNCT[c])
|
|
72
129
|
@i += 1
|
|
73
130
|
return Token.new(type: type, value: c, pos: start)
|
|
@@ -77,13 +134,13 @@ module Fusion
|
|
|
77
134
|
|
|
78
135
|
def skip_trivia
|
|
79
136
|
loop do
|
|
80
|
-
|
|
81
|
-
|
|
137
|
+
case peek
|
|
138
|
+
when " ", "\t", "\n", "\r"
|
|
82
139
|
@i += 1
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
@i += 1 until
|
|
140
|
+
when "#"
|
|
141
|
+
break if !at_line_start?
|
|
142
|
+
|
|
143
|
+
@i += 1 until [nil, "\n"].include?(peek)
|
|
87
144
|
else
|
|
88
145
|
break
|
|
89
146
|
end
|
|
@@ -93,7 +150,7 @@ module Fusion
|
|
|
93
150
|
# True when only whitespace precedes @i on the current physical line.
|
|
94
151
|
def at_line_start?
|
|
95
152
|
j = @i - 1
|
|
96
|
-
j -= 1 while j >= 0 &&
|
|
153
|
+
j -= 1 while j >= 0 && [" ", "\t"].include?(@src[j])
|
|
97
154
|
j < 0 || @src[j] == "\n" || @src[j] == "\r"
|
|
98
155
|
end
|
|
99
156
|
|
|
@@ -101,37 +158,39 @@ module Fusion
|
|
|
101
158
|
@i += 1 # opening quote
|
|
102
159
|
buf = +""
|
|
103
160
|
while (c = peek)
|
|
104
|
-
|
|
161
|
+
case c
|
|
162
|
+
when '"'
|
|
105
163
|
@i += 1
|
|
106
164
|
return Token.new(type: :string, value: buf, pos: start)
|
|
107
|
-
|
|
165
|
+
when "\\"
|
|
108
166
|
@i += 1
|
|
109
167
|
e = peek
|
|
110
168
|
buf << case e
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
169
|
+
when '"' then '"'
|
|
170
|
+
when "\\" then "\\"
|
|
171
|
+
when "/" then "/"
|
|
172
|
+
when "n" then "\n"
|
|
173
|
+
when "t" then "\t"
|
|
174
|
+
when "r" then "\r"
|
|
175
|
+
when "b" then "\b"
|
|
176
|
+
when "f" then "\f"
|
|
177
|
+
when "u"
|
|
178
|
+
hex = @src[@i + 1, 4]
|
|
179
|
+
@i += 4
|
|
180
|
+
code_point = hex.to_i(16)
|
|
181
|
+
# Reject surrogates and out-of-range code points up front:
|
|
182
|
+
# pack("U") would otherwise build an invalid-encoding string
|
|
183
|
+
# whose malformed-UTF-8 error surfaces far downstream.
|
|
184
|
+
if code_point.between?(0xD800, 0xDFFF) || code_point > 0x10FFFF
|
|
185
|
+
raise ParseError, "Invalid unicode escape \\u#{hex}"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
[code_point].pack("U")
|
|
189
|
+
else
|
|
190
|
+
raise ParseError, "Bad escape \\#{e}"
|
|
191
|
+
end
|
|
133
192
|
@i += 1
|
|
134
|
-
|
|
193
|
+
when "\n", "\r"
|
|
135
194
|
raise ParseError, "Raw newline in string starting at #{start}; use \\n"
|
|
136
195
|
else
|
|
137
196
|
buf << c
|
|
@@ -143,7 +202,6 @@ module Fusion
|
|
|
143
202
|
|
|
144
203
|
def lex_number(start)
|
|
145
204
|
j = @i
|
|
146
|
-
j += 1 if @src[j] == "-"
|
|
147
205
|
j += 1 while j < @n && digit?(@src[j])
|
|
148
206
|
is_float = false
|
|
149
207
|
if @src[j] == "." && digit?(@src[j + 1])
|
|
@@ -151,10 +209,10 @@ module Fusion
|
|
|
151
209
|
j += 1
|
|
152
210
|
j += 1 while j < @n && digit?(@src[j])
|
|
153
211
|
end
|
|
154
|
-
if
|
|
212
|
+
if ["e", "E"].include?(@src[j])
|
|
155
213
|
is_float = true
|
|
156
214
|
j += 1
|
|
157
|
-
j += 1 if
|
|
215
|
+
j += 1 if ["+", "-"].include?(@src[j])
|
|
158
216
|
j += 1 while j < @n && digit?(@src[j])
|
|
159
217
|
end
|
|
160
218
|
text = @src[@i...j]
|
|
@@ -176,6 +234,36 @@ module Fusion
|
|
|
176
234
|
end
|
|
177
235
|
end
|
|
178
236
|
|
|
237
|
+
# Path lexing — see `tokens`. Only called immediately after `@`/`@@`, with no
|
|
238
|
+
# whitespace skipped, so the path must be tight. Returns nil (position restored)
|
|
239
|
+
# when nothing path-like follows, i.e. a bare `@`/`@@`.
|
|
240
|
+
def try_lex_path
|
|
241
|
+
start = @i
|
|
242
|
+
buf = +""
|
|
243
|
+
while peek == "." && peek(1) == "." && peek(2) == "/"
|
|
244
|
+
buf << "../"
|
|
245
|
+
@i += 3
|
|
246
|
+
end
|
|
247
|
+
unless ident_start?(peek)
|
|
248
|
+
@i = start
|
|
249
|
+
return nil
|
|
250
|
+
end
|
|
251
|
+
buf << lex_ident_text
|
|
252
|
+
while peek == "/" && ident_start?(peek(1))
|
|
253
|
+
@i += 1
|
|
254
|
+
buf << "/" << lex_ident_text
|
|
255
|
+
end
|
|
256
|
+
Token.new(type: :path, value: buf, pos: start)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def lex_ident_text
|
|
260
|
+
j = @i
|
|
261
|
+
j += 1 while j < @n && ident_part?(@src[j])
|
|
262
|
+
text = @src[@i...j]
|
|
263
|
+
@i = j
|
|
264
|
+
text
|
|
265
|
+
end
|
|
266
|
+
|
|
179
267
|
def digit?(c) = c && c >= "0" && c <= "9"
|
|
180
268
|
def ident_start?(c) = c && (c =~ /[A-Za-z_]/)
|
|
181
269
|
def ident_part?(c) = c && (c =~ /[A-Za-z0-9_]/)
|