modl 0.0.2 → 0.3.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 +5 -5
- data/.gitignore +15 -0
- data/.idea/vcs.xml +6 -0
- data/.rspec +3 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +4 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +52 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/grammar_tests/1.modl +1 -0
- data/grammar_tests/2.modl +1 -0
- data/grammar_tests/3.modl +1 -0
- data/grammar_tests/a.modl +1 -0
- data/grammar_tests/b.modl +1 -0
- data/grammar_tests/base_tests.json +996 -0
- data/grammar_tests/c.modl +1 -0
- data/grammar_tests/demo_config.modl +9 -0
- data/grammar_tests/error_tests.json +70 -0
- data/grammar_tests/import_config.modl +9 -0
- data/grammar_tests/test_import_dir/nested_import1.txt +1 -0
- data/grammar_tests/test_import_dir/nested_import2.txt +1 -0
- data/grammar_tests/test_import_dir/nested_import3.txt +1 -0
- data/grammar_tests/test_import_dir/test_import.txt +9 -0
- data/lib/modl/interpreter.rb +10 -0
- data/lib/modl/parser/MODLLexer.interp +136 -0
- data/lib/modl/parser/MODLLexer.rb +324 -0
- data/lib/modl/parser/MODLLexer.tokens +41 -0
- data/lib/modl/parser/MODLParser.interp +95 -0
- data/lib/modl/parser/MODLParser.rb +2504 -0
- data/lib/modl/parser/MODLParser.tokens +41 -0
- data/lib/modl/parser/MODLParserBaseListener.rb +164 -0
- data/lib/modl/parser/MODLParserBaseVisitor.rb +107 -0
- data/lib/modl/parser/MODLParserListener.rb +151 -0
- data/lib/modl/parser/MODLParserVisitor.rb +56 -0
- data/lib/modl/parser/class_processor.rb +159 -0
- data/lib/modl/parser/evaluator.rb +164 -0
- data/lib/modl/parser/file_importer.rb +64 -0
- data/lib/modl/parser/global_parse_context.rb +249 -0
- data/lib/modl/parser/instruction_processor.rb +58 -0
- data/lib/modl/parser/interpreter.rb +38 -0
- data/lib/modl/parser/modl_class.rb +102 -0
- data/lib/modl/parser/modl_index.rb +30 -0
- data/lib/modl/parser/modl_keylist.rb +43 -0
- data/lib/modl/parser/modl_method.rb +132 -0
- data/lib/modl/parser/object_cache.rb +54 -0
- data/lib/modl/parser/parsed.rb +1410 -0
- data/lib/modl/parser/parser.rb +42 -0
- data/lib/modl/parser/ref_processor.rb +139 -0
- data/lib/modl/parser/substitutions.rb +67 -0
- data/lib/modl/parser/sutil.rb +78 -0
- data/lib/modl/parser/throwing_error_listener.rb +20 -0
- data/lib/modl/parser/version.rb +5 -0
- data/modl.gemspec +32 -0
- metadata +138 -11
- data/lib/modl.rb +0 -5
@@ -0,0 +1,43 @@
|
|
1
|
+
module Modl
|
2
|
+
module Parser
|
3
|
+
# Extracts an index definition from a ParsedPair
|
4
|
+
class KeylistExtractor
|
5
|
+
def self.extract(pair, item)
|
6
|
+
# the item must be an array of arrays
|
7
|
+
pair.key_lists = []
|
8
|
+
last_keylist_len = 0
|
9
|
+
if item.is_a?(Parsed::ParsedValueItem) && item.value.is_a?(Parsed::ParsedValue) && item.value.array
|
10
|
+
item.value.array.abstractArrayItems.each do |avi|
|
11
|
+
key_list = []
|
12
|
+
avi.arrayValueItem.array.abstractArrayItems.each do |key|
|
13
|
+
key_list << key.arrayValueItem.primitive.string.string if key.arrayValueItem.primitive.string
|
14
|
+
key_list << key.arrayValueItem.primitive.number.num if key.arrayValueItem.primitive.number
|
15
|
+
end
|
16
|
+
if key_list.length > last_keylist_len
|
17
|
+
last_keylist_len = key_list.length
|
18
|
+
else
|
19
|
+
raise InterpreterError, 'Error: Key lists in *assign are not in ascending order of list length: ' + key_list.to_s
|
20
|
+
end
|
21
|
+
pair.key_lists << key_list
|
22
|
+
end
|
23
|
+
elsif item.is_a?(Parsed::ParsedArray)
|
24
|
+
item.abstractArrayItems.each do |avi|
|
25
|
+
key_list = []
|
26
|
+
avi.arrayValueItem.array.abstractArrayItems.each do |key|
|
27
|
+
key_list << key.arrayValueItem.primitive.string.string if key.arrayValueItem.primitive.string
|
28
|
+
key_list << key.arrayValueItem.primitive.number.num if key.arrayValueItem.primitive.number
|
29
|
+
end
|
30
|
+
if key_list.length > last_keylist_len
|
31
|
+
last_keylist_len = key_list.length
|
32
|
+
else
|
33
|
+
raise InterpreterError, 'Error: Key lists in *assign are not in ascending order of list length.'
|
34
|
+
end
|
35
|
+
pair.key_lists << key_list
|
36
|
+
end
|
37
|
+
else
|
38
|
+
raise InterpreterError, 'Array of arrays expected for: ' + pair.key
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Modl
|
2
|
+
module Parser
|
3
|
+
# Represents a *method defined by a MODL document.
|
4
|
+
class MODLMethod
|
5
|
+
attr_accessor :id
|
6
|
+
attr_accessor :name
|
7
|
+
attr_accessor :transform
|
8
|
+
|
9
|
+
def name_or_id
|
10
|
+
@name.nil? ? @id : @name
|
11
|
+
end
|
12
|
+
|
13
|
+
# There is a user-defined method transform to run on the str
|
14
|
+
def run(str)
|
15
|
+
# Consume the elements of the transform spec until there are none left.
|
16
|
+
transform = @transform
|
17
|
+
while transform && transform.length > 0
|
18
|
+
if transform.start_with? 'replace'
|
19
|
+
close_bracket = transform.index(')')
|
20
|
+
m = Sutil.head(transform, close_bracket + 1).sub!('replace', 'r')
|
21
|
+
str = StandardMethods.run_method(m, str)
|
22
|
+
# Consume the subst clause
|
23
|
+
close_bracket = transform.index(')')
|
24
|
+
transform = Sutil.tail(transform, close_bracket + 2)
|
25
|
+
elsif transform.start_with? 'trim'
|
26
|
+
close_bracket = transform.index(')')
|
27
|
+
m = Sutil.head(transform, close_bracket + 1).sub!('trim', 't')
|
28
|
+
str = StandardMethods.run_method(m, str)
|
29
|
+
# Consume the trunc clause
|
30
|
+
close_bracket = transform.index(')')
|
31
|
+
transform = Sutil.tail(transform, close_bracket + 2)
|
32
|
+
elsif transform.start_with? 'initcap'
|
33
|
+
str = str.split.map(&:capitalize) * ' '
|
34
|
+
transform = Sutil.tail(transform, 8)
|
35
|
+
elsif transform.start_with? 'upcase'
|
36
|
+
raise InterpreterError, 'NOT IMPLEMENTED'
|
37
|
+
elsif transform.start_with? 'downcase'
|
38
|
+
raise InterpreterError, 'NOT IMPLEMENTED'
|
39
|
+
elsif transform.start_with? 'sentence'
|
40
|
+
raise InterpreterError, 'NOT IMPLEMENTED'
|
41
|
+
elsif transform.start_with? 'urlencode'
|
42
|
+
raise InterpreterError, 'NOT IMPLEMENTED'
|
43
|
+
else
|
44
|
+
raise InterpreterError, 'NOT IMPLEMENTED'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
str
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
# Extracts a method definition from a ParsedPair
|
53
|
+
class MethodExtractor
|
54
|
+
def self.extract(pair, global)
|
55
|
+
return unless pair.type == 'method'
|
56
|
+
|
57
|
+
mthd = MODLMethod.new
|
58
|
+
map = pair.map if pair.map
|
59
|
+
map = pair.valueItem&.value&.map if pair.valueItem&.value&.map
|
60
|
+
|
61
|
+
map.mapItems.each do |item|
|
62
|
+
next unless item&.pair&.type
|
63
|
+
|
64
|
+
case item&.pair&.type
|
65
|
+
when 'id'
|
66
|
+
mthd.id = item.pair.valueItem.value.primitive.string.string
|
67
|
+
when 'transform'
|
68
|
+
mthd.transform = item.pair.valueItem.value.primitive.string.string
|
69
|
+
when 'name'
|
70
|
+
mthd.name = item.pair.valueItem.value.primitive.string.string
|
71
|
+
else
|
72
|
+
raise InterpreterError, 'Invalid *method - only *id, *name, and *transform fields expected'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
raise InterpreterError, 'Missing id for method' if mthd.id.nil?
|
77
|
+
raise InterpreterError, 'Missing name for method' if mthd.name.nil?
|
78
|
+
raise InterpreterError, 'Duplicate method name: ' + mthd.name if global.has_user_method?(mthd.name)
|
79
|
+
raise InterpreterError, 'Duplicate method id: ' + mthd.id if global.has_user_method?(mthd.id)
|
80
|
+
|
81
|
+
# store the methods by id and name to make them easier to find later
|
82
|
+
global.user_method_id(mthd.id, mthd)
|
83
|
+
global.user_method(mthd.name, mthd)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class StandardMethods
|
88
|
+
def self.run_method(m, str)
|
89
|
+
case m[0]
|
90
|
+
when 'u'
|
91
|
+
str.upcase
|
92
|
+
when 'd'
|
93
|
+
str.downcase
|
94
|
+
when 'i'
|
95
|
+
str.split.map(&:capitalize) * ' '
|
96
|
+
when 's'
|
97
|
+
split = str.split
|
98
|
+
split[0].capitalize!
|
99
|
+
split.join(' ')
|
100
|
+
when 'e'
|
101
|
+
CGI.escape(str)
|
102
|
+
when 'r'
|
103
|
+
s1, s2 = get_subst_parts m
|
104
|
+
str.sub(s1, s2)
|
105
|
+
when 't'
|
106
|
+
s1 = extract_params m
|
107
|
+
i = str.index(s1)
|
108
|
+
Sutil.head(str, i)
|
109
|
+
when 'p'
|
110
|
+
Punycode.decode(str)
|
111
|
+
else
|
112
|
+
str + '.' + m
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Extract the method parameters
|
117
|
+
def self.get_subst_parts(s)
|
118
|
+
# should be of the form .r(s1,s2)
|
119
|
+
result = extract_params(s).split(',')
|
120
|
+
result[0] = '' if result.length.zero? || result[0].nil?
|
121
|
+
result[1] = '' if result.length == 1 || result[1].nil?
|
122
|
+
result
|
123
|
+
end
|
124
|
+
|
125
|
+
# Extract the method parameter
|
126
|
+
def self.extract_params(str)
|
127
|
+
# should be of the form .r(s1,s2)
|
128
|
+
Sutil.between(str, '(', ')')
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
module Modl
|
3
|
+
module Parser
|
4
|
+
# Store any files for up to 1 hour by default.
|
5
|
+
class ObjectCache
|
6
|
+
# A cache record to keep track of the time since an object was last cached.
|
7
|
+
class CacheEntry
|
8
|
+
TTL_ONE_HOUR = 3_600 # seconds
|
9
|
+
|
10
|
+
attr_reader :object
|
11
|
+
|
12
|
+
# Initialiase the CacheEntry with an object and an optional ttl in seconds (default 1 hour)
|
13
|
+
def initialize(object, ttl = nil)
|
14
|
+
ttl = TTL_ONE_HOUR if ttl.nil?
|
15
|
+
@object = object
|
16
|
+
@expiry_time = Time.now + ttl
|
17
|
+
end
|
18
|
+
|
19
|
+
# Check whether the CacheEntry is live
|
20
|
+
def expired?
|
21
|
+
@expiry_time < Time.now
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Set up and empty cache.
|
26
|
+
def initialize
|
27
|
+
@cache = {}
|
28
|
+
end
|
29
|
+
|
30
|
+
# Cache an object with the given key and optional ttl in seconds (default 1 hour)
|
31
|
+
def put(key, object, ttl = nil)
|
32
|
+
@cache[key] = CacheEntry.new(object, ttl) unless key.nil? || object.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Evict a cache entry
|
36
|
+
def evict(key)
|
37
|
+
@cache.delete(key) unless key.nil?
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return the object with the given key if one exists and has not expired.
|
41
|
+
def get(key)
|
42
|
+
# Return nothing if not in the cache or it has expired.
|
43
|
+
return if key.nil?
|
44
|
+
|
45
|
+
entry = @cache[key]
|
46
|
+
return unless entry
|
47
|
+
return if entry.expired?
|
48
|
+
|
49
|
+
# Otherwise return the cached object.
|
50
|
+
entry.object
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|