activefacts-cql 1.7.1
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/.gitignore +11 -0
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +19 -0
- data/Rakefile +6 -0
- data/activefacts-cql.gemspec +29 -0
- data/bin/setup +7 -0
- data/lib/activefacts/cql.rb +7 -0
- data/lib/activefacts/cql/.gitignore +0 -0
- data/lib/activefacts/cql/Rakefile +14 -0
- data/lib/activefacts/cql/compiler.rb +156 -0
- data/lib/activefacts/cql/compiler/clause.rb +1137 -0
- data/lib/activefacts/cql/compiler/constraint.rb +581 -0
- data/lib/activefacts/cql/compiler/entity_type.rb +457 -0
- data/lib/activefacts/cql/compiler/expression.rb +443 -0
- data/lib/activefacts/cql/compiler/fact.rb +390 -0
- data/lib/activefacts/cql/compiler/fact_type.rb +421 -0
- data/lib/activefacts/cql/compiler/query.rb +106 -0
- data/lib/activefacts/cql/compiler/shared.rb +161 -0
- data/lib/activefacts/cql/compiler/value_type.rb +174 -0
- data/lib/activefacts/cql/parser.rb +234 -0
- data/lib/activefacts/cql/parser/CQLParser.treetop +167 -0
- data/lib/activefacts/cql/parser/Context.treetop +48 -0
- data/lib/activefacts/cql/parser/Expressions.treetop +67 -0
- data/lib/activefacts/cql/parser/FactTypes.treetop +358 -0
- data/lib/activefacts/cql/parser/Language/English.treetop +315 -0
- data/lib/activefacts/cql/parser/Language/French.treetop +315 -0
- data/lib/activefacts/cql/parser/Language/Mandarin.treetop +304 -0
- data/lib/activefacts/cql/parser/LexicalRules.treetop +253 -0
- data/lib/activefacts/cql/parser/ObjectTypes.treetop +210 -0
- data/lib/activefacts/cql/parser/Terms.treetop +183 -0
- data/lib/activefacts/cql/parser/ValueTypes.treetop +202 -0
- data/lib/activefacts/cql/parser/nodes.rb +49 -0
- data/lib/activefacts/cql/require.rb +36 -0
- data/lib/activefacts/cql/verbaliser.rb +804 -0
- data/lib/activefacts/cql/version.rb +5 -0
- data/lib/activefacts/input/cql.rb +43 -0
- data/lib/rubygems_plugin.rb +12 -0
- metadata +167 -0
@@ -0,0 +1,210 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts CQL Parser.
|
3
|
+
# Parse rules relating to ObjectType definitions.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
module ActiveFacts
|
8
|
+
module CQL
|
9
|
+
grammar ObjectTypes
|
10
|
+
rule object_type
|
11
|
+
value_type
|
12
|
+
/ entity_type
|
13
|
+
/ named_fact_type
|
14
|
+
/ anonymous_fact_type
|
15
|
+
end
|
16
|
+
|
17
|
+
rule entity_type
|
18
|
+
s each?
|
19
|
+
s term_definition_name
|
20
|
+
m1:mapping_pragmas
|
21
|
+
c:context_note?
|
22
|
+
sup:(basetype / subtype)
|
23
|
+
&{|s|
|
24
|
+
# There's an implicit type when we use an identification mode, register it:
|
25
|
+
mode = s[6].identification_mode
|
26
|
+
if mode
|
27
|
+
input.context.object_type(s[3].value+mode, "identification mode type")
|
28
|
+
input.context.object_type(s[3].value+' '+mode, "identification mode type")
|
29
|
+
end
|
30
|
+
true
|
31
|
+
}
|
32
|
+
m2:mapping_pragmas
|
33
|
+
c2:context_note?
|
34
|
+
ec:entity_clauses?
|
35
|
+
';'
|
36
|
+
{
|
37
|
+
def ast
|
38
|
+
name = term_definition_name.value
|
39
|
+
clauses_ast = ec.empty? ? [] : ec.ast
|
40
|
+
pragmas = m1.value+m2.value
|
41
|
+
pragmas << 'independent' if sup.independent
|
42
|
+
context_note = !c.empty? ? c.ast : (!c2.empty? ? c2.ast : nil)
|
43
|
+
Compiler::EntityType.new name, sup.supers, sup.ast, pragmas, clauses_ast, context_note
|
44
|
+
end
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
rule basetype
|
49
|
+
basetype_expression
|
50
|
+
{
|
51
|
+
def ast; identification.ast; end
|
52
|
+
def supers; []; end
|
53
|
+
def identification_mode; identification.mode; end
|
54
|
+
def independent; !i.empty?; end
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
rule subtype
|
59
|
+
subtype_expression
|
60
|
+
{
|
61
|
+
def ast; ident.empty? ? nil : ident.ast; end
|
62
|
+
def supers; supertype_list.value; end
|
63
|
+
def identification_mode; ident.empty? ? nil : ident.mode; end
|
64
|
+
def independent; !i.empty?; end
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
rule supertype_list
|
69
|
+
primary:term s alternate_supertypes:( (','/'and' !alpha) s !identified_by name:term s )*
|
70
|
+
{
|
71
|
+
def value
|
72
|
+
[primary.value, *alternate_supertypes.elements.map { |sup| sup.name.value } ]
|
73
|
+
end
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
rule identification
|
78
|
+
# REVISIT: Consider distinguishing "-Id" from just "Id", and not prepending the entity type name if no "-"
|
79
|
+
identified_by its s i:(term/implicit_value_type_name) value_type_parameters
|
80
|
+
r:(value_constraint enforcement)? # Reference Mode; value_constraint may be needed for the ValueType
|
81
|
+
{
|
82
|
+
def ast
|
83
|
+
if r.empty?
|
84
|
+
value_constraint = nil
|
85
|
+
else
|
86
|
+
value_constraint = Compiler::ValueConstraint.new(r.value_constraint.ast, r.enforcement.ast)
|
87
|
+
end
|
88
|
+
Compiler::ReferenceMode.new(i.value, value_constraint, value_type_parameters.values)
|
89
|
+
end
|
90
|
+
|
91
|
+
def mode
|
92
|
+
i.value
|
93
|
+
end
|
94
|
+
}
|
95
|
+
/
|
96
|
+
identified_by role_list
|
97
|
+
&{|s|
|
98
|
+
role_list = s[-1]
|
99
|
+
forwards = role_list.ast.
|
100
|
+
map do |role|
|
101
|
+
next nil if role.is_a?(Compiler::Clause) # Can't forward-reference unaries
|
102
|
+
next nil if role.leading_adjective or role.trailing_adjective
|
103
|
+
role.term
|
104
|
+
end.
|
105
|
+
compact
|
106
|
+
input.context.allowed_forward_terms(forwards)
|
107
|
+
true
|
108
|
+
}
|
109
|
+
{
|
110
|
+
def ast
|
111
|
+
role_list.ast
|
112
|
+
end
|
113
|
+
|
114
|
+
def mode
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
# Identified by roles... also used for constraints, beware
|
121
|
+
rule role_list
|
122
|
+
a:any? s
|
123
|
+
head:term_or_unary s
|
124
|
+
tail:(
|
125
|
+
( and S / ',' s )
|
126
|
+
any? s
|
127
|
+
term_or_unary s
|
128
|
+
)*
|
129
|
+
{
|
130
|
+
def ast
|
131
|
+
[head.ast, *tail.elements.map{|e| e.term_or_unary.ast}]
|
132
|
+
end
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
rule unary_text
|
137
|
+
(s !any !non_phrase !term id)*
|
138
|
+
{
|
139
|
+
def node_type; :linking; end
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
rule term_or_unary
|
144
|
+
pre_text:unary_text s term post_text:unary_text s ss:subscript?
|
145
|
+
{
|
146
|
+
def ast
|
147
|
+
t = term.ast
|
148
|
+
t.role_name = ss.value if !ss.empty?
|
149
|
+
if pre_text.elements.size == 0 && post_text.elements.size == 0
|
150
|
+
t
|
151
|
+
else
|
152
|
+
pre_words = pre_text.elements.map{|w| w.id.text_value}
|
153
|
+
post_words = post_text.elements.map{|w| w.id.text_value}
|
154
|
+
Compiler::Clause.new(pre_words + [t] + post_words, [], nil)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
}
|
158
|
+
/
|
159
|
+
s !non_phrase id s &non_phrase s ss:subscript?
|
160
|
+
{ # A forward-referenced entity type
|
161
|
+
# REVISIT: A change in this rule might allow forward-referencing a multi-word term
|
162
|
+
def ast
|
163
|
+
Compiler::Reference.new(id.text_value, nil, nil, nil, nil, ss.empty? ? nil : ss.value)
|
164
|
+
end
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
rule mapping_pragmas
|
169
|
+
'[' s h:mapping_pragma t:(s ',' s mapping_pragma)* s ']' s
|
170
|
+
{
|
171
|
+
def value
|
172
|
+
t.elements.inject([h.value*' ']) do |a, e|
|
173
|
+
a << e.mapping_pragma.value*' '
|
174
|
+
end
|
175
|
+
end
|
176
|
+
}
|
177
|
+
/
|
178
|
+
s
|
179
|
+
{ def value; []; end }
|
180
|
+
end
|
181
|
+
|
182
|
+
# Each mapping_pragma returns an array of words
|
183
|
+
rule mapping_pragma
|
184
|
+
was s names:(id s)+
|
185
|
+
{ # Old or previous name of an object type:
|
186
|
+
def value
|
187
|
+
[ was.text_value ] + names.elements.map{|n|n.text_value}
|
188
|
+
end
|
189
|
+
}
|
190
|
+
/
|
191
|
+
head:id tail:(s id)*
|
192
|
+
{ # A sequence of one or more words denoting a pragma:
|
193
|
+
def value
|
194
|
+
([head]+tail.elements.map(&:id)).map(&:text_value)
|
195
|
+
end
|
196
|
+
}
|
197
|
+
end
|
198
|
+
|
199
|
+
rule entity_clauses
|
200
|
+
(':' / where) s query_clauses
|
201
|
+
{
|
202
|
+
def ast
|
203
|
+
query_clauses.ast
|
204
|
+
end
|
205
|
+
}
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts CQL Parser.
|
3
|
+
# Parse rules relating to Term names
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
module ActiveFacts
|
8
|
+
module CQL
|
9
|
+
grammar Terms
|
10
|
+
rule term_definition_name
|
11
|
+
id s t:(!non_term_def id s)*
|
12
|
+
<Parser::TermDefinitionNameNode>
|
13
|
+
end
|
14
|
+
|
15
|
+
rule non_term_def
|
16
|
+
mapping_pragmas entity_prefix
|
17
|
+
/ mapping_pragmas written_as # Value type
|
18
|
+
/ mapping_pragmas is_where # Objectified type
|
19
|
+
/ non_phrase
|
20
|
+
/ identified_by # as in: "a kind of X identified by..."
|
21
|
+
/ in_units
|
22
|
+
/ auto_assignment
|
23
|
+
/ value_constraint
|
24
|
+
end
|
25
|
+
|
26
|
+
rule entity_prefix
|
27
|
+
is s (independent s )? identified_by
|
28
|
+
/
|
29
|
+
subtype_prefix (independent s )? term_definition_name
|
30
|
+
&{|e| input.context.object_type(e[2].value, "subtype") }
|
31
|
+
end
|
32
|
+
|
33
|
+
rule prescan
|
34
|
+
s each?
|
35
|
+
s (
|
36
|
+
term_definition_name mapping_pragmas entity_prefix
|
37
|
+
&{|e| input.context.object_type(e[0].value, "entity type") }
|
38
|
+
/
|
39
|
+
t1:term_definition_name mapping_pragmas written_as any? s t2:term_definition_name
|
40
|
+
&{|e|
|
41
|
+
new_term = e[0].value
|
42
|
+
input.context.object_type(new_term, "value type")
|
43
|
+
base_term = e[5].value
|
44
|
+
input.context.object_type(base_term, "value type")
|
45
|
+
}
|
46
|
+
/
|
47
|
+
term_definition_name s mapping_pragmas is_where
|
48
|
+
&{|e| input.context.object_type(e[0].value, "objectified_fact_type") }
|
49
|
+
)?
|
50
|
+
prescan_rest
|
51
|
+
&{|s|
|
52
|
+
# Wipe any terminal failures that were added:
|
53
|
+
@terminal_failures = []
|
54
|
+
@max_terminal_failure_index = start_index
|
55
|
+
|
56
|
+
# puts "========== prescan is complete on #{(s.map{|e|e.text_value}*" ").inspect} =========="
|
57
|
+
false
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
# Do a first-pass mainly lexical analysis, looking for role name definitions and adjectives,
|
62
|
+
# for use in detecting terms later.
|
63
|
+
rule prescan_rest
|
64
|
+
&{|s| input.context.reset_role_names }
|
65
|
+
(
|
66
|
+
context_note # Context notes have different lexical conventions
|
67
|
+
/ '(' as S term_definition_name s ')' s # Prepare for a Role Name
|
68
|
+
&{|s| input.context.role_name(s[3].value) }
|
69
|
+
/ new_derived_value # Prepare for a derived term
|
70
|
+
/ new_adjective_term # Prepare for an existing term with new Adjectives
|
71
|
+
# The remaining rules exist to correctly eat up anything that doesn't match the above:
|
72
|
+
/ global_term # If we see A B - C D, don't recognise B as a new adjective for C D.
|
73
|
+
/ prescan_aggregate
|
74
|
+
/ id
|
75
|
+
# / literal # REVISIT: Literals might contain "(as Foo)" and mess things up
|
76
|
+
/ range # Covers all numbers and strings
|
77
|
+
/ comparator # handle two-character operators
|
78
|
+
/ S # White space and comments, must precede / and *
|
79
|
+
/ [-+{}\[\].,:^/%*()] # All other punctuation and operators
|
80
|
+
)* [?;] s
|
81
|
+
end
|
82
|
+
|
83
|
+
# Not sure this is even needed, but it doesn't seem to hurt:
|
84
|
+
rule prescan_aggregate
|
85
|
+
aggregate_type:id s agg_of s global_term agg_in s &'('
|
86
|
+
end
|
87
|
+
|
88
|
+
rule new_derived_value
|
89
|
+
!global_term id derived_value_continuation? s '='
|
90
|
+
&{|s|
|
91
|
+
name = [s[1].text_value] + (s[2].empty? ? [] : s[2].value)
|
92
|
+
input.context.object_type(name*' ', "derived value type")
|
93
|
+
}
|
94
|
+
/
|
95
|
+
'=' s !global_term id derived_value_continuation? s (that/who)
|
96
|
+
&{|s|
|
97
|
+
name = [s[3].text_value] + (s[4].empty? ? [] : s[4].value)
|
98
|
+
input.context.object_type(name*' ', "derived value type")
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
# Derived values are new terms introduced by an = sign before an expression
|
103
|
+
# This rule handles trailing words of a multi-word derived value
|
104
|
+
rule derived_value_continuation
|
105
|
+
s '-' tail:(s !global_term !(that/who) id)*
|
106
|
+
{
|
107
|
+
def value
|
108
|
+
tail.elements.map{|e| e.id.text_value}
|
109
|
+
end
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
# Used during the pre-scan, match a term with new adjective(s)
|
114
|
+
rule new_adjective_term
|
115
|
+
!global_term adj:id '-' '-'? lead_intervening s global_term # Definitely a new leading adjective for this term
|
116
|
+
&{|s| adj = [s[1].text_value, s[4].value].compact*" "; input.context.new_leading_adjective_term(adj, s[6].text_value) }
|
117
|
+
/
|
118
|
+
global_term s trail_intervening '-' '-'? !global_term adj:id # Definitely a new trailing adjective for this term
|
119
|
+
&{|s| adj = [s[2].value, s[6].text_value].compact*" "; input.context.new_trailing_adjective_term(adj, s[0].text_value) }
|
120
|
+
end
|
121
|
+
|
122
|
+
rule lead_intervening # Words intervening between a new adjective and the term
|
123
|
+
(S !global_term id)*
|
124
|
+
{
|
125
|
+
def value
|
126
|
+
elements.size == 0 ? nil : elements.map{|e| e.id.text_value}*" "
|
127
|
+
end
|
128
|
+
}
|
129
|
+
end
|
130
|
+
|
131
|
+
rule trail_intervening # Words intervening between a new adjective and the term
|
132
|
+
(!global_term id S)*
|
133
|
+
{
|
134
|
+
def value
|
135
|
+
elements.size == 0 ? nil : elements.map{|e| e.id.text_value}*" "
|
136
|
+
end
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
140
|
+
# This is the rule to use after the prescan; it only succeeds on a complete term or role reference
|
141
|
+
rule term
|
142
|
+
s head:id x &{|s| w = s[1].text_value; input.context.term_starts?(w, s[2]) }
|
143
|
+
tail:(
|
144
|
+
s '-'? dbl:'-'? s w:id &{|s| w = s[4].text_value; input.context.term_continues?(w) }
|
145
|
+
)* &{|s| input.context.term_complete? }
|
146
|
+
<Parser::TermNode>
|
147
|
+
/
|
148
|
+
s head:id '-' '-'? s term &{|s| s[5].ast.leading_adjective == nil }
|
149
|
+
<Parser::TermLANode>
|
150
|
+
end
|
151
|
+
|
152
|
+
rule x
|
153
|
+
'' <SavedContext>
|
154
|
+
end
|
155
|
+
|
156
|
+
rule global_term
|
157
|
+
# This rule shouldn't be used outside the prescan, it will memoize the wrong things.
|
158
|
+
head:id x &{|s| input.context.term_starts?(s[0].text_value, s[1]) }
|
159
|
+
tail:(s w:id &{|s| input.context.term_continues?(s[1].text_value) } )*
|
160
|
+
{ def value
|
161
|
+
tail.elements.inject(head.value) { |t, e| "#{t} #{e.w.value}" }
|
162
|
+
end
|
163
|
+
}
|
164
|
+
end
|
165
|
+
|
166
|
+
rule non_phrase
|
167
|
+
# These words are illegal in (but maybe ok following) a clause where a phrase is expected:
|
168
|
+
and
|
169
|
+
/ but
|
170
|
+
/ if
|
171
|
+
/ role_list_constraint_followers
|
172
|
+
/ only_if
|
173
|
+
/ or
|
174
|
+
/ quantifier
|
175
|
+
/ returning
|
176
|
+
/ then
|
177
|
+
/ value_constraint
|
178
|
+
/ where
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts CQL Parser.
|
3
|
+
# Parse rules relating to ValueType definitions.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
module ActiveFacts
|
8
|
+
module CQL
|
9
|
+
grammar ValueTypes
|
10
|
+
rule value_type
|
11
|
+
s each?
|
12
|
+
s term_definition_name
|
13
|
+
m1:mapping_pragmas
|
14
|
+
# REVISIT: ORM2 would allow (subtype_prefix term)?
|
15
|
+
written_as
|
16
|
+
any? s
|
17
|
+
base:(term/implicit_value_type_name) s
|
18
|
+
value_type_parameters
|
19
|
+
u:in_units?
|
20
|
+
a:auto_assignment?
|
21
|
+
c:context_note?
|
22
|
+
r:(value_constraint enforcement)?
|
23
|
+
m2:mapping_pragmas
|
24
|
+
c2:context_note?
|
25
|
+
s ';' s
|
26
|
+
{
|
27
|
+
def ast
|
28
|
+
name = term_definition_name.value
|
29
|
+
params = value_type_parameters.values
|
30
|
+
value_constraint = nil
|
31
|
+
unless r.empty?
|
32
|
+
value_constraint = Compiler::ValueConstraint.new(r.value_constraint.ast, r.enforcement.ast)
|
33
|
+
end
|
34
|
+
units = u.empty? ? [] : u.units.value
|
35
|
+
auto_assigned_at = a.empty? ? nil : a.auto_assigned_at
|
36
|
+
pragmas = m1.value+m2.value
|
37
|
+
context_note = !c.empty? ? c.ast : (!c2.empty? ? c2.ast : nil)
|
38
|
+
Compiler::ValueType.new name, base.value, params, units, value_constraint, pragmas, context_note, auto_assigned_at
|
39
|
+
end
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
rule in_units
|
44
|
+
in S units
|
45
|
+
end
|
46
|
+
|
47
|
+
rule implicit_value_type_name
|
48
|
+
id
|
49
|
+
{
|
50
|
+
def node_type; :term; end
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
rule value_type_parameters
|
55
|
+
'(' s tpl:type_parameter_list? ')' s
|
56
|
+
{ def values; tpl.empty? ? [] : tpl.values; end }
|
57
|
+
/ s
|
58
|
+
{ def values; []; end }
|
59
|
+
end
|
60
|
+
|
61
|
+
rule type_parameter_list
|
62
|
+
head:number s tail:( ',' s number s )*
|
63
|
+
{
|
64
|
+
def values
|
65
|
+
[head.value, *tail.elements.map{|i| i.number.value}]
|
66
|
+
end
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
rule unit_definition
|
71
|
+
u:(
|
72
|
+
s coeff:unit_coefficient? base:units? s o:unit_offset?
|
73
|
+
conversion
|
74
|
+
singular:unit_name s plural:('/' s p:unit_name s)?
|
75
|
+
/
|
76
|
+
s singular:unit_name s plural:('/' s p:unit_name s)?
|
77
|
+
conversion
|
78
|
+
coeff:unit_coefficient? base:units? s o:unit_offset?
|
79
|
+
)
|
80
|
+
q:(approximately '' / ephemera s url )? s
|
81
|
+
';'
|
82
|
+
{
|
83
|
+
def ast
|
84
|
+
singular = u.singular.text_value
|
85
|
+
plural = u.plural.text_value.empty? ? nil : u.plural.p.text_value
|
86
|
+
if u.coeff.empty?
|
87
|
+
raise "Unit definition requires either a coefficient or an ephemera URL" unless q.respond_to?(:ephemera)
|
88
|
+
numerator,denominator = 1, 1
|
89
|
+
else
|
90
|
+
numerator, denominator = *u.coeff.ast
|
91
|
+
end
|
92
|
+
offset = u.o.text_value.empty? ? 0 : u.o.value
|
93
|
+
bases = u.base.empty? ? [] : u.base.value
|
94
|
+
approximately = q.respond_to?(:approximately) || u.conversion.approximate?
|
95
|
+
ephemera = q.respond_to?(:ephemera) ? q.url.text_value : nil
|
96
|
+
Compiler::Unit.new singular, plural, numerator, denominator, offset, bases, approximately, ephemera
|
97
|
+
end
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
rule unit_name
|
102
|
+
id
|
103
|
+
{
|
104
|
+
def node_type; :unit; end
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
rule unit_coefficient
|
110
|
+
numerator:number denominator:(s '/' s number)? s
|
111
|
+
{
|
112
|
+
def ast
|
113
|
+
[ numerator.text_value,
|
114
|
+
(denominator.text_value.empty? ? "1" : denominator.number.text_value)
|
115
|
+
]
|
116
|
+
end
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
rule unit_offset
|
121
|
+
sign:[-+] s number s
|
122
|
+
{ def value
|
123
|
+
sign.text_value == '-' ? "-"+number.text_value : number.text_value
|
124
|
+
end
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
# In a unit definition, we may use undefined base units; this is the only way to get fundamental units
|
129
|
+
rule units
|
130
|
+
!non_unit maybe_unit s tail:(!non_unit maybe_unit s)* div:('/' s maybe_unit s tail:(!non_unit maybe_unit s)*)?
|
131
|
+
{ def value
|
132
|
+
tail.elements.inject([maybe_unit.value]) { |a, e| a << e.maybe_unit.value } +
|
133
|
+
(div.text_value.empty? ? [] : div.tail.elements.inject([div.maybe_unit.inverse]) { |a, e| a << e.maybe_unit.inverse })
|
134
|
+
end
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
138
|
+
rule non_unit
|
139
|
+
restricted_to / conversion / approximately / ephemera / auto_assignment
|
140
|
+
end
|
141
|
+
|
142
|
+
rule unit
|
143
|
+
maybe_unit &{|s| input.context.unit?(s[0].unit_name.text_value) }
|
144
|
+
end
|
145
|
+
|
146
|
+
rule maybe_unit
|
147
|
+
unit_name pow:('^' '-'? [0-9])?
|
148
|
+
{ def value
|
149
|
+
[unit_name.text_value, pow.text_value.empty? ? 1 : Integer(pow.text_value[1..-1])]
|
150
|
+
end
|
151
|
+
def inverse
|
152
|
+
a = value
|
153
|
+
a[1] = -a[1]
|
154
|
+
a
|
155
|
+
end
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
rule value_constraint
|
160
|
+
restricted_to restricted_values c:context_note?
|
161
|
+
{
|
162
|
+
def ast
|
163
|
+
v = restricted_values.values
|
164
|
+
c[:context_note] = c.ast unless c.empty?
|
165
|
+
v
|
166
|
+
end
|
167
|
+
}
|
168
|
+
# REVISIT: "where the possible value/s of that <Term> is/are value (, ...)"
|
169
|
+
end
|
170
|
+
|
171
|
+
rule restricted_values
|
172
|
+
range_list s u:units?
|
173
|
+
{
|
174
|
+
def values
|
175
|
+
{ :ranges => range_list.ranges,
|
176
|
+
:units => u.empty? ? nil : u.value
|
177
|
+
}
|
178
|
+
end
|
179
|
+
}
|
180
|
+
/
|
181
|
+
regular_expression
|
182
|
+
{
|
183
|
+
def values
|
184
|
+
{ :regular_expression => contents }
|
185
|
+
end
|
186
|
+
}
|
187
|
+
end
|
188
|
+
|
189
|
+
rule range_list
|
190
|
+
'{' s
|
191
|
+
head:range s tail:( ',' s range )*
|
192
|
+
'}' s
|
193
|
+
{
|
194
|
+
def ranges
|
195
|
+
[head.value, *tail.elements.map{|e| e.range.value }]
|
196
|
+
end
|
197
|
+
}
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|