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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +24 -0
  3. data/.simplecov +11 -0
  4. data/CHANGELOG.md +42 -0
  5. data/README.md +11 -9
  6. data/Rakefile +8 -0
  7. data/docs/lang/design.md +289 -51
  8. data/docs/lang/implementation.md +279 -0
  9. data/docs/lang/roadmap.md +20 -36
  10. data/docs/user/explanation.md +5 -10
  11. data/docs/user/how-to-guides.md +145 -15
  12. data/docs/user/reference.md +365 -140
  13. data/docs/user/tutorial.md +22 -19
  14. data/examples/double.fsn +4 -1
  15. data/examples/factorial.fsn +6 -3
  16. data/examples/fizzbuzz.fsn +1 -4
  17. data/examples/gcd.fsn +9 -0
  18. data/examples/json_test.fsn +4 -0
  19. data/examples/matrix/OP.fsn +2 -0
  20. data/examples/matrix/average.fsn +2 -0
  21. data/examples/matrix/solve.fsn +2 -0
  22. data/examples/palindrome.fsn +1 -1
  23. data/exe/fusion +12 -12
  24. data/lib/fusion/ast.rb +76 -27
  25. data/lib/fusion/atom.rb +1 -1
  26. data/lib/fusion/cli/decoder.rb +13 -8
  27. data/lib/fusion/cli/encoder.rb +2 -2
  28. data/lib/fusion/cli/options.rb +134 -61
  29. data/lib/fusion/cli/parser.rb +4 -4
  30. data/lib/fusion/cli/repl.rb +32 -27
  31. data/lib/fusion/cli/serializer.rb +11 -11
  32. data/lib/fusion/cli.rb +120 -49
  33. data/lib/fusion/interpreter/builtins.rb +298 -160
  34. data/lib/fusion/interpreter/env.rb +42 -12
  35. data/lib/fusion/interpreter/error_val.rb +42 -20
  36. data/lib/fusion/interpreter/thunk.rb +53 -0
  37. data/lib/fusion/interpreter.rb +263 -98
  38. data/lib/fusion/lexer.rb +125 -37
  39. data/lib/fusion/parser.rb +245 -70
  40. data/lib/fusion/version.rb +3 -1
  41. data/lib/fusion.rb +0 -1
  42. data/stdlib/all.fsn +13 -0
  43. data/stdlib/any.fsn +12 -0
  44. data/stdlib/chars.fsn +5 -0
  45. data/stdlib/compact.fsn +5 -0
  46. data/stdlib/concat.fsn +6 -0
  47. data/stdlib/entries.fsn +6 -0
  48. data/stdlib/falsey.fsn +6 -0
  49. data/stdlib/filter.fsn +12 -0
  50. data/stdlib/flatten.fsn +7 -0
  51. data/stdlib/map.fsn +7 -4
  52. data/stdlib/matrix/Matrix.fsn +8 -0
  53. data/stdlib/matrix/OP.fsn +18 -0
  54. data/stdlib/matrix/add.fsn +7 -0
  55. data/stdlib/matrix/column.fsn +6 -0
  56. data/stdlib/matrix/determinant.fsn +16 -0
  57. data/stdlib/matrix/dimensions.fsn +5 -0
  58. data/stdlib/matrix/identity.fsn +6 -0
  59. data/stdlib/matrix/invert.fsn +26 -0
  60. data/stdlib/matrix/minor.fsn +15 -0
  61. data/stdlib/matrix/multiply.fsn +10 -0
  62. data/stdlib/matrix/negate.fsn +5 -0
  63. data/stdlib/matrix/product.fsn +11 -0
  64. data/stdlib/matrix/rotate.fsn +10 -0
  65. data/stdlib/matrix/row.fsn +6 -0
  66. data/stdlib/matrix/scale.fsn +6 -0
  67. data/stdlib/matrix/subtract.fsn +7 -0
  68. data/stdlib/matrix/sum.fsn +14 -0
  69. data/stdlib/matrix/transpose.fsn +5 -0
  70. data/stdlib/range.fsn +2 -2
  71. data/stdlib/reduce.fsn +8 -0
  72. data/stdlib/safe.fsn +7 -0
  73. data/stdlib/sanitize.fsn +2 -3
  74. data/stdlib/toObject.fsn +7 -0
  75. data/stdlib/truthy.fsn +7 -0
  76. data/stdlib/vector/Vector.fsn +7 -0
  77. data/stdlib/vector/add.fsn +6 -0
  78. data/stdlib/vector/cross.fsn +6 -0
  79. data/stdlib/vector/dot.fsn +6 -0
  80. data/stdlib/vector/norm.fsn +6 -0
  81. data/stdlib/vector/scale.fsn +5 -0
  82. data/stdlib/vector/subtract.fsn +6 -0
  83. data/stdlib/zip.fsn +6 -0
  84. metadata +50 -4
  85. data/lib/fusion/interpreter/file_thunk.rb +0 -39
  86. data/stdlib/mapValues.fsn +0 -5
  87. 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
- "|" => :pipe, "?" => :question, "." => :dot,
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) || (c == "-" && digit?(peek(1)))
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
- c = peek
81
- if c == " " || c == "\t" || c == "\n" || c == "\r"
137
+ case peek
138
+ when " ", "\t", "\n", "\r"
82
139
  @i += 1
83
- elsif c == "#" && at_line_start?
84
- # A line is a comment iff its first non-whitespace char is "#".
85
- # This also covers shebang lines (#!) for free.
86
- @i += 1 until peek.nil? || peek == "\n"
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 && (@src[j] == " " || @src[j] == "\t")
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
- if c == '"'
161
+ case c
162
+ when '"'
105
163
  @i += 1
106
164
  return Token.new(type: :string, value: buf, pos: start)
107
- elsif c == "\\"
165
+ when "\\"
108
166
  @i += 1
109
167
  e = peek
110
168
  buf << case e
111
- when '"' then '"'
112
- when "\\" then "\\"
113
- when "/" then "/"
114
- when "n" then "\n"
115
- when "t" then "\t"
116
- when "r" then "\r"
117
- when "b" then "\b"
118
- when "f" then "\f"
119
- when "u"
120
- hex = @src[@i + 1, 4]
121
- @i += 4
122
- code_point = hex.to_i(16)
123
- # Reject surrogates and out-of-range code points up front:
124
- # pack("U") would otherwise build an invalid-encoding string
125
- # whose malformed-UTF-8 error surfaces far downstream.
126
- if code_point.between?(0xD800, 0xDFFF) || code_point > 0x10FFFF
127
- raise ParseError, "Invalid unicode escape \\u#{hex}"
128
- end
129
- [code_point].pack("U")
130
- else
131
- raise ParseError, "Bad escape \\#{e}"
132
- end
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
- elsif c == "\n" || c == "\r"
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 (@src[j] == "e" || @src[j] == "E")
212
+ if ["e", "E"].include?(@src[j])
155
213
  is_float = true
156
214
  j += 1
157
- j += 1 if (@src[j] == "+" || @src[j] == "-")
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_]/)