activefacts-cql 1.8.3 → 1.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/activefacts-cql.gemspec +1 -1
- data/lib/activefacts/cql/compiler.rb +84 -29
- data/lib/activefacts/cql/compiler/clause.rb +51 -9
- data/lib/activefacts/cql/compiler/constraint.rb +1 -1
- data/lib/activefacts/cql/compiler/expression.rb +279 -34
- data/lib/activefacts/cql/compiler/fact_type.rb +4 -2
- data/lib/activefacts/cql/compiler/query.rb +13 -9
- data/lib/activefacts/cql/compiler/shared.rb +10 -6
- data/lib/activefacts/cql/compiler/transform_rule.rb +136 -0
- data/lib/activefacts/cql/parser.rb +45 -2
- data/lib/activefacts/cql/parser/CQLParser.treetop +59 -5
- data/lib/activefacts/cql/parser/Language/English.treetop +5 -1
- data/lib/activefacts/cql/parser/Language/French.treetop +6 -2
- data/lib/activefacts/cql/parser/Language/Mandarin.treetop +2 -1
- data/lib/activefacts/cql/parser/Terms.treetop +2 -2
- data/lib/activefacts/cql/parser/TransformRules.treetop +226 -0
- data/lib/activefacts/cql/verbaliser.rb +5 -7
- data/lib/activefacts/cql/version.rb +1 -1
- data/lib/activefacts/input/cql.rb +2 -1
- data/lib/rubygems_plugin.rb +15 -12
- metadata +6 -4
@@ -44,7 +44,7 @@ module ActiveFacts
|
|
44
44
|
unless @conditions.empty? and @returning.empty?
|
45
45
|
trace :query, "building query for derived fact type (returning #{@returning}) with #{@conditions.size} conditions: (#{@conditions.map{|c|c.inspect}*', '})" do
|
46
46
|
@query = build_variables(@conditions.flatten)
|
47
|
-
@roles_by_binding = build_all_steps(@conditions)
|
47
|
+
@roles_by_binding = build_all_steps(@query, @conditions)
|
48
48
|
@query.validate
|
49
49
|
@query
|
50
50
|
end
|
@@ -108,7 +108,9 @@ module ActiveFacts
|
|
108
108
|
# Prepare to objectify the fact type (so readings for link fact types can be created)
|
109
109
|
if @name
|
110
110
|
entity_type = @vocabulary.valid_entity_type_name(@name)
|
111
|
-
|
111
|
+
|
112
|
+
# REVISIT disable name check -- GSP 5 Jul 2017
|
113
|
+
raise "You can't objectify #{@name}, it already exists" if entity_type && false
|
112
114
|
@entity_type = @constellation.EntityType(@vocabulary, @name, :fact_type => @fact_type, :concept => :new)
|
113
115
|
end
|
114
116
|
|
@@ -8,8 +8,11 @@ module ActiveFacts
|
|
8
8
|
query = @constellation.Query(:new)
|
9
9
|
all_bindings_in_clauses(clauses_list).
|
10
10
|
each do |binding|
|
11
|
-
|
12
|
-
|
11
|
+
var_name = (r = binding.refs.select{|r| r.is_a?(Reference)}.first) ? r.var_name : nil
|
12
|
+
trace :query, "Creating variable #{query.all_variable.size} for #{binding.inspect} with role_name #{var_name}"
|
13
|
+
binding.variable = @constellation.Variable(
|
14
|
+
query, query.all_variable.size, :object_type => binding.player, role_name: var_name
|
15
|
+
)
|
13
16
|
if literal = binding.refs.detect{|r| r.literal}
|
14
17
|
if literal.kind_of?(ActiveFacts::CQL::Compiler::Reference)
|
15
18
|
# REVISIT: Fix this crappy ad-hoc polymorphism hack
|
@@ -23,21 +26,21 @@ module ActiveFacts
|
|
23
26
|
end
|
24
27
|
end
|
25
28
|
|
26
|
-
def build_all_steps(clauses_list)
|
29
|
+
def build_all_steps(query, clauses_list)
|
27
30
|
roles_by_binding = {}
|
28
31
|
trace :query, "Building steps" do
|
29
32
|
clauses_list.each do |clause|
|
30
|
-
build_step(clause, roles_by_binding)
|
33
|
+
build_step(query, clause, roles_by_binding)
|
31
34
|
end
|
32
35
|
end
|
33
36
|
roles_by_binding
|
34
37
|
end
|
35
38
|
|
36
|
-
def build_step clause, roles_by_binding = {}, parent_variable = nil
|
39
|
+
def build_step query, clause, roles_by_binding = {}, parent_variable = nil
|
37
40
|
return unless clause.refs.size > 0 # Empty clause... really?
|
38
41
|
|
39
42
|
step = @constellation.Step(
|
40
|
-
|
43
|
+
query, query.all_step.size,
|
41
44
|
:fact_type => clause.fact_type,
|
42
45
|
:alternative_set => nil,
|
43
46
|
:is_disallowed => clause.certainty == false,
|
@@ -56,7 +59,7 @@ module ActiveFacts
|
|
56
59
|
objectification_step = nil
|
57
60
|
if ref.nested_clauses
|
58
61
|
ref.nested_clauses.each do |nested_clause|
|
59
|
-
objectification_step = build_step nested_clause, roles_by_binding
|
62
|
+
objectification_step = build_step(query, nested_clause, roles_by_binding)
|
60
63
|
if ref.binding.player.is_a?(ActiveFacts::Metamodel::EntityType) and
|
61
64
|
ref.binding.player.fact_type == nested_clause.fact_type
|
62
65
|
objectification_step.objectification_variable = binding.variable
|
@@ -72,9 +75,10 @@ module ActiveFacts
|
|
72
75
|
if binding.variable.object_type.common_supertype(role.object_type)
|
73
76
|
# REVISIT: there's an implicit subtyping step here, create it; then always raise the error here.
|
74
77
|
# I don't want to do this for now because the verbaliser will always verbalise all steps.
|
75
|
-
raise "Disallowing implicit subtyping step from #{role.object_type.name} to #{binding.variable.object_type.name} in #{clause.fact_type.default_reading.inspect}"
|
78
|
+
# raise "Disallowing implicit subtyping step from #{role.object_type.name} to #{binding.variable.object_type.name} in #{clause.fact_type.default_reading.inspect}"
|
79
|
+
else
|
80
|
+
raise "A #{role.object_type.name} cannot satisfy #{binding.variable.object_type.name} in #{clause.fact_type.default_reading.inspect}"
|
76
81
|
end
|
77
|
-
raise "A #{role.object_type.name} cannot satisfy #{binding.variable.object_type.name} in #{clause.fact_type.default_reading.inspect}"
|
78
82
|
end
|
79
83
|
|
80
84
|
trace :query, "Creating Play for #{ref.inspect}"
|
@@ -13,7 +13,7 @@ module ActiveFacts
|
|
13
13
|
class Binding
|
14
14
|
attr_reader :player # The ObjectType (object type)
|
15
15
|
attr_reader :refs # an array of the References
|
16
|
-
|
16
|
+
attr_accessor :role_name
|
17
17
|
attr_accessor :rebound_to # Loose binding may set this to another binding
|
18
18
|
attr_reader :variable
|
19
19
|
attr_accessor :instance # When binding fact instances, the instance goes here
|
@@ -35,7 +35,7 @@ module ActiveFacts
|
|
35
35
|
def <=>(other)
|
36
36
|
key <=> other.key
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
def variable= v
|
40
40
|
@variable = v # A place for a breakpoint :)
|
41
41
|
end
|
@@ -111,15 +111,17 @@ module ActiveFacts
|
|
111
111
|
end
|
112
112
|
|
113
113
|
class Vocabulary < Definition
|
114
|
-
def initialize name
|
114
|
+
def initialize name, is_transform, version_number
|
115
115
|
@name = name
|
116
|
+
@is_transform = is_transform
|
117
|
+
@version_number = version_number
|
116
118
|
end
|
117
119
|
|
118
120
|
def compile
|
119
121
|
if @constellation.Vocabulary.size > 0
|
120
122
|
@constellation.Topic @name
|
121
123
|
else
|
122
|
-
@constellation.Vocabulary @
|
124
|
+
@constellation.Vocabulary(@name, is_transform: @is_transform, version_number: @version_number)
|
123
125
|
end
|
124
126
|
end
|
125
127
|
|
@@ -129,9 +131,11 @@ module ActiveFacts
|
|
129
131
|
end
|
130
132
|
|
131
133
|
class Import < Definition
|
132
|
-
def initialize parser, name, alias_hash
|
134
|
+
def initialize parser, name, import_role, version_pattern, alias_hash
|
133
135
|
@parser = parser
|
134
136
|
@name = name
|
137
|
+
@import_role = import_role
|
138
|
+
@version_pattern = version_pattern
|
135
139
|
@alias_hash = alias_hash
|
136
140
|
end
|
137
141
|
|
@@ -140,7 +144,7 @@ module ActiveFacts
|
|
140
144
|
end
|
141
145
|
|
142
146
|
def compile
|
143
|
-
@parser.compile_import(@name, @alias_hash)
|
147
|
+
@parser.compile_import(@name, @import_role, @alias_hash)
|
144
148
|
end
|
145
149
|
end
|
146
150
|
|
@@ -0,0 +1,136 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts CQL Parser.
|
3
|
+
# Compiler classes relating to Transform Rules.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2017 Factil Pty Ltd. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
module ActiveFacts
|
8
|
+
module CQL
|
9
|
+
class Compiler < ActiveFacts::CQL::Parser
|
10
|
+
|
11
|
+
class TransformRule < Definition
|
12
|
+
attr_accessor :compound_matching
|
13
|
+
|
14
|
+
def initialize compound_matching
|
15
|
+
@compound_matching = compound_matching
|
16
|
+
end
|
17
|
+
|
18
|
+
def compile
|
19
|
+
context = CompilationContext.new(@vocabulary)
|
20
|
+
transform_matching = @compound_matching.compile(context)
|
21
|
+
@constellation.TransformRule(:new, :compound_matching => transform_matching)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.build_transform_target_refs context, targ_term_list, transform_rule
|
26
|
+
vocabulary_identifier = context.vocabulary.identifying_role_values
|
27
|
+
constellation = context.vocabulary.constellation
|
28
|
+
|
29
|
+
targ_term_list.flatten!
|
30
|
+
(0 ... targ_term_list.size).each do |idx|
|
31
|
+
ref = targ_term_list[idx]
|
32
|
+
if (target_ot = constellation.ObjectType[[vocabulary_identifier, ref.term]]).nil?
|
33
|
+
raise "Target object '#{ref.term}' of transformation must be a valid object type"
|
34
|
+
end
|
35
|
+
constellation.TransformTargetRef(
|
36
|
+
transform_rule, idx, :object_type => target_ot,
|
37
|
+
:leading_adjective => ref.leading_adjective, :trailing_adjective => ref.trailing_adjective
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class CompoundMatching
|
43
|
+
attr_accessor :targ_term_list, :transform_query, :transform_matchings
|
44
|
+
|
45
|
+
def initialize targ_term_list, transform_query, transform_matchings
|
46
|
+
@targ_term_list = targ_term_list
|
47
|
+
@transform_query = transform_query
|
48
|
+
@transform_matchings = transform_matchings
|
49
|
+
end
|
50
|
+
|
51
|
+
def compile(context)
|
52
|
+
compound_rule = nil
|
53
|
+
vocabulary_identifier = context.vocabulary.identifying_role_values
|
54
|
+
constellation = context.vocabulary.constellation
|
55
|
+
|
56
|
+
source_ot = nil
|
57
|
+
source_query = nil
|
58
|
+
if @transform_query.is_a?(ActiveFacts::CQL::Compiler::Reference)
|
59
|
+
if (source_ot = constellation.ObjectType[[vocabulary_identifier, @transform_query.term]]).nil?
|
60
|
+
raise "Invalid source object '#{@transform_query.term}' for '#{@targ_term.term}' transformation"
|
61
|
+
end
|
62
|
+
elsif @transform_query.is_a?(Array)
|
63
|
+
query = Query.new(nil, @transform_query.flatten)
|
64
|
+
query.constellation = constellation
|
65
|
+
query.vocabulary = context.vocabulary
|
66
|
+
if (source_query = query.compile).nil?
|
67
|
+
raise "Invalid source query for '#{@targ_term.term}' transformation"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
compound_rule = constellation.CompoundMatching(
|
72
|
+
:new, :source_object_type => source_ot, :source_query => source_query
|
73
|
+
)
|
74
|
+
ActiveFacts::CQL::Compiler.build_transform_target_refs(context, @targ_term_list, compound_rule)
|
75
|
+
|
76
|
+
@transform_matchings.each do |tr|
|
77
|
+
trule = tr.compile(context)
|
78
|
+
trule.compound_matching = compound_rule
|
79
|
+
end
|
80
|
+
|
81
|
+
compound_rule
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class SimpleMatching
|
86
|
+
attr_accessor :targ_term_list, :transform_expr
|
87
|
+
|
88
|
+
def initialize targ_term_list, transform_expr
|
89
|
+
@targ_term_list = targ_term_list
|
90
|
+
@transform_expr = transform_expr
|
91
|
+
end
|
92
|
+
|
93
|
+
def compile(context)
|
94
|
+
vocabulary_identifier = context.vocabulary.identifying_role_values
|
95
|
+
constellation = context.vocabulary.constellation
|
96
|
+
|
97
|
+
expr = transform_expr ? transform_expr.compile(context) : nil
|
98
|
+
simple_rule = constellation.SimpleMatching(:new, :expression => expr)
|
99
|
+
ActiveFacts::CQL::Compiler.build_transform_target_refs(context, @targ_term_list, simple_rule)
|
100
|
+
|
101
|
+
simple_rule
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class ExpressionTermList
|
106
|
+
attr_accessor :term_list
|
107
|
+
|
108
|
+
def initialize term_list
|
109
|
+
@term_list = term_list
|
110
|
+
end
|
111
|
+
|
112
|
+
def compile(context)
|
113
|
+
vocabulary_identifier = context.vocabulary.identifying_role_values
|
114
|
+
constellation = context.vocabulary.constellation
|
115
|
+
|
116
|
+
expression = context.vocabulary.constellation.Expression(:new, :expression_type => 'Role')
|
117
|
+
|
118
|
+
@term_list.flatten!
|
119
|
+
(0 ... @term_list.size).each do |idx|
|
120
|
+
ref = term_list[idx]
|
121
|
+
if (object_type = constellation.ObjectType[[vocabulary_identifier, ref.term]]).nil?
|
122
|
+
raise "Object '#{ref.term}' of transformation must be a valid object type"
|
123
|
+
end
|
124
|
+
constellation.ExpressionObjectRef(
|
125
|
+
expression, idx, :object_type => object_type,
|
126
|
+
:leading_adjective => ref.leading_adjective, :trailing_adjective => ref.trailing_adjective
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
expression
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -119,6 +119,8 @@ module ActiveFacts
|
|
119
119
|
@term = @term_part if t[@term_part]
|
120
120
|
@global_term = (t = t[@term_part]) == true ? @term_part : t
|
121
121
|
trace :context, "saving context #{@term}/#{@global_term}"
|
122
|
+
# trace :context, "@terms =\n\t#{@terms.map{|k,v| "#{k} => #{v}"} * "\n\t"}"
|
123
|
+
# trace :context, "@role_names =\n\t#{@role_names.map{|k,v| "#{k} => #{v}"} * "\n\t"}"
|
122
124
|
@context_saver.context = {:term => @term, :global_term => @global_term }
|
123
125
|
end
|
124
126
|
end
|
@@ -128,8 +130,49 @@ module ActiveFacts
|
|
128
130
|
def term_complete?
|
129
131
|
return true if @allowed_forward_terms.include?(@term)
|
130
132
|
return true if system_term(@term)
|
131
|
-
(t = @terms[@term] and t[@term]) or
|
132
|
-
|
133
|
+
result = ((t = @terms[@term] and t[@term]) or (t = @role_names[@term] and t[@term]))
|
134
|
+
trace :context, "term #{@term} is #{result ? '' : 'in'}complete"
|
135
|
+
result
|
136
|
+
end
|
137
|
+
|
138
|
+
def global_term_starts?(s, context_saver)
|
139
|
+
@term = @global_term = nil
|
140
|
+
|
141
|
+
@term_part = s
|
142
|
+
@context_saver = context_saver
|
143
|
+
t = @terms[s] || system_term(s)
|
144
|
+
if t
|
145
|
+
# s is a prefix of the keys of t.
|
146
|
+
if t[s]
|
147
|
+
@global_term = @term = @term_part
|
148
|
+
@context_saver.context = {:term => @term, :global_term => @global_term }
|
149
|
+
end
|
150
|
+
trace :context, "Term #{t[s] ? "is" : "starts"} '#{@term_part}'"
|
151
|
+
elsif @allowed_forward_terms.include?(@term_part)
|
152
|
+
@term = @term_part
|
153
|
+
@context_saver.context = {:term => @term, :global_term => @term }
|
154
|
+
trace :context, "Term #{s} is an allowed forward"
|
155
|
+
return true
|
156
|
+
end
|
157
|
+
t
|
158
|
+
end
|
159
|
+
|
160
|
+
def global_term_continues?(s)
|
161
|
+
@term_part = "#{@term_part} #{s}"
|
162
|
+
t = @terms[@term_part]
|
163
|
+
if t
|
164
|
+
trace :context, "Multi-word term #{t[@term_part] ? 'ends at' : 'continues to'} #{@term_part.inspect}"
|
165
|
+
|
166
|
+
# Record the name of the full term and the underlying global term:
|
167
|
+
if t[@term_part]
|
168
|
+
@term = @term_part if t[@term_part]
|
169
|
+
@global_term = (t = t[@term_part]) == true ? @term_part : t
|
170
|
+
trace :context, "saving context #{@term}/#{@global_term}"
|
171
|
+
# trace :context, "@terms =\n\t#{@terms.map{|k,v| "#{k} => #{v}"} * "\n\t"}"
|
172
|
+
@context_saver.context = {:term => @term, :global_term => @global_term }
|
173
|
+
end
|
174
|
+
end
|
175
|
+
t
|
133
176
|
end
|
134
177
|
|
135
178
|
def system_term(s)
|
@@ -11,6 +11,7 @@ require 'activefacts/cql/parser/Terms'
|
|
11
11
|
require 'activefacts/cql/parser/ObjectTypes'
|
12
12
|
require 'activefacts/cql/parser/ValueTypes'
|
13
13
|
require 'activefacts/cql/parser/FactTypes'
|
14
|
+
require 'activefacts/cql/parser/TransformRules'
|
14
15
|
require 'activefacts/cql/parser/Context'
|
15
16
|
|
16
17
|
module ActiveFacts
|
@@ -22,6 +23,7 @@ module ActiveFacts
|
|
22
23
|
include ObjectTypes
|
23
24
|
include ValueTypes
|
24
25
|
include FactTypes
|
26
|
+
include TransformRules
|
25
27
|
include Context
|
26
28
|
|
27
29
|
rule cql_file
|
@@ -58,20 +60,35 @@ module ActiveFacts
|
|
58
60
|
/ object_type
|
59
61
|
/ informal_description
|
60
62
|
/ query
|
63
|
+
/ transform_rule
|
61
64
|
/ s ';' s { def ast; nil; end }
|
62
65
|
end
|
63
66
|
|
64
67
|
rule vocabulary_definition
|
65
|
-
|
68
|
+
schema_definition /
|
69
|
+
transform_definition
|
70
|
+
end
|
71
|
+
|
72
|
+
rule schema_definition
|
73
|
+
s ( schema / topic / vocabulary ) S vocabulary_name vn:version_number? s ';'
|
74
|
+
{
|
75
|
+
def ast
|
76
|
+
Compiler::Vocabulary.new(vocabulary_name.value, false, vn.empty? ? nil : vn.value)
|
77
|
+
end
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
rule transform_definition
|
82
|
+
s transform S vocabulary_name vn:version_number? s ';'
|
66
83
|
{
|
67
84
|
def ast
|
68
|
-
Compiler::Vocabulary.new(vocabulary_name.value)
|
85
|
+
Compiler::Vocabulary.new(vocabulary_name.value, true, vn.empty? ? nil : vn.value)
|
69
86
|
end
|
70
87
|
}
|
71
88
|
end
|
72
89
|
|
73
90
|
rule vocabulary_name
|
74
|
-
id tail:(S id)*
|
91
|
+
id tail:(S !version id)*
|
75
92
|
{
|
76
93
|
def node_type; :vocabulary; end
|
77
94
|
def value
|
@@ -81,10 +98,47 @@ module ActiveFacts
|
|
81
98
|
end
|
82
99
|
|
83
100
|
rule import_definition
|
84
|
-
s import S vocabulary_name alias_list ';'
|
101
|
+
s import i:import_role? S vocabulary_name vp:version_pattern? alias_list ';'
|
85
102
|
{
|
86
103
|
def ast
|
87
|
-
Compiler::Import.new(
|
104
|
+
Compiler::Import.new(
|
105
|
+
import.input.parser, vocabulary_name.value, i.empty? ? "topic" : i.value, vp.empty? ? nil : vp.value, alias_list.value
|
106
|
+
)
|
107
|
+
end
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
rule version_number
|
112
|
+
S version S version_number_string
|
113
|
+
{
|
114
|
+
def value
|
115
|
+
version_number_string.text_value
|
116
|
+
end
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
rule version_pattern
|
121
|
+
S version S version_pattern_string
|
122
|
+
{
|
123
|
+
def value
|
124
|
+
version_pattern_string.text_value
|
125
|
+
end
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
rule version_number_string
|
130
|
+
[0-9]+ '.' [0-9]+ '.' [0-9]+ ('-' [0-9A-Za-z-]+ ('.' [0-9A-Za-z-]+ )* )?
|
131
|
+
end
|
132
|
+
|
133
|
+
rule version_pattern_string
|
134
|
+
[0-9]+ ('.' [0-9]+ ('.' [0-9]+ ('-' [0-9A-Za-z-]+ ('.' [0-9A-Za-z-]+ )* )? )? )?
|
135
|
+
end
|
136
|
+
|
137
|
+
rule import_role
|
138
|
+
S id
|
139
|
+
{
|
140
|
+
def value
|
141
|
+
id.text_value
|
88
142
|
end
|
89
143
|
}
|
90
144
|
end
|
@@ -295,6 +295,7 @@ module ActiveFacts
|
|
295
295
|
rule radix_point '.' end
|
296
296
|
rule reflexive 'reflexive' !alphanumeric end
|
297
297
|
rule returning 'returning' !alphanumeric end
|
298
|
+
rule schema 'schema' !alphanumeric end
|
298
299
|
rule separate 'separate' !alphanumeric end
|
299
300
|
rule so_that 'so' S that end
|
300
301
|
rule static 'static' !alphanumeric end
|
@@ -304,10 +305,13 @@ module ActiveFacts
|
|
304
305
|
rule then 'then' !alphanumeric end
|
305
306
|
rule to 'to' !alphanumeric end
|
306
307
|
rule to_avoid to s 'avoid' !alphanumeric end
|
308
|
+
rule topic 'topic' !alphanumeric end
|
309
|
+
rule transform 'transform' !alphanumeric end
|
307
310
|
rule transient 'transient' !alphanumeric end
|
308
311
|
rule transitive 'transitive' !alphanumeric end
|
309
312
|
rule true 'true' !alphanumeric end
|
310
|
-
rule
|
313
|
+
rule version 'version' !alphanumeric end
|
314
|
+
rule vocabulary 'vocabulary' !alphanumeric end
|
311
315
|
rule when 'when' !alphanumeric end
|
312
316
|
rule where 'where' !alphanumeric end
|
313
317
|
rule which 'which' !alphanumeric end
|