predicator 1.2.1 → 2.0.0
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/CHANGELOG.md +42 -0
- data/{LICENSE.txt → LICENSE} +1 -1
- data/README.md +322 -46
- data/lib/predicator/ast.rb +74 -187
- data/lib/predicator/boundary.rb +237 -0
- data/lib/predicator/civil.rb +112 -0
- data/lib/predicator/duration_text.rb +92 -0
- data/lib/predicator/emitter/chains.rb +62 -0
- data/lib/predicator/emitter/statements.rb +86 -0
- data/lib/predicator/emitter.rb +187 -0
- data/lib/predicator/errors.rb +140 -0
- data/lib/predicator/evaluator/access.rb +108 -0
- data/lib/predicator/evaluator/arithmetic.rb +132 -0
- data/lib/predicator/evaluator/calls.rb +129 -0
- data/lib/predicator/evaluator/casts.rb +158 -0
- data/lib/predicator/evaluator/chronology.rb +88 -0
- data/lib/predicator/evaluator/compare.rb +158 -0
- data/lib/predicator/evaluator/json_functions.rb +167 -0
- data/lib/predicator/evaluator/json_scan.rb +303 -0
- data/lib/predicator/evaluator/library.rb +127 -0
- data/lib/predicator/evaluator/math_functions.rb +72 -0
- data/lib/predicator/evaluator/order.rb +120 -0
- data/lib/predicator/evaluator/rich_types.rb +146 -0
- data/lib/predicator/evaluator/statements.rb +151 -0
- data/lib/predicator/evaluator.rb +391 -117
- data/lib/predicator/float_text.rb +94 -0
- data/lib/predicator/iso_text.rb +99 -0
- data/lib/predicator/lexer/literals.rb +115 -0
- data/lib/predicator/lexer/numbers.rb +91 -0
- data/lib/predicator/lexer.rb +194 -0
- data/lib/predicator/parser/durations.rb +108 -0
- data/lib/predicator/parser/operators.rb +86 -0
- data/lib/predicator/parser/postfix.rb +57 -0
- data/lib/predicator/parser/primaries.rb +122 -0
- data/lib/predicator/parser/statements.rb +114 -0
- data/lib/predicator/parser/token_text.rb +52 -0
- data/lib/predicator/parser.rb +217 -438
- data/lib/predicator/result.rb +46 -0
- data/lib/predicator/tagged/reader.rb +139 -0
- data/lib/predicator/tagged/writer.rb +106 -0
- data/lib/predicator/tagged.rb +134 -0
- data/lib/predicator/values.rb +147 -0
- data/lib/predicator/version.rb +4 -1
- data/lib/predicator.rb +384 -14
- metadata +53 -147
- data/.gitignore +0 -11
- data/.rubocop.yml +0 -46
- data/.travis.yml +0 -5
- data/CODE_OF_CONDUCT.md +0 -49
- data/Gemfile +0 -4
- data/HISTORY.md +0 -51
- data/Rakefile +0 -24
- data/lib/predicator/context.rb +0 -18
- data/lib/predicator/lexer.rex +0 -69
- data/lib/predicator/lexer.rex.rb +0 -187
- data/lib/predicator/parser.y +0 -120
- data/lib/predicator/visitors/dot.rb +0 -113
- data/lib/predicator/visitors/each.rb +0 -16
- data/lib/predicator/visitors/instructions.rb +0 -183
- data/lib/predicator/visitors/string.rb +0 -76
- data/lib/predicator/visitors/visitor.rb +0 -100
- data/lib/predicator/visitors.rb +0 -5
- data/predicator.gemspec +0 -34
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Predicator
|
|
4
|
+
module Parser
|
|
5
|
+
# The operator levels of the expression grammar, from `OR` down to the
|
|
6
|
+
# prefix operators, read into the parse that includes this module.
|
|
7
|
+
module Operators
|
|
8
|
+
# Each level's operator tokens and the operator each one is.
|
|
9
|
+
OR = { or_op: :or, or_or: :or }.freeze
|
|
10
|
+
AND = { and_op: :and, and_and: :and }.freeze
|
|
11
|
+
ADDITIVE = { plus: :add, minus: :subtract }.freeze
|
|
12
|
+
MULTIPLICATIVE = { multiply: :multiply, divide: :divide, modulo: :modulo }.freeze
|
|
13
|
+
NEGATIONS = %i[not_op bang].freeze
|
|
14
|
+
UNARY = { minus: :minus, bang: :bang }.freeze
|
|
15
|
+
|
|
16
|
+
# The comparison and membership tokens: the node each builds and its
|
|
17
|
+
# operator.
|
|
18
|
+
RELATIONS = {
|
|
19
|
+
gt: [Ast::Comparison, :gt], lt: [Ast::Comparison, :lt], gte: [Ast::Comparison, :gte],
|
|
20
|
+
lte: [Ast::Comparison, :lte], equal_equal: [Ast::Comparison, :equal_equal], ne: [Ast::Comparison, :ne],
|
|
21
|
+
strict_equal: [Ast::Comparison, :strict_eq], strict_ne: [Ast::Comparison, :strict_ne],
|
|
22
|
+
in_op: [Ast::Membership, :in], contains_op: [Ast::Membership, :contains]
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
# A bare `=` where an expression goes on.
|
|
26
|
+
EQUALS = "'=' is not an equality operator - use '==' for equality. " \
|
|
27
|
+
"Assignment is only valid at the start of a statement."
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def logical_or = chain(OR, :logical_and) { |_, left, right, at| Ast::LogicalOr.new(left:, right:, **at) }
|
|
32
|
+
|
|
33
|
+
def logical_and = chain(AND, :logical_not) { |_, left, right, at| Ast::LogicalAnd.new(left:, right:, **at) }
|
|
34
|
+
|
|
35
|
+
def addition = chain(ADDITIVE, :multiplication) { |*parts| arithmetic(*parts) }
|
|
36
|
+
|
|
37
|
+
def multiplication = chain(MULTIPLICATIVE, :unary) { |*parts| arithmetic(*parts) }
|
|
38
|
+
|
|
39
|
+
def arithmetic(operator, left, right, at) = Ast::Arithmetic.new(operator:, left:, right:, **at)
|
|
40
|
+
|
|
41
|
+
# A left-associative level: operands read by the method named, joined
|
|
42
|
+
# by the operators given, each join built by the block.
|
|
43
|
+
def chain(operators, operand)
|
|
44
|
+
left = send(operand)
|
|
45
|
+
while (operator = operators[peek.type])
|
|
46
|
+
token = advance
|
|
47
|
+
right = send(operand)
|
|
48
|
+
left = yield(operator, left, right, infix(token, left, right))
|
|
49
|
+
end
|
|
50
|
+
left
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The higher of the bang's two places: here it negates a whole
|
|
54
|
+
# comparison, below the comparison it negates an operand.
|
|
55
|
+
def logical_not
|
|
56
|
+
return comparison unless NEGATIONS.include?(peek.type)
|
|
57
|
+
|
|
58
|
+
token = advance
|
|
59
|
+
operand = descend { logical_not }
|
|
60
|
+
Ast::LogicalNot.new(operand:, **prefixed(token, operand))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def comparison
|
|
64
|
+
left = addition
|
|
65
|
+
refuse("assignment_in_expression", EQUALS, peek) if at?(:eq)
|
|
66
|
+
kind, operator = RELATIONS[peek.type]
|
|
67
|
+
return left unless kind
|
|
68
|
+
|
|
69
|
+
token = advance
|
|
70
|
+
right = addition
|
|
71
|
+
kind.new(operator:, left:, right:, **infix(token, left, right))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# A minus never folds into the number after it: `-5` is a negation.
|
|
75
|
+
def unary
|
|
76
|
+
operator = UNARY[peek.type]
|
|
77
|
+
return postfix unless operator
|
|
78
|
+
|
|
79
|
+
token = advance
|
|
80
|
+
operand = descend { unary }
|
|
81
|
+
Ast::Unary.new(operator:, operand:, **prefixed(token, operand))
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
private_constant :Operators
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Predicator
|
|
4
|
+
module Parser
|
|
5
|
+
# The postfix steps after a primary, read in a loop into the parse that
|
|
6
|
+
# includes this module: an index, a property and a cast. Each step's
|
|
7
|
+
# node is positioned at what it accesses - the key, the property name,
|
|
8
|
+
# the type name - and spans from its object's start.
|
|
9
|
+
module Postfix
|
|
10
|
+
# Each step's opening token and the method that reads the rest.
|
|
11
|
+
STEPS = { lbracket: :index, dot: :property, double_colon: :cast }.freeze
|
|
12
|
+
|
|
13
|
+
# The tokens that may name a property: an identifier, and the five
|
|
14
|
+
# relative-date words, which the lexer reserves.
|
|
15
|
+
PROPERTY_NAMES = %i[identifier last_op next_op ago_op from_op now_op].freeze
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def postfix
|
|
20
|
+
node = primary
|
|
21
|
+
while (step = STEPS[peek.type])
|
|
22
|
+
advance
|
|
23
|
+
node = send(step, node)
|
|
24
|
+
end
|
|
25
|
+
node
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def index(object)
|
|
29
|
+
key_token = peek
|
|
30
|
+
key = expression
|
|
31
|
+
close = expect(:rbracket, "expected_close_bracket", "Expected ']' but found")
|
|
32
|
+
Ast::BracketAccess.new(object:, key:, position: start_of(key_token), span: reaching(object, close))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def property(object)
|
|
36
|
+
name = peek
|
|
37
|
+
unless PROPERTY_NAMES.include?(name.type)
|
|
38
|
+
refuse("expected_property_name", "Expected property name after '.' but found #{describe(name)}", name)
|
|
39
|
+
end
|
|
40
|
+
advance
|
|
41
|
+
Ast::PropertyAccess.new(object:, property: name.value, position: start_of(name), span: reaching(object, name))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The type name is an identifier matched against the seven names, so
|
|
45
|
+
# each stays usable as a variable, a property and an object key.
|
|
46
|
+
def cast(expression)
|
|
47
|
+
name = expect(:identifier, "expected_type_name", "Expected a type name after '::' but found")
|
|
48
|
+
unless CAST_TYPE_NAMES.include?(name.value)
|
|
49
|
+
refuse("unknown_cast_type",
|
|
50
|
+
"Unknown cast type '#{name.value}' - expected one of: #{CAST_TYPE_NAMES.join(", ")}", name)
|
|
51
|
+
end
|
|
52
|
+
Ast::Cast.new(expression:, type_name: name.value, position: start_of(name), span: reaching(expression, name))
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
private_constant :Postfix
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Predicator
|
|
4
|
+
module Parser
|
|
5
|
+
# The primaries: literals, identifiers, calls, parentheses, lists,
|
|
6
|
+
# objects and relative dates written `next` or `last`, read into the
|
|
7
|
+
# parse that includes this module. Each reader takes the token that
|
|
8
|
+
# started it, already consumed.
|
|
9
|
+
module Primaries
|
|
10
|
+
# The reader for each token a primary may start with; any other token
|
|
11
|
+
# is refused.
|
|
12
|
+
READERS = {
|
|
13
|
+
integer: :number, fractional_number: :fraction, float: :literal, boolean: :literal,
|
|
14
|
+
undefined: :literal, null: :literal, date: :literal, datetime: :literal, string: :string,
|
|
15
|
+
identifier: :identifier, function_name: :call, qualified_function_name: :call,
|
|
16
|
+
lparen: :parenthesized, lbracket: :list, lbrace: :object, next_op: :relative_date,
|
|
17
|
+
last_op: :relative_date, if_kw: :statement_keyword, else_kw: :statement_keyword,
|
|
18
|
+
while_kw: :statement_keyword
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
# What a primary may be, in the refusal of anything else.
|
|
22
|
+
EXPECTED = "Expected number, string, boolean, date, datetime, identifier, function call, list, object, " \
|
|
23
|
+
"or '(' but found"
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def primary
|
|
28
|
+
reader = READERS.fetch(peek.type, :not_a_primary)
|
|
29
|
+
send(reader, advance)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def not_a_primary(token) = refuse("expected_primary", "#{EXPECTED} #{describe(token)}", token)
|
|
33
|
+
|
|
34
|
+
def statement_keyword(token)
|
|
35
|
+
refuse("statement_keyword", "'#{token.value}' is a statement keyword, not an expression - control flow " \
|
|
36
|
+
"is only valid in a program (Predicator.parse_program/2).", token)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def literal(token) = Ast::Literal.new(value: token.value, **leaf(token))
|
|
40
|
+
|
|
41
|
+
# A whole number, or the first component of a duration.
|
|
42
|
+
def number(token) = duration_from(token.value, start_of(token)) || literal(token)
|
|
43
|
+
|
|
44
|
+
# A number with a point that a unit follows, which the lexer writes
|
|
45
|
+
# only before a unit; without one, no primary starts with it.
|
|
46
|
+
def fraction(token) = duration_from(token.value, start_of(token)) || not_a_primary(token)
|
|
47
|
+
|
|
48
|
+
def string(token) = Ast::StringLiteral.new(value: token.value, quote: token.quote, **leaf(token))
|
|
49
|
+
|
|
50
|
+
def identifier(token) = Ast::Identifier.new(name: token.value, **leaf(token))
|
|
51
|
+
|
|
52
|
+
# A parenthesis keeps its expression's node and position and widens
|
|
53
|
+
# its span to the parentheses.
|
|
54
|
+
def parenthesized(open)
|
|
55
|
+
inner = expression
|
|
56
|
+
close = expect(:rparen, "expected_close_paren", "Expected ')' but found")
|
|
57
|
+
inner.with(span: between(start_of(open), end_of(close)))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# A call's name stays whole: the lexer writes a dotted name as one
|
|
61
|
+
# token only where a parenthesis follows it.
|
|
62
|
+
def call(name)
|
|
63
|
+
expect(:lparen, "expected_primary", EXPECTED)
|
|
64
|
+
arguments = items(:rparen) { expression }
|
|
65
|
+
close = expect(:rparen, "expected_close_paren", "Expected ')' but found")
|
|
66
|
+
Ast::FunctionCall.new(name: name.value, arguments:, **delimited(name, close))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def list(open)
|
|
70
|
+
elements = items(:rbracket) { expression }
|
|
71
|
+
close = expect(:rbracket, "expected_close_bracket", "Expected ']' but found")
|
|
72
|
+
Ast::ListLiteral.new(elements:, **delimited(open, close))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def object(open)
|
|
76
|
+
entries = items(:rbrace) { entry }
|
|
77
|
+
close = expect(:rbrace, "expected_close_brace", "Expected '}' but found")
|
|
78
|
+
Ast::ObjectLiteral.new(entries:, **delimited(open, close))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Comma-separated items up to the closing token, which is left for the
|
|
82
|
+
# caller; none when it comes first.
|
|
83
|
+
def items(closer)
|
|
84
|
+
return [] if at?(closer)
|
|
85
|
+
|
|
86
|
+
found = [yield]
|
|
87
|
+
while at?(:comma)
|
|
88
|
+
advance
|
|
89
|
+
found << yield
|
|
90
|
+
end
|
|
91
|
+
found
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# A key, bare or quoted, a colon and a value.
|
|
95
|
+
def entry
|
|
96
|
+
token = peek
|
|
97
|
+
style = token.type == :identifier ? :identifier : token.quote
|
|
98
|
+
unless %i[identifier string].include?(token.type)
|
|
99
|
+
refuse("expected_object_key", "Expected identifier or string for object key but found #{describe(token)}",
|
|
100
|
+
token)
|
|
101
|
+
end
|
|
102
|
+
advance
|
|
103
|
+
key = Ast::ObjectKey.new(name: token.value, style:, **leaf(token))
|
|
104
|
+
expect(:colon, "expected_object_colon", "Expected ':' after object key but found")
|
|
105
|
+
[key, expression]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# `next` or `last` and a bare duration; anything else after the word
|
|
109
|
+
# is refused at the token it started at.
|
|
110
|
+
def relative_date(keyword)
|
|
111
|
+
direction = keyword.type == :next_op ? :next : :last
|
|
112
|
+
start = peek
|
|
113
|
+
duration = descend { primary }
|
|
114
|
+
unless duration.is_a?(Ast::Duration)
|
|
115
|
+
refuse("expected_duration", "Expected duration after '#{direction}' but found #{describe(start)}", start)
|
|
116
|
+
end
|
|
117
|
+
Ast::RelativeDate.new(duration:, direction:, **prefixed(keyword, duration))
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
private_constant :Primaries
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Predicator
|
|
4
|
+
module Parser
|
|
5
|
+
# The statement grammar over the expression grammar, read into the parse
|
|
6
|
+
# that includes this module: sequences, assignments, `if` with its
|
|
7
|
+
# optional `else` and `else if`, `while`, and blocks.
|
|
8
|
+
module Statements
|
|
9
|
+
# The statements a keyword starts.
|
|
10
|
+
KEYWORDS = { if_kw: :if_statement, while_kw: :while_statement, else_kw: :stray_else }.freeze
|
|
11
|
+
|
|
12
|
+
# What an assignment may write: an identifier and any run of property
|
|
13
|
+
# and bracket accesses.
|
|
14
|
+
ACCESSES = [Ast::PropertyAccess, Ast::BracketAccess].freeze
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
# One statement or more up to the terminator, which is left for the
|
|
19
|
+
# caller. A separator is followed by another statement unless it is
|
|
20
|
+
# the one the terminator may follow; without one, another follows
|
|
21
|
+
# only a statement that ended in a closing brace.
|
|
22
|
+
def statement_sequence(terminator)
|
|
23
|
+
statements = [statement]
|
|
24
|
+
statements << statement while continues?(statements.last, terminator)
|
|
25
|
+
statements
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def continues?(last, terminator)
|
|
29
|
+
return !at?(terminator) && (last.is_a?(Ast::If) || last.is_a?(Ast::While)) unless at?(:semicolon)
|
|
30
|
+
|
|
31
|
+
advance
|
|
32
|
+
!at?(terminator)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def statement
|
|
36
|
+
reader = KEYWORDS[peek.type]
|
|
37
|
+
reader ? send(reader, advance) : assignment_or_expression
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def stray_else(token)
|
|
41
|
+
refuse("unexpected_else", "Unexpected 'else' - an 'else' block must follow an 'if' block.", token)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# What is left over after a finished statement.
|
|
45
|
+
def after_statement(token)
|
|
46
|
+
stray_else(token) if token.type == :else_kw
|
|
47
|
+
refuse("trailing_token", "Unexpected token #{describe(token)} after statement", token)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# An assignment when an addition-level expression is followed by `=`,
|
|
51
|
+
# and an expression read again from the start otherwise: a probe that
|
|
52
|
+
# was refused, or no `=` after it, is discarded.
|
|
53
|
+
def assignment_or_expression
|
|
54
|
+
start = @index
|
|
55
|
+
target = catch(REFUSED) { descend { addition } }
|
|
56
|
+
if target.is_a?(Lexer::Refusal) || !at?(:eq)
|
|
57
|
+
@index = start
|
|
58
|
+
return expression
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
assignment(target, advance)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Past the `=`, a left side that is not a location is refused at it.
|
|
65
|
+
def assignment(target, equals)
|
|
66
|
+
unless location?(target)
|
|
67
|
+
refuse("unassignable_location", "Left side of '=' must be an assignable location - an identifier, " \
|
|
68
|
+
"a property access, or a bracket access.", equals)
|
|
69
|
+
end
|
|
70
|
+
value = expression
|
|
71
|
+
Ast::Assignment.new(target:, value:, **infix(equals, target, value))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def location?(node)
|
|
75
|
+
node = node.object while ACCESSES.any? { |access| node.is_a?(access) }
|
|
76
|
+
node.is_a?(Ast::Identifier)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def if_statement(keyword)
|
|
80
|
+
condition = expression
|
|
81
|
+
then_block = block
|
|
82
|
+
else_block = else_clause
|
|
83
|
+
Ast::If.new(condition:, then_block:, else_block:, **prefixed(keyword, else_block || then_block))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# An `else if` is an else block holding the one `if`, at its position
|
|
87
|
+
# and over its span; it nests a level as a block does.
|
|
88
|
+
def else_clause
|
|
89
|
+
return unless at?(:else_kw)
|
|
90
|
+
|
|
91
|
+
advance
|
|
92
|
+
return block unless at?(:if_kw)
|
|
93
|
+
|
|
94
|
+
nested = descend { if_statement(advance) }
|
|
95
|
+
Ast::Block.new(statements: [nested], position: nested.position, span: nested.span)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def while_statement(keyword)
|
|
99
|
+
condition = expression
|
|
100
|
+
body = block
|
|
101
|
+
Ast::While.new(condition:, body:, **prefixed(keyword, body))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Braces around zero statements or more, one level deeper.
|
|
105
|
+
def block
|
|
106
|
+
open = expect(:lbrace, "expected_open_brace", "Expected '{' to open a block but found")
|
|
107
|
+
statements = at?(:rbrace) ? [] : descend { statement_sequence(:rbrace) }
|
|
108
|
+
close = expect(:rbrace, "expected_close_brace", "Expected '}' to close the block but found")
|
|
109
|
+
Ast::Block.new(statements:, **delimited(open, close))
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
private_constant :Statements
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Predicator
|
|
4
|
+
module Parser
|
|
5
|
+
# How a refusal's message names a token, in the reference's words: the
|
|
6
|
+
# word connectives uppercase whatever case the source wrote, the end of
|
|
7
|
+
# input in words, a string in double quotes, and every other token that
|
|
8
|
+
# carries a value as its kind and that value in single quotes. Read into
|
|
9
|
+
# the parse that includes this module.
|
|
10
|
+
module TokenText
|
|
11
|
+
# The tokens named without their value.
|
|
12
|
+
FIXED = {
|
|
13
|
+
undefined: "'undefined'", null: "'null'", gt: "'>'", lt: "'<'", gte: "'>='", lte: "'<='", eq: "'='",
|
|
14
|
+
ne: "'!='", equal_equal: "'=='", strict_equal: "'==='", strict_ne: "'!=='", and_op: "'AND'",
|
|
15
|
+
or_op: "'OR'", not_op: "'NOT'", in_op: "'IN'", contains_op: "'CONTAINS'", ago_op: "'ago'",
|
|
16
|
+
from_op: "'from'", now_op: "'now'", next_op: "'next'", last_op: "'last'", lparen: "'('", rparen: "')'",
|
|
17
|
+
lbracket: "'['", rbracket: "']'", lbrace: "'{'", rbrace: "'}'", colon: "':'", double_colon: "'::'",
|
|
18
|
+
comma: "','", semicolon: "';'", dot: "'.'", plus: "'+'", minus: "'-'", multiply: "'*'", divide: "'/'",
|
|
19
|
+
modulo: "'%'", and_and: "'&&'", or_or: "'||'", bang: "'!'", if_kw: "'if'", else_kw: "'else'",
|
|
20
|
+
while_kw: "'while'", eof: "end of input"
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
# The kind a token carrying a value is named by.
|
|
24
|
+
KINDS = {
|
|
25
|
+
integer: "number", float: "number", fractional_number: "number", boolean: "boolean", date: "date",
|
|
26
|
+
datetime: "datetime", identifier: "identifier", function_name: "function",
|
|
27
|
+
qualified_function_name: "function", duration_unit: "duration unit"
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def describe(token)
|
|
33
|
+
return FIXED.fetch(token.type) if FIXED.key?(token.type)
|
|
34
|
+
return %(string "#{token.value}") if token.type == :string
|
|
35
|
+
|
|
36
|
+
"#{KINDS.fetch(token.type)} '#{spelling(token.value)}'"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# A float as the reference writes one; an infinite one, which the
|
|
40
|
+
# lexer answers for a decimal too long for a finite Float where the
|
|
41
|
+
# reference's lexer raises, as Ruby writes it.
|
|
42
|
+
def spelling(value)
|
|
43
|
+
case value
|
|
44
|
+
when Float then value.finite? ? FloatText.spell(value) : value.to_s
|
|
45
|
+
when Lexer::Fraction then "#{value.whole}.#{value.fraction}"
|
|
46
|
+
else value.to_s
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
private_constant :TokenText
|
|
51
|
+
end
|
|
52
|
+
end
|