introhive_expression_language 0.6.8
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/.github/dependabot.yml +80 -0
- data/.github/pull_request_template.md +17 -0
- data/.github/workflows/ci.yml +31 -0
- data/.gitignore +15 -0
- data/.qlty/qlty.toml +10 -0
- data/.ruby-version +1 -0
- data/CLAUDE.md +169 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +87 -0
- data/README.md +55 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/introhive_expression_language.gemspec +54 -0
- data/lib/introhive_expression_language/iel/evaluation_context.rb +35 -0
- data/lib/introhive_expression_language/iel/evaluation_error.rb +9 -0
- data/lib/introhive_expression_language/iel/evaluator.rb +166 -0
- data/lib/introhive_expression_language/iel/node_util.rb +65 -0
- data/lib/introhive_expression_language/iel/parser.rb +30 -0
- data/lib/introhive_expression_language/iel/sexp_parser.rb +339 -0
- data/lib/introhive_expression_language/iel/std_lib.rb +43 -0
- data/lib/introhive_expression_language/iel/std_lib_assoc.rb +84 -0
- data/lib/introhive_expression_language/iel/std_lib_control.rb +81 -0
- data/lib/introhive_expression_language/iel/std_lib_enum.rb +30 -0
- data/lib/introhive_expression_language/iel/std_lib_existence.rb +19 -0
- data/lib/introhive_expression_language/iel/std_lib_json.rb +38 -0
- data/lib/introhive_expression_language/iel/std_lib_kind.rb +88 -0
- data/lib/introhive_expression_language/iel/std_lib_let.rb +22 -0
- data/lib/introhive_expression_language/iel/std_lib_list.rb +85 -0
- data/lib/introhive_expression_language/iel/std_lib_logic.rb +52 -0
- data/lib/introhive_expression_language/iel/std_lib_math.rb +75 -0
- data/lib/introhive_expression_language/iel/std_lib_number.rb +28 -0
- data/lib/introhive_expression_language/iel/std_lib_regexp.rb +30 -0
- data/lib/introhive_expression_language/iel/std_lib_string.rb +79 -0
- data/lib/introhive_expression_language/iel/symbol_detail.rb +16 -0
- data/lib/introhive_expression_language/iel.rb +2 -0
- data/lib/introhive_expression_language/version.rb +3 -0
- data/lib/introhive_expression_language.rb +9 -0
- metadata +305 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module Platform
|
|
2
|
+
module IEL
|
|
3
|
+
class StdLibAssoc
|
|
4
|
+
class << self
|
|
5
|
+
|
|
6
|
+
def declare(defining_context)
|
|
7
|
+
StdLibList.declare(defining_context)
|
|
8
|
+
declare_assoc(defining_context)
|
|
9
|
+
declare_assoc_get(defining_context)
|
|
10
|
+
# declare_assoc_let(defining_context) # This needs to be reimplemented to ensure immutability of data structures
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def assoc_get(associative_array, keys)
|
|
14
|
+
match = nil
|
|
15
|
+
# Still have to support single keys - Strings and symbols. Convert them to a list so we can go through logic normally
|
|
16
|
+
keys = SexpParser::Node.list([keys]) unless keys.kind == :list
|
|
17
|
+
|
|
18
|
+
keys.value.each do |k|
|
|
19
|
+
match = associative_array.value.find do |kv_node|
|
|
20
|
+
kv_node.kind == :list && kv_node.value.size >= 2 && kv_node.value.first == k
|
|
21
|
+
end
|
|
22
|
+
break if match.nil?
|
|
23
|
+
associative_array = match.value[1]
|
|
24
|
+
end
|
|
25
|
+
if match
|
|
26
|
+
match.value[1]
|
|
27
|
+
else
|
|
28
|
+
SexpParser::Node.nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# TODO this can be reused as a defining_context with tests
|
|
33
|
+
def assoc_contains(associative_array, key)
|
|
34
|
+
match = associative_array.value.find do |kv_node|
|
|
35
|
+
kv_node.kind == :list && kv_node.value.size >= 2 && kv_node.value.first == key
|
|
36
|
+
end
|
|
37
|
+
if match
|
|
38
|
+
SexpParser::Node.boolean(true)
|
|
39
|
+
else
|
|
40
|
+
SexpParser::Node.boolean(false)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def declare_assoc(defining_context)
|
|
47
|
+
Evaluator.eval_expr(%q{
|
|
48
|
+
[def assoc [va_pairs]
|
|
49
|
+
/* Build an assoc from the arguments.
|
|
50
|
+
Allows this expression to produce an assoc:
|
|
51
|
+
[assoc [a 1] [b 2]] */
|
|
52
|
+
[
|
|
53
|
+
[list-prepend va_pairs 'assoc]
|
|
54
|
+
]
|
|
55
|
+
]
|
|
56
|
+
}, defining_context)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def declare_assoc_get(defining_context)
|
|
60
|
+
defining_context['assoc-get'] ||= Evaluator.wrap_native_function do |_calling_ctx, associative_array, key|
|
|
61
|
+
raise EvaluationError.new("associative_array argument to function 'assoc-get' must be of type list but it is #{associative_array.kind}", associative_array) unless associative_array.kind == :list
|
|
62
|
+
assoc_get(associative_array, key)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def declare_assoc_let(defining_context)
|
|
67
|
+
defining_context['assoc-let'] ||= Evaluator.wrap_native_function do |_calling_ctx, associative_array, key, value|
|
|
68
|
+
raise EvaluationError.new("associative_array argument to function 'assoc-let' must be of type list but it is #{associative_array.kind}", associative_array) unless associative_array.kind == :list
|
|
69
|
+
match = associative_array.value.find do |kv_node|
|
|
70
|
+
kv_node.kind == :list && kv_node.value.size >= 2 && kv_node.value.first == key
|
|
71
|
+
end
|
|
72
|
+
if match
|
|
73
|
+
match.value[1] = value
|
|
74
|
+
else
|
|
75
|
+
associative_array.value << SexpParser::Node.list([key, value], key.source, _calling_ctx)
|
|
76
|
+
end
|
|
77
|
+
value
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
module Platform
|
|
2
|
+
module IEL
|
|
3
|
+
class StdLibControl
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
|
|
7
|
+
def declare(defining_context)
|
|
8
|
+
declare_def(defining_context)
|
|
9
|
+
declare_if(defining_context)
|
|
10
|
+
declare_quote(defining_context)
|
|
11
|
+
declare_block(defining_context)
|
|
12
|
+
declare_eval(defining_context)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def declare_def(defining_context)
|
|
18
|
+
defining_context['def'] ||= Evaluator.wrap_native_function do |calling_ctx, name__, arg_list__, body__|
|
|
19
|
+
raise EvaluationError.new("name argument to function 'def' must be of type symbol but it is #{name__.kind}", name__) unless name__.kind == :symbol
|
|
20
|
+
function_name = name__.value
|
|
21
|
+
arg_list = arg_list__.value.map do |n|
|
|
22
|
+
raise EvaluationError.new("function arg list member '#{n.inspect}' must be a symbol", n) unless n.kind == :symbol
|
|
23
|
+
n.value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
raise EvaluationError.new("function body must be a list", body__) if body__.kind != :list
|
|
27
|
+
|
|
28
|
+
calling_ctx.enclosing_context[function_name] = SexpParser::Node.new(
|
|
29
|
+
:native_function,
|
|
30
|
+
body__.source,
|
|
31
|
+
{
|
|
32
|
+
proc: Proc.new do |inner_ctx|
|
|
33
|
+
Evaluator.eval_block(body__, inner_ctx)
|
|
34
|
+
end,
|
|
35
|
+
arg_list: arg_list
|
|
36
|
+
},
|
|
37
|
+
body__.context
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def declare_if(defining_context)
|
|
43
|
+
defining_context['if'] ||= Evaluator.wrap_native_function do |calling_ctx, condition_node, true_body__, false_body__|
|
|
44
|
+
raise EvaluationError.new("Condition argument to function 'if' must be of type boolean but it is #{condition_node.kind}", condition_node) unless condition_node.kind == :boolean
|
|
45
|
+
if condition_node.value
|
|
46
|
+
Evaluator.eval_block(true_body__, calling_ctx)
|
|
47
|
+
else
|
|
48
|
+
Evaluator.eval_block(false_body__, calling_ctx)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def declare_quote(defining_context)
|
|
54
|
+
defining_context['quote'] ||= Evaluator.wrap_native_function do |_calling_ctx, arg__|
|
|
55
|
+
arg__
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def declare_block(defining_context)
|
|
60
|
+
defining_context['block'] ||= Evaluator.wrap_native_function do |_calling_ctx, va_list_of_exprs__|
|
|
61
|
+
raise EvaluationError.new("list_of_exprs argument to function 'block' must be of type list but it is #{va_list_of_exprs__.kind}", va_list_of_exprs__) unless va_list_of_exprs__.kind == :list
|
|
62
|
+
rv = SexpParser::Node.nil(va_list_of_exprs__.source)
|
|
63
|
+
va_list_of_exprs__.value.each do |expr|
|
|
64
|
+
rv = Evaluator.eval_node(expr, _calling_ctx)
|
|
65
|
+
end
|
|
66
|
+
rv
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def declare_eval(defining_context)
|
|
71
|
+
defining_context['eval'] ||= Evaluator.wrap_native_function do |calling_ctx, node__|
|
|
72
|
+
actual_code = calling_ctx[node__.value]
|
|
73
|
+
Evaluator.eval_node(actual_code, calling_ctx)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Platform
|
|
2
|
+
module IEL
|
|
3
|
+
class StdLibEnum
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
|
|
7
|
+
def declare(defining_context)
|
|
8
|
+
declare_map(defining_context)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def declare_map(defining_context)
|
|
14
|
+
defining_context['map'] ||= Evaluator.wrap_native_function do |calling_ctx, list, var_symbol__, code_block__|
|
|
15
|
+
raise EvaluationError.new("list argument to function 'map' must be of type list but it is #{list.kind}", list) unless list.kind == :list
|
|
16
|
+
raise EvaluationError.new("var_symbol argument to function 'map' must be of type symbol but it is #{var_symbol__.kind}", var_symbol__) unless var_symbol__.kind == :symbol
|
|
17
|
+
raise EvaluationError.new("code_block argument to function 'map' must be of type list but it is #{code_block__.kind}", code_block__) unless code_block__.kind == :list
|
|
18
|
+
SexpParser::Node.list(list.value.map do |item|
|
|
19
|
+
instance_context = EvaluationContext.new(calling_ctx)
|
|
20
|
+
instance_context[var_symbol__.value] = item
|
|
21
|
+
Evaluator.eval_block(code_block__, instance_context)
|
|
22
|
+
end, list.source)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Platform
|
|
2
|
+
module IEL
|
|
3
|
+
class StdLibExistence
|
|
4
|
+
class << self
|
|
5
|
+
def declare(defining_context)
|
|
6
|
+
declare_present(defining_context)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def declare_present(defining_context)
|
|
12
|
+
defining_context['present?'] ||= Evaluator.wrap_native_function do |calling_ctx, expr|
|
|
13
|
+
Platform::IEL::SexpParser::Node.boolean(expr.value.present?, expr.source, calling_ctx)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Platform
|
|
2
|
+
module IEL
|
|
3
|
+
class StdLibJson
|
|
4
|
+
class << self
|
|
5
|
+
|
|
6
|
+
def declare(defining_context)
|
|
7
|
+
declare_json_build(defining_context)
|
|
8
|
+
declare_json_load(defining_context)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def declare_json_build(defining_context)
|
|
14
|
+
defining_context['json-build'] ||= Evaluator.wrap_native_function do |_calling_ctx, item|
|
|
15
|
+
struct = NodeUtil.to_native(item)
|
|
16
|
+
SexpParser::Node.string_literal(JSON.dump(struct), item.source, _calling_ctx)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def declare_json_load(defining_context)
|
|
21
|
+
defining_context['json-load'] ||= Evaluator.wrap_native_function do |_calling_ctx, json_string|
|
|
22
|
+
raise EvaluationError.new("json_string argument to function 'json-load' must be of type string_literal but it is #{json_string.kind}", json_string) unless json_string.kind == :string_literal
|
|
23
|
+
begin
|
|
24
|
+
doc = JSON.parse(json_string.value)
|
|
25
|
+
NodeUtil.from_native(doc)
|
|
26
|
+
rescue JSON::ParserError => e
|
|
27
|
+
_calling_ctx.root_context['json-last-error'] = SexpParser::Node.list([
|
|
28
|
+
SexpParser::Node.string_literal(e.message.gsub(/\d{3}: /, ''))
|
|
29
|
+
])
|
|
30
|
+
SexpParser::Node.nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module Platform
|
|
2
|
+
module IEL
|
|
3
|
+
class StdLibKind
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
|
|
7
|
+
def declare(defining_context)
|
|
8
|
+
KIND_TEST_FUNCTIONS.keys.each do |function_name|
|
|
9
|
+
send("declare_#{function_name}", defining_context)
|
|
10
|
+
end
|
|
11
|
+
declare_sym(defining_context)
|
|
12
|
+
declare_eq(defining_context)
|
|
13
|
+
declare_float(defining_context)
|
|
14
|
+
declare_int(defining_context)
|
|
15
|
+
declare_str(defining_context)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
KIND_TEST_FUNCTIONS = {
|
|
21
|
+
'nil?' => :nil,
|
|
22
|
+
'list?' => :list,
|
|
23
|
+
'num?' => :numeric_literal,
|
|
24
|
+
'str?' => :string_literal,
|
|
25
|
+
'func?' => :native_function,
|
|
26
|
+
'nan?' => :nan,
|
|
27
|
+
'sym?' => :symbol
|
|
28
|
+
}.freeze
|
|
29
|
+
KIND_TEST_FUNCTIONS.each do |function_name, node_kind|
|
|
30
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
31
|
+
def declare_#{function_name}(defining_context)
|
|
32
|
+
defining_context['#{function_name}'] = Evaluator.wrap_native_function do |calling_ctx, expr|
|
|
33
|
+
Platform::IEL::SexpParser::Node.boolean(expr.kind == :#{node_kind}, expr.source)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
RUBY
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def declare_sym(defining_context)
|
|
40
|
+
defining_context['sym'] ||= Evaluator.wrap_native_function do |_calling_ctx, value|
|
|
41
|
+
raise EvaluationError.new("value argument to function 'sym' must be of type symbol or string but it is #{value.kind}", value) unless [:symbol, :string_literal].include?(value.kind)
|
|
42
|
+
Platform::IEL::SexpParser::Node.symbol(value.value, value.source)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def declare_eq(defining_context)
|
|
47
|
+
defining_context['='] ||= Evaluator.wrap_native_function do |_calling_ctx, left, right|
|
|
48
|
+
Platform::IEL::SexpParser::Node.boolean(left.value == right.value)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def declare_cast(defining_context, cast_name, allowed_kinds, cast_function)
|
|
53
|
+
defining_context[cast_name] ||= Evaluator.wrap_native_function do |_calling_ctx, operand|
|
|
54
|
+
raise EvaluationError.new("value argument to function '#{cast_name}' must be of type #{allowed_kinds.join(' or ')} but it is #{operand.kind}", operand) unless allowed_kinds.include? operand.kind
|
|
55
|
+
NodeUtil.from_native(operand.value.send(cast_function))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def declare_float(defining_context)
|
|
60
|
+
declare_cast(defining_context, 'float', [:numeric_literal, :string_literal, :nil], :to_f)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def declare_int(defining_context)
|
|
64
|
+
declare_cast(defining_context, 'int', [:numeric_literal, :string_literal, :nil], :to_i)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def declare_str(defining_context)
|
|
68
|
+
declare_cast(defining_context, 'str', [:symbol, :string_literal, :numeric_literal, :nan, :list, :nil, :boolean], :to_s)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# def declare_def_nn(defining_context)
|
|
72
|
+
# Evaluator.eval_expr(%{
|
|
73
|
+
# [def def-nn [function_name] [
|
|
74
|
+
# [def [sym [join function_name "-nn"]]
|
|
75
|
+
# [args function_name]
|
|
76
|
+
# [
|
|
77
|
+
# [function_name
|
|
78
|
+
# ]
|
|
79
|
+
# ]
|
|
80
|
+
# ]]
|
|
81
|
+
# }, defining_context)
|
|
82
|
+
# end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Platform
|
|
2
|
+
module IEL
|
|
3
|
+
class StdLibLet
|
|
4
|
+
class << self
|
|
5
|
+
|
|
6
|
+
def declare(defining_context)
|
|
7
|
+
declare_let(defining_context)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def declare_let(defining_context)
|
|
13
|
+
defining_context['let'] ||= Evaluator.wrap_native_function do |_calling_ctx, symbol__, expr|
|
|
14
|
+
raise EvaluationError.new("symbol argument to function 'let' must be of type symbol but it is #{symbol__.kind}", symbol__) unless symbol__.kind == :symbol
|
|
15
|
+
_calling_ctx.enclosing_context[symbol__.value] = expr
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module Platform
|
|
2
|
+
module IEL
|
|
3
|
+
class StdLibList
|
|
4
|
+
class << self
|
|
5
|
+
|
|
6
|
+
def declare(defining_context)
|
|
7
|
+
declare_list_elem(defining_context)
|
|
8
|
+
declare_list_join(defining_context)
|
|
9
|
+
declare_list_append(defining_context)
|
|
10
|
+
declare_list_prepend(defining_context)
|
|
11
|
+
declare_list_len(defining_context)
|
|
12
|
+
declare_list_any(defining_context)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Return an element from the list at its ordinal position. List indexes are zero based.
|
|
16
|
+
# If a negative index is provided then it specifies items from the end of the list.
|
|
17
|
+
# e.g. 0 is the first item in the list
|
|
18
|
+
# -1 is the last item in the list
|
|
19
|
+
# -2 is the second-to-last item in the list
|
|
20
|
+
def list_elem(list_node, index_node)
|
|
21
|
+
raise EvaluationError.new("list argument to function 'list-elem' must be of type list but it is #{list_node.kind}", list_node) unless list_node.kind == :list
|
|
22
|
+
raise EvaluationError.new("index argument to function 'list-elem' must be of type numeric_literal but it is #{index_node.kind}", index_node) unless index_node.kind == :numeric_literal
|
|
23
|
+
index = index_node.value
|
|
24
|
+
if index >= list_node.value.size
|
|
25
|
+
SexpParser::Node.nil
|
|
26
|
+
elsif index < 0
|
|
27
|
+
index_from_end = list_node.value.size + index
|
|
28
|
+
if index_from_end < 0
|
|
29
|
+
SexpParser::Node.nil
|
|
30
|
+
else
|
|
31
|
+
list_node.value[index_from_end]
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
list_node.value[index]
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def declare_list_elem(defining_context)
|
|
41
|
+
defining_context['list-elem'] ||= Evaluator.wrap_native_function do |_calling_ctx, list, index|
|
|
42
|
+
list_elem(list, index)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def declare_list_join(defining_context)
|
|
47
|
+
defining_context['list-join'] ||= Evaluator.wrap_native_function do |_calling_ctx, va_lists|
|
|
48
|
+
combined_list = []
|
|
49
|
+
va_lists.value.each_with_index do |arg, index|
|
|
50
|
+
raise EvaluationError.new("argument #{index+1} to function 'list-join' must be of type list but it is #{arg.kind}", arg) unless arg.kind == :list
|
|
51
|
+
combined_list.concat(arg.value)
|
|
52
|
+
end
|
|
53
|
+
SexpParser::Node.list(combined_list, va_lists.source)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def declare_list_append(defining_context)
|
|
58
|
+
defining_context['list-append'] ||= Evaluator.wrap_native_function do |_calling_ctx, list, item|
|
|
59
|
+
raise EvaluationError.new("argument list to function 'list-append' must be of type list but it is #{list.kind}", list) unless list.kind == :list
|
|
60
|
+
SexpParser::Node.list(list.value + [item], list.source)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def declare_list_prepend(defining_context)
|
|
65
|
+
defining_context['list-prepend'] ||= Evaluator.wrap_native_function do |_calling_ctx, list, item|
|
|
66
|
+
raise EvaluationError.new("argument list to function 'list-prepend' must be of type list but it is #{list.kind}", list) unless list.kind == :list
|
|
67
|
+
SexpParser::Node.list([item] + list.value, list.source)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
def declare_list_len(defining_context)
|
|
71
|
+
defining_context['list-len'] ||= Evaluator.wrap_native_function do |_calling_ctx, list|
|
|
72
|
+
raise EvaluationError.new("argument list to function 'list-len' must be of type list but it is #{list.kind}", list) unless list.kind == :list
|
|
73
|
+
SexpParser::Node.numeric_literal(list.value.length)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
def declare_list_any(defining_context)
|
|
77
|
+
defining_context['list-any?'] ||= Evaluator.wrap_native_function do |_calling_ctx, list, value|
|
|
78
|
+
raise EvaluationError.new("argument list to function 'list-any?' must be of type list but it is #{list.kind}", list) unless list.kind == :list
|
|
79
|
+
SexpParser::Node.boolean(list.value.include?(value))
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Platform
|
|
2
|
+
module IEL
|
|
3
|
+
class StdLibLogic
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
|
|
7
|
+
def declare(defining_context)
|
|
8
|
+
declare_and(defining_context)
|
|
9
|
+
declare_or(defining_context)
|
|
10
|
+
declare_not(defining_context)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def declare_or(defining_context)
|
|
16
|
+
defining_context['or'] ||= Evaluator.wrap_native_function do |calling_ctx, va_conditions__|
|
|
17
|
+
raise EvaluationError.new("conditions argument to function 'or' must be of type list but it is #{va_conditions__.kind}", va_conditions__) unless va_conditions__.kind == :list
|
|
18
|
+
raise EvaluationError.new("function 'or' requires at least 2 arguments but #{va_conditions__.value.size} are provided", va_conditions__) if va_conditions__.value.size < 2
|
|
19
|
+
|
|
20
|
+
result = va_conditions__.value.each_with_index.any? do |condition, index|
|
|
21
|
+
condition = Evaluator.eval_node(condition, calling_ctx)
|
|
22
|
+
raise EvaluationError.new("Condition argument #{index} to function 'or' must be of type boolean but it is #{condition.kind}", condition) unless condition.kind == :boolean
|
|
23
|
+
condition.value
|
|
24
|
+
end
|
|
25
|
+
Platform::IEL::SexpParser::Node.boolean(result)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def declare_and(defining_context)
|
|
30
|
+
defining_context['and'] ||= Evaluator.wrap_native_function do |calling_ctx, va_conditions__|
|
|
31
|
+
raise EvaluationError.new("conditions argument to function 'and' must be of type list but it is #{va_conditions__.kind}", va_conditions__) unless va_conditions__.kind == :list
|
|
32
|
+
raise EvaluationError.new("function 'and' requires at least 2 arguments but #{va_conditions__.value.size} are provided", va_conditions__) if va_conditions__.value.size < 2
|
|
33
|
+
|
|
34
|
+
result = va_conditions__.value.each_with_index.all? do |condition, index|
|
|
35
|
+
condition = Evaluator.eval_node(condition, calling_ctx)
|
|
36
|
+
raise EvaluationError.new("Condition argument #{index} to function 'and' must be of type boolean but it is #{condition.kind}", condition) unless condition.kind == :boolean
|
|
37
|
+
condition.value
|
|
38
|
+
end
|
|
39
|
+
Platform::IEL::SexpParser::Node.boolean(result)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def declare_not(defining_context)
|
|
44
|
+
defining_context['not'] ||= Evaluator.wrap_native_function do |__calling_ctx, condition_node|
|
|
45
|
+
raise EvaluationError.new("Condition argument to function 'not' must be of type boolean but it is #{condition_node.kind}", condition_node) unless condition_node.kind == :boolean
|
|
46
|
+
Platform::IEL::SexpParser::Node.boolean(!condition_node.value)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module Platform
|
|
2
|
+
module IEL
|
|
3
|
+
class StdLibMath
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
|
|
7
|
+
def declare(defining_context)
|
|
8
|
+
declare_add(defining_context)
|
|
9
|
+
declare_subtract(defining_context)
|
|
10
|
+
declare_multiply(defining_context)
|
|
11
|
+
declare_divide(defining_context)
|
|
12
|
+
declare_greater_than(defining_context)
|
|
13
|
+
declare_less_than(defining_context)
|
|
14
|
+
declare_greater_than_or_equal(defining_context)
|
|
15
|
+
declare_less_than_or_equal(defining_context)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def declare_add(defining_context)
|
|
21
|
+
defining_context['+'] ||= Evaluator.wrap_native_function do |_calling_ctx, operand1, operand2|
|
|
22
|
+
SexpParser::Node.numeric_literal(Evaluator.numeric_value_of(operand1) + Evaluator.numeric_value_of(operand2), operand1.source)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def declare_subtract(defining_context)
|
|
27
|
+
defining_context['-'] ||= Evaluator.wrap_native_function do |_calling_ctx, operand1, operand2|
|
|
28
|
+
SexpParser::Node.numeric_literal(Evaluator.numeric_value_of(operand1) - Evaluator.numeric_value_of(operand2), operand1.source)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def declare_multiply(defining_context)
|
|
33
|
+
defining_context['*'] ||= Evaluator.wrap_native_function do |_calling_ctx, operand1, operand2|
|
|
34
|
+
SexpParser::Node.numeric_literal(Evaluator.numeric_value_of(operand1) * Evaluator.numeric_value_of(operand2), operand1.source)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def declare_divide(defining_context)
|
|
39
|
+
defining_context['/'] ||= Evaluator.wrap_native_function do |_calling_ctx, operand1, operand2|
|
|
40
|
+
divisor = Evaluator.numeric_value_of(operand2)
|
|
41
|
+
if divisor == 0
|
|
42
|
+
SexpParser::Node.nan
|
|
43
|
+
else
|
|
44
|
+
SexpParser::Node.numeric_literal(Evaluator.numeric_value_of(operand1) / divisor, operand1.source)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def declare_greater_than(defining_context)
|
|
50
|
+
defining_context['>'] ||= Evaluator.wrap_native_function do |_calling_ctx, operand1, operand2|
|
|
51
|
+
SexpParser::Node.boolean(Evaluator.numeric_value_of(operand1) > Evaluator.numeric_value_of(operand2))
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def declare_less_than(defining_context)
|
|
56
|
+
defining_context['<'] ||= Evaluator.wrap_native_function do |_calling_ctx, operand1, operand2|
|
|
57
|
+
SexpParser::Node.boolean(Evaluator.numeric_value_of(operand1) < Evaluator.numeric_value_of(operand2))
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def declare_greater_than_or_equal(defining_context)
|
|
62
|
+
defining_context['>='] ||= Evaluator.wrap_native_function do |_calling_ctx, operand1, operand2|
|
|
63
|
+
SexpParser::Node.boolean(Evaluator.numeric_value_of(operand1) >= Evaluator.numeric_value_of(operand2))
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def declare_less_than_or_equal(defining_context)
|
|
68
|
+
defining_context['<='] ||= Evaluator.wrap_native_function do |_calling_ctx, operand1, operand2|
|
|
69
|
+
SexpParser::Node.boolean(Evaluator.numeric_value_of(operand1) <= Evaluator.numeric_value_of(operand2))
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'active_support/number_helper'
|
|
2
|
+
module Platform
|
|
3
|
+
module IEL
|
|
4
|
+
class StdLibNumber
|
|
5
|
+
class << self
|
|
6
|
+
|
|
7
|
+
def declare(defining_context)
|
|
8
|
+
StdLibAssoc.declare(defining_context)
|
|
9
|
+
declare_money_humanize(defining_context)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def declare_money_humanize(defining_context)
|
|
15
|
+
defining_context['number-humanize'] ||= Evaluator.wrap_native_function do |_calling_ctx, number, units|
|
|
16
|
+
raise EvaluationError.new("Argument 'number' of function 'number-humanize' must be a numeric_literal", number) unless number.kind == :numeric_literal
|
|
17
|
+
raise EvaluationError.new("Argument 'units' of function 'number-humanize' must be a list or nil", units) unless units.kind == :list || units.kind == :nil
|
|
18
|
+
|
|
19
|
+
SexpParser::Node.string_literal(ActiveSupport::NumberHelper.number_to_human(number.value, units: Platform::IEL::NodeUtil.to_native(units)))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Platform
|
|
2
|
+
module IEL
|
|
3
|
+
class StdLibRegexp
|
|
4
|
+
class << self
|
|
5
|
+
def declare(defining_context)
|
|
6
|
+
declare_regexp_match?(defining_context)
|
|
7
|
+
declare_regexp_match(defining_context)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def declare_regexp_match?(defining_context)
|
|
13
|
+
defining_context['regexp-match?'] ||= Evaluator.wrap_native_function do |context, regexp, string|
|
|
14
|
+
raise EvaluationError.new("Argument 'regexp' of function 'regexp-match?' must be a string_literal but it is #{regexp.kind}", regexp) unless regexp.kind == :string_literal
|
|
15
|
+
raise EvaluationError.new("Argument 'string' of function 'regexp-match?' must be a string_literal but it is #{string.kind}", string) unless string.kind == :string_literal
|
|
16
|
+
SexpParser::Node.boolean(Regexp.new(regexp.value).match?(string.value), regexp.source, context)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def declare_regexp_match(defining_context)
|
|
21
|
+
defining_context['regexp-match'] ||= Evaluator.wrap_native_function do |context, regexp, string|
|
|
22
|
+
raise EvaluationError.new("Argument 'regexp' of function 'regexp-match' must be a string_literal but it is #{regexp.kind}", regexp) unless regexp.kind == :string_literal
|
|
23
|
+
raise EvaluationError.new("Argument 'string' of function 'regexp-match' must be a string_literal but it is #{string.kind}", string) unless string.kind == :string_literal
|
|
24
|
+
Platform::IEL::NodeUtil.from_native(Regexp.new(regexp.value).match(string.value).to_a)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|