Module: Fusion::Interpreter::Builtins
Constant Summary collapse
- NUMBER_PAIR =
--- math (reached as
@math.round,@math.divide,@math.pi, …) --- ["[_ ? @Number, _ ? @Number]"].freeze
- NUMBER_ARRAY =
['_ ? (xs => {"c": xs, "f": @Number} | @all)'].freeze
- NUMBER =
["_ ? @Number"].freeze
- INTEGER_PAIR =
["[_ ? @Integer, _ ? @Integer]"].freeze
- COMPARE_PAIR =
["[_ ? @Number, _ ? @Number]", "[_ ? @String, _ ? @String]"].freeze
Instance Method Summary collapse
- #install(table, interp) ⇒ Object
-
#join(v) ⇒ Object
[items, separator]: join an array of strings into one string. -
#keys(v) ⇒ Object
--- object key enumeration (Tier 0: patterns can't enumerate unknown keys) ---.
- #math_abs(v) ⇒ Object
- #math_ceil(v) ⇒ Object
- #math_cos(v) ⇒ Object
-
#math_divide(v) ⇒ Object
dividealways yields a float. - #math_exp(v) ⇒ Object
- #math_floor(v) ⇒ Object
-
#math_log(v) ⇒ Object
Natural logarithm; the domain is positive numbers (
Math.lograises on a negative and gives-Infinityat 0). -
#math_pow(v) ⇒ Object
base ** exponent: integer when the base and a non-negative integer exponent are integers, a float otherwise. -
#math_rand(v) ⇒ Object
null→ a float in[0.0, 1.0); a positive integern→ an integer in[0, n). -
#math_round(v) ⇒ Object
round/floor/ceilreturn an integer; a non-finite input is a math_error (their Ruby forms raiseFloatDomainErroron it). -
#math_sign(v) ⇒ Object
-1 / 0 / 1 (via
</>, so NaN → 0, never Ruby'snil). - #math_sin(v) ⇒ Object
-
#math_sqrt(v) ⇒ Object
Square root; the domain is non-negative numbers (a negative would be complex).
- #op_and(v) ⇒ Object
-
#op_compare(v) ⇒ Object
Order two numbers or two strings: -1, 0, or 1 (no deep equality).
-
#op_equal(v) ⇒ Object
Deep, exact equality across the whole array: true iff every element equals the first (so 0 and 1 elements are vacuously equal).
-
#op_invert(v) ⇒ Object
The unary reciprocal 1/x, always a float; 0 is a math_error.
- #op_modulo(v) ⇒ Object
- #op_negate(v) ⇒ Object
- #op_not(v) ⇒ Object
- #op_or(v) ⇒ Object
- #op_product(v) ⇒ Object
-
#op_quotient(v) ⇒ Object
Integer division and its remainder — integers only (
@dividehandles the float case). -
#op_sum(v) ⇒ Object
--- OP: the sugar-target operators (reached as
@OP.sum,@OP.and, …) ---. - #parse_number(v) ⇒ Object
-
#size(v) ⇒ Object
--- strings and structure bridges ---.
-
#split(v) ⇒ Object
[string, separator]: the inverse of@join. - #to_object(v) ⇒ Object
- #to_string(v) ⇒ Object
- #values(v) ⇒ Object
Instance Method Details
#install(table, interp) ⇒ Object
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/fusion/interpreter/builtins.rb', line 10 def install(table, interp) @interp = interp define = ->(name, fn) { table[name] = NativeFunc.new(name, fn) } # Irreducible primitives kept as built-ins. The sugar-target operators # (arithmetic/comparison/boolean) live in `@OP` below; their friendly # derived forms (`@lt`, `@gt`, `@truthy`, …) are stdlib files that build on # `@OP.*`, so they follow a per-directory `@OP` override. Numeric functions # (`round`, `divide`, `sin`, …) and constants (`pi`, `e`) live in `@math`. define.call("size", method(:size)) define.call("join", method(:join)) define.call("split", method(:split)) define.call("toString", method(:to_string)) define.call("parseNumber", method(:parse_number)) define.call("keys", method(:keys)) define.call("values", method(:values)) define.call("toObject", method(:to_object)) # type predicates: return false on any non-matching value, never an error define.call("Integer", method(:integer?)) define.call("Float", method(:float?)) define.call("Number", method(:numeric?)) define.call("String", method(:string?)) define.call("Boolean", method(:boolean?)) define.call("Array", method(:array?)) define.call("Object", method(:object?)) define.call("Null", method(:null?)) define.call("Function", method(:function?)) define.call("NonFinite", method(:non_finite?)) # `OP` bundles the sugar-target operators as a shadowable builtin object, # reached as `@OP.sum`, `@OP.and`, … A directory can swap the operators by # placing an `OP.fsn` sibling that overrides members (reaching the # originals with `@@`); infix sugar and the derived stdlib helpers resolve # `@OP` per directory, so they follow the override. table["OP"] = { "sum" => NativeFunc.new("OP.sum", method(:op_sum)), "product" => NativeFunc.new("OP.product", method(:op_product)), "negate" => NativeFunc.new("OP.negate", method(:op_negate)), "invert" => NativeFunc.new("OP.invert", method(:op_invert)), "quotient" => NativeFunc.new("OP.quotient", method(:op_quotient)), "modulo" => NativeFunc.new("OP.modulo", method(:op_modulo)), "equal" => NativeFunc.new("OP.equal", method(:op_equal)), "compare" => NativeFunc.new("OP.compare", method(:op_compare)), "and" => NativeFunc.new("OP.and", method(:op_and)), "or" => NativeFunc.new("OP.or", method(:op_or)), "not" => NativeFunc.new("OP.not", method(:op_not)), } # `math` bundles numeric functions and constants, reached as `@math.round`, # `@math.pi`, … Like `@OP`, it is a shadowable builtin object. `pi`/`e` are # plain values; the rest are one-argument functions. table["math"] = { "round" => NativeFunc.new("math.round", method(:math_round)), "floor" => NativeFunc.new("math.floor", method(:math_floor)), "ceil" => NativeFunc.new("math.ceil", method(:math_ceil)), "divide" => NativeFunc.new("math.divide", method(:math_divide)), "sign" => NativeFunc.new("math.sign", method(:math_sign)), "abs" => NativeFunc.new("math.abs", method(:math_abs)), "rand" => NativeFunc.new("math.rand", method(:math_rand)), "sin" => NativeFunc.new("math.sin", method(:math_sin)), "cos" => NativeFunc.new("math.cos", method(:math_cos)), "exp" => NativeFunc.new("math.exp", method(:math_exp)), "log" => NativeFunc.new("math.log", method(:math_log)), "pow" => NativeFunc.new("math.pow", method(:math_pow)), "sqrt" => NativeFunc.new("math.sqrt", method(:math_sqrt)), "pi" => Math::PI, "e" => Math::E, } end |
#join(v) ⇒ Object
[items, separator]: join an array of strings into one string. @concat
is the stdlib pair-case built on this.
323 324 325 326 327 328 329 330 331 332 333 334 |
# File 'lib/fusion/interpreter/builtins.rb', line 323 def join(v) return v if v.is_a?(ErrorVal) expected = ['[_ ? (xs => {"c": xs, "f": @String} | @all), _ ? @String]'] return argument_error("join", v, expected) unless pair?(v) array, separator = v unless array.is_a?(Array) && separator.is_a?(String) && array.all? { |item| item.is_a?(String) } return argument_error("join", v, expected) end array.join(separator) end |
#keys(v) ⇒ Object
--- object key enumeration (Tier 0: patterns can't enumerate unknown keys) ---
377 378 379 380 381 382 |
# File 'lib/fusion/interpreter/builtins.rb', line 377 def keys(v) return v if v.is_a?(ErrorVal) return argument_error("keys", v, ["_ ? @Object"]) unless v.is_a?(Hash) v.keys end |
#math_abs(v) ⇒ Object
134 135 136 137 138 139 |
# File 'lib/fusion/interpreter/builtins.rb', line 134 def math_abs(v) return v if v.is_a?(ErrorVal) return argument_error("math.abs", v, NUMBER) unless numeric?(v) v.abs end |
#math_ceil(v) ⇒ Object
105 106 107 108 109 110 111 |
# File 'lib/fusion/interpreter/builtins.rb', line 105 def math_ceil(v) return v if v.is_a?(ErrorVal) return argument_error("math.ceil", v, NUMBER) unless numeric?(v) return error("math_error", "math.ceil", v, "not a finite number") if non_finite?(v) v.ceil end |
#math_cos(v) ⇒ Object
158 159 160 161 162 163 |
# File 'lib/fusion/interpreter/builtins.rb', line 158 def math_cos(v) return v if v.is_a?(ErrorVal) return argument_error("math.cos", v, NUMBER) unless numeric?(v) Math.cos(v) end |
#math_divide(v) ⇒ Object
divide always yields a float. Integer division is @OP.quotient, the
remainder @OP.modulo.
115 116 117 118 119 120 121 |
# File 'lib/fusion/interpreter/builtins.rb', line 115 def math_divide(v) return v if v.is_a?(ErrorVal) return argument_error("math.divide", v, NUMBER_PAIR) unless pair?(v) && numeric?(v[0]) && numeric?(v[1]) return error("math_error", "math.divide", v, "division by zero") if v[1] == 0 v[0].to_f / v[1] end |
#math_exp(v) ⇒ Object
165 166 167 168 169 170 |
# File 'lib/fusion/interpreter/builtins.rb', line 165 def math_exp(v) return v if v.is_a?(ErrorVal) return argument_error("math.exp", v, NUMBER) unless numeric?(v) Math.exp(v) end |
#math_floor(v) ⇒ Object
97 98 99 100 101 102 103 |
# File 'lib/fusion/interpreter/builtins.rb', line 97 def math_floor(v) return v if v.is_a?(ErrorVal) return argument_error("math.floor", v, NUMBER) unless numeric?(v) return error("math_error", "math.floor", v, "not a finite number") if non_finite?(v) v.floor end |
#math_log(v) ⇒ Object
Natural logarithm; the domain is positive numbers (Math.log raises on a
negative and gives -Infinity at 0).
184 185 186 187 188 189 190 |
# File 'lib/fusion/interpreter/builtins.rb', line 184 def math_log(v) return v if v.is_a?(ErrorVal) return argument_error("math.log", v, NUMBER) unless numeric?(v) return error("math_error", "math.log", v, "log of a non-positive number") if v <= 0 Math.log(v) end |
#math_pow(v) ⇒ Object
base ** exponent: integer when the base and a non-negative integer
exponent are integers, a float otherwise. A negative base with a fractional
exponent (a complex result) is a math_error.
195 196 197 198 199 200 201 202 203 204 |
# File 'lib/fusion/interpreter/builtins.rb', line 195 def math_pow(v) return v if v.is_a?(ErrorVal) return argument_error("math.pow", v, NUMBER_PAIR) unless pair?(v) && numeric?(v[0]) && numeric?(v[1]) a, b = v result = a.is_a?(Integer) && b.is_a?(Integer) && !b.negative? ? a**b : a.to_f**b return error("math_error", "math.pow", v, "not in domain (complex result)") if result.is_a?(Complex) result end |
#math_rand(v) ⇒ Object
null → a float in [0.0, 1.0); a positive integer n → an integer in
[0, n).
143 144 145 146 147 148 149 |
# File 'lib/fusion/interpreter/builtins.rb', line 143 def math_rand(v) return v if v.is_a?(ErrorVal) return rand if v == NULL return rand(v) if integer?(v) && v.positive? argument_error("math.rand", v, ["_ ? @Null", '_ ? (n ? @Integer => [0, n] | @OP.compare | (-1 => true))']) end |
#math_round(v) ⇒ Object
round/floor/ceil return an integer; a non-finite input is a math_error
(their Ruby forms raise FloatDomainError on it).
89 90 91 92 93 94 95 |
# File 'lib/fusion/interpreter/builtins.rb', line 89 def math_round(v) return v if v.is_a?(ErrorVal) return argument_error("math.round", v, NUMBER) unless numeric?(v) return error("math_error", "math.round", v, "not a finite number") if non_finite?(v) v.round end |
#math_sign(v) ⇒ Object
-1 / 0 / 1 (via </>, so NaN → 0, never Ruby's nil).
124 125 126 127 128 129 130 131 132 |
# File 'lib/fusion/interpreter/builtins.rb', line 124 def math_sign(v) return v if v.is_a?(ErrorVal) return argument_error("math.sign", v, NUMBER) unless numeric?(v) if v < 0 then -1 elsif v > 0 then 1 else 0 end end |
#math_sin(v) ⇒ Object
151 152 153 154 155 156 |
# File 'lib/fusion/interpreter/builtins.rb', line 151 def math_sin(v) return v if v.is_a?(ErrorVal) return argument_error("math.sin", v, NUMBER) unless numeric?(v) Math.sin(v) end |
#math_sqrt(v) ⇒ Object
Square root; the domain is non-negative numbers (a negative would be complex).
174 175 176 177 178 179 180 |
# File 'lib/fusion/interpreter/builtins.rb', line 174 def math_sqrt(v) return v if v.is_a?(ErrorVal) return argument_error("math.sqrt", v, NUMBER) unless numeric?(v) return error("math_error", "math.sqrt", v, "square root of a negative number") if v < 0 Math.sqrt(v) end |
#op_and(v) ⇒ Object
292 293 294 295 296 297 |
# File 'lib/fusion/interpreter/builtins.rb', line 292 def op_and(v) return v if v.is_a?(ErrorVal) return argument_error("OP.and", v, ["_ ? @Array"]) unless v.is_a?(Array) v.all? { |x| @interp.truthy?(x) } end |
#op_compare(v) ⇒ Object
Order two numbers or two strings: -1, 0, or 1 (no deep equality). Built on
< rather than <=> so a NaN operand yields 0 (unordered), never Ruby's
nil — NaN is a reachable value (Infinity - Infinity).
277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/fusion/interpreter/builtins.rb', line 277 def op_compare(v) return v if v.is_a?(ErrorVal) return argument_error("OP.compare", v, COMPARE_PAIR) unless pair?(v) a, b = v unless (numeric?(a) && numeric?(b)) || (a.is_a?(String) && b.is_a?(String)) return argument_error("OP.compare", v, COMPARE_PAIR) end if a < b then -1 elsif b < a then 1 else 0 end end |
#op_equal(v) ⇒ Object
Deep, exact equality across the whole array: true iff every element equals the first (so 0 and 1 elements are vacuously equal). Any types.
265 266 267 268 269 270 |
# File 'lib/fusion/interpreter/builtins.rb', line 265 def op_equal(v) return v if v.is_a?(ErrorVal) return argument_error("OP.equal", v, ["_ ? @Array"]) unless v.is_a?(Array) v.all? { |x| @interp.deep_equal?(v[0], x) } end |
#op_invert(v) ⇒ Object
The unary reciprocal 1/x, always a float; 0 is a math_error.
235 236 237 238 239 240 241 |
# File 'lib/fusion/interpreter/builtins.rb', line 235 def op_invert(v) return v if v.is_a?(ErrorVal) return argument_error("OP.invert", v, ["_ ? @Number"]) unless numeric?(v) return error("math_error", "OP.invert", v, "division by zero") if v == 0 1.0 / v end |
#op_modulo(v) ⇒ Object
255 256 257 258 259 260 261 |
# File 'lib/fusion/interpreter/builtins.rb', line 255 def op_modulo(v) return v if v.is_a?(ErrorVal) return argument_error("OP.modulo", v, INTEGER_PAIR) unless pair?(v) && integer?(v[0]) && integer?(v[1]) return error("math_error", "OP.modulo", v, "modulo by zero") if v[1] == 0 v[0] % v[1] end |
#op_negate(v) ⇒ Object
227 228 229 230 231 232 |
# File 'lib/fusion/interpreter/builtins.rb', line 227 def op_negate(v) return v if v.is_a?(ErrorVal) return argument_error("OP.negate", v, ["_ ? @Number"]) unless numeric?(v) -v end |
#op_not(v) ⇒ Object
306 307 308 309 310 |
# File 'lib/fusion/interpreter/builtins.rb', line 306 def op_not(v) return v if v.is_a?(ErrorVal) !@interp.truthy?(v) end |
#op_or(v) ⇒ Object
299 300 301 302 303 304 |
# File 'lib/fusion/interpreter/builtins.rb', line 299 def op_or(v) return v if v.is_a?(ErrorVal) return argument_error("OP.or", v, ["_ ? @Array"]) unless v.is_a?(Array) v.any? { |x| @interp.truthy?(x) } end |
#op_product(v) ⇒ Object
220 221 222 223 224 225 |
# File 'lib/fusion/interpreter/builtins.rb', line 220 def op_product(v) return v if v.is_a?(ErrorVal) return argument_error("OP.product", v, NUMBER_ARRAY) unless v.is_a?(Array) && v.all? { |x| numeric?(x) } v.reduce(1, :*) end |
#op_quotient(v) ⇒ Object
Integer division and its remainder — integers only (@divide handles the
float case). Ruby's / and % agree in sign, so q*b + r == a holds.
247 248 249 250 251 252 253 |
# File 'lib/fusion/interpreter/builtins.rb', line 247 def op_quotient(v) return v if v.is_a?(ErrorVal) return argument_error("OP.quotient", v, INTEGER_PAIR) unless pair?(v) && integer?(v[0]) && integer?(v[1]) return error("math_error", "OP.quotient", v, "division by zero") if v[1] == 0 v[0] / v[1] end |
#op_sum(v) ⇒ Object
--- OP: the sugar-target operators (reached as @OP.sum, @OP.and, …) ---
The arithmetic, boolean, and equality members take an array of ANY length;
the unary ones take a single value; compare returns -1 / 0 / 1. The
friendly derived forms (@lt, @gt, @truthy, …) are stdlib files built on
these.
213 214 215 216 217 218 |
# File 'lib/fusion/interpreter/builtins.rb', line 213 def op_sum(v) return v if v.is_a?(ErrorVal) return argument_error("OP.sum", v, NUMBER_ARRAY) unless v.is_a?(Array) && v.all? { |x| numeric?(x) } v.sum(0) end |
#parse_number(v) ⇒ Object
364 365 366 367 368 369 370 371 372 373 |
# File 'lib/fusion/interpreter/builtins.rb', line 364 def parse_number(v) return v if v.is_a?(ErrorVal) return argument_error("parseNumber", v, ["_ ? @String"]) unless v.is_a?(String) case v when /\A-?\d+\z/ then v.to_i when /\A-?\d+(\.\d+)?([eE][+-]?\d+)?\z/ then v.to_f else error("conversion_error", "parseNumber", v, "not a numeric string") end end |
#size(v) ⇒ Object
--- strings and structure bridges ---
314 315 316 317 318 319 |
# File 'lib/fusion/interpreter/builtins.rb', line 314 def size(v) return v if v.is_a?(ErrorVal) return argument_error("size", v, ["_ ? @String", "_ ? @Array", "_ ? @Object"]) unless v.is_a?(String) || v.is_a?(Array) || v.is_a?(Hash) v.length end |
#split(v) ⇒ Object
[string, separator]: the inverse of @join. Splits on the LITERAL
separator (Ruby's " " whitespace special-case does not apply) and keeps
empty fields; an empty separator splits into characters. @chars is the
stdlib single-string case built on this.
340 341 342 343 344 345 346 347 348 349 |
# File 'lib/fusion/interpreter/builtins.rb', line 340 def split(v) return v if v.is_a?(ErrorVal) expected = ["[_ ? @String, _ ? @String]"] return argument_error("split", v, expected) unless pair?(v) && v[0].is_a?(String) && v[1].is_a?(String) string, separator = v return string.chars if separator.empty? string.split(Regexp.new(Regexp.escape(separator)), -1) end |
#to_object(v) ⇒ Object
391 392 393 394 395 396 397 398 399 400 |
# File 'lib/fusion/interpreter/builtins.rb', line 391 def to_object(v) return v if v.is_a?(ErrorVal) # Each entry must be a [string, _] pair. expected = ['_ ? (xs => {"c": xs, "f": ([_ ? @String, _] => true)} | @all)'] unless v.is_a?(Array) && v.all? { |entry| pair?(entry) && entry[0].is_a?(String) } return argument_error("toObject", v, expected) end v.to_h end |
#to_string(v) ⇒ Object
351 352 353 354 355 356 357 358 359 360 361 362 |
# File 'lib/fusion/interpreter/builtins.rb', line 351 def to_string(v) return v if v.is_a?(ErrorVal) case v when String then v when Integer, Float then v.to_s when true then "true" when false then "false" when NULL then "null" else error("conversion_error", "toString", v, "cannot stringify this value type") end end |
#values(v) ⇒ Object
384 385 386 387 388 389 |
# File 'lib/fusion/interpreter/builtins.rb', line 384 def values(v) return v if v.is_a?(ErrorVal) return argument_error("values", v, ["_ ? @Object"]) unless v.is_a?(Hash) v.values end |