modl 0.3.26 → 0.3.27

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -149
  3. data/Gemfile +4 -2
  4. data/LICENSE.txt +1 -1
  5. data/README.md +19 -11
  6. data/Rakefile +5 -3
  7. data/lib/modl/interpreter.rb +38 -0
  8. data/lib/modl/model/model.rb +264 -0
  9. data/lib/modl/parser/parser.rb +272 -59
  10. data/lib/modl/tokeniser/context.rb +113 -0
  11. data/lib/modl/tokeniser/tokeniser.rb +28 -0
  12. data/lib/modl/util/functions.rb +74 -0
  13. data/lib/modl/util/unicode.rb +44 -0
  14. data/lib/modl/version.rb +5 -0
  15. data/lib/modl.rb +7 -32
  16. data/modl.gemspec +8 -11
  17. metadata +16 -75
  18. data/.DS_Store +0 -0
  19. data/.idea/vcs.xml +0 -6
  20. data/.rspec +0 -3
  21. data/.rubocop.yml +0 -5
  22. data/.travis.yml +0 -7
  23. data/bin/console +0 -14
  24. data/bin/setup +0 -8
  25. data/lib/modl/parser/MODLLexer.interp +0 -132
  26. data/lib/modl/parser/MODLLexer.rb +0 -324
  27. data/lib/modl/parser/MODLLexer.tokens +0 -40
  28. data/lib/modl/parser/MODLParser.interp +0 -93
  29. data/lib/modl/parser/MODLParser.rb +0 -2492
  30. data/lib/modl/parser/MODLParser.tokens +0 -40
  31. data/lib/modl/parser/MODLParserBaseListener.rb +0 -164
  32. data/lib/modl/parser/MODLParserBaseVisitor.rb +0 -107
  33. data/lib/modl/parser/MODLParserListener.rb +0 -151
  34. data/lib/modl/parser/MODLParserVisitor.rb +0 -56
  35. data/lib/modl/parser/class_processor.rb +0 -411
  36. data/lib/modl/parser/evaluator.rb +0 -125
  37. data/lib/modl/parser/file_importer.rb +0 -101
  38. data/lib/modl/parser/global_parse_context.rb +0 -318
  39. data/lib/modl/parser/instruction_processor.rb +0 -82
  40. data/lib/modl/parser/interpreter.rb +0 -75
  41. data/lib/modl/parser/modl_class.rb +0 -138
  42. data/lib/modl/parser/modl_index.rb +0 -54
  43. data/lib/modl/parser/modl_keylist.rb +0 -81
  44. data/lib/modl/parser/modl_method.rb +0 -172
  45. data/lib/modl/parser/object_cache.rb +0 -88
  46. data/lib/modl/parser/orphan_handler.rb +0 -98
  47. data/lib/modl/parser/parsed.rb +0 -1469
  48. data/lib/modl/parser/ref_processor.rb +0 -258
  49. data/lib/modl/parser/substitutions.rb +0 -101
  50. data/lib/modl/parser/sutil.rb +0 -108
  51. data/lib/modl/parser/throwing_error_listener.rb +0 -44
  52. data/lib/modl/parser/unicode_escape_replacer.rb +0 -148
  53. data/lib/modl/parser/unicode_escapes.rb +0 -112
  54. data/lib/modl/parser/version.rb +0 -29
@@ -1,81 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # The MIT License (MIT)
4
- #
5
- # Copyright (c) 2019 NUM Technology Ltd
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the "Software"), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in
15
- # all copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- # THE SOFTWARE.
24
-
25
- module MODL
26
- module Parser
27
- # Extracts an index definition from a ParsedPair
28
- class KeylistExtractor
29
- def self.extract(pair, item)
30
- # the item must be an array of arrays
31
- pair.key_lists = []
32
- last_keylist_len = 0
33
- if item.is_a?(Parsed::ParsedValueItem) && item.value.is_a?(Parsed::ParsedValue) && item.value.array
34
- item.value.array.abstractArrayItems.each do |avi|
35
- key_list = []
36
- avi.arrayValueItem.array.abstractArrayItems.each do |key|
37
- key_list << key.arrayValueItem.primitive.string.string if key.arrayValueItem.primitive.string
38
- key_list << key.arrayValueItem.primitive.number.num if key.arrayValueItem.primitive.number
39
- end
40
- if key_list.length > last_keylist_len
41
- last_keylist_len = key_list.length
42
- else
43
- raise InterpreterError, 'Error: Key lists in *assign are not in ascending order of list length: ' + key_list.to_s
44
- end
45
- pair.key_lists << key_list
46
- end
47
- elsif item.is_a?(Parsed::ParsedArray)
48
- item.abstractArrayItems.each do |avi|
49
- key_list = []
50
- avi.arrayValueItem.array.abstractArrayItems.each do |key|
51
- key_list << key.arrayValueItem.primitive.string.string if key.arrayValueItem.primitive.string
52
- key_list << key.arrayValueItem.primitive.number.num if key.arrayValueItem.primitive.number
53
- end
54
- if key_list.length > last_keylist_len
55
- last_keylist_len = key_list.length
56
- else
57
- raise InterpreterError, 'Error: Key lists in *assign are not in ascending order of list length.'
58
- end
59
- pair.key_lists << key_list
60
- end
61
- elsif item.is_a?(Parsed::ParsedValueItem) && !item.value.nbArray.nil?
62
- item.value.nbArray.arrayItems.each do |avi|
63
- key_list = []
64
- avi.arrayValueItem.array.abstractArrayItems.each do |key|
65
- key_list << key.arrayValueItem.primitive.string.string if key.arrayValueItem.primitive.string
66
- key_list << key.arrayValueItem.primitive.number.num if key.arrayValueItem.primitive.number
67
- end
68
- if key_list.length > last_keylist_len
69
- last_keylist_len = key_list.length
70
- else
71
- raise InterpreterError, 'Error: Key lists in *assign are not in ascending order of list length.'
72
- end
73
- pair.key_lists << key_list
74
- end
75
- else
76
- raise InterpreterError, 'Array of arrays expected for: ' + pair.key
77
- end
78
- end
79
- end
80
- end
81
- end
@@ -1,172 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # The MIT License (MIT)
4
- #
5
- # Copyright (c) 2019 NUM Technology Ltd
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the "Software"), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in
15
- # all copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- # THE SOFTWARE.
24
-
25
- module MODL
26
- module Parser
27
- # Represents a *method defined by a MODL document.
28
- class MODLMethod
29
- attr_accessor :id
30
- attr_accessor :name
31
- attr_accessor :transform
32
-
33
- def name_or_id
34
- @name.nil? ? @id : @name
35
- end
36
-
37
- # There is a user-defined method transform to run on the str
38
- def run(str)
39
- # Consume the elements of the transform spec until there are none left.
40
- transform = @transform
41
- while transform && transform.length > 0
42
- if transform.start_with?('replace<') || transform.start_with?('r<')
43
- close_bracket = transform.index('>')
44
- m = Sutil.head(transform, close_bracket + 1).sub!('replace', 'r')
45
- str = StandardMethods.run_method(m, str)
46
- # Consume the subst clause
47
- close_bracket = transform.index('>')
48
- transform = Sutil.tail(transform, close_bracket + 2)
49
- elsif transform.start_with?('trim') || transform.start_with?('t<')
50
- close_bracket = transform.index('>')
51
- m = Sutil.head(transform, close_bracket + 1).sub!('trim', 't')
52
- str = StandardMethods.run_method(m, str)
53
- # Consume the trunc clause
54
- close_bracket = transform.index('>')
55
- transform = Sutil.tail(transform, close_bracket + 2)
56
- elsif transform.start_with?('initcap') || transform.start_with?('i')
57
- str = StandardMethods.run_method('i', str)
58
- transform = Sutil.after(transform, '.')
59
- elsif transform.start_with?('upcase') || transform.start_with?('u')
60
- str = StandardMethods.run_method('u', str)
61
- transform = Sutil.after(transform, '.')
62
- elsif transform.start_with?('downcase') || transform.start_with?('d')
63
- str = StandardMethods.run_method('d', str)
64
- transform = Sutil.after(transform, '.')
65
- elsif transform.start_with?('sentence') || transform.start_with?('s')
66
- str = StandardMethods.run_method('s', str)
67
- transform = Sutil.after(transform, '.')
68
- elsif transform.start_with?('urlencode') || transform.start_with?('e')
69
- str = StandardMethods.run_method('e', str)
70
- transform = Sutil.after(transform, '.')
71
- else
72
- raise InterpreterError, 'NOT IMPLEMENTED'
73
- end
74
- end
75
- str
76
- end
77
-
78
- end
79
-
80
- # Extracts a method definition from a ParsedPair
81
- class MethodExtractor
82
- def self.extract(pair, global)
83
- return unless pair.type == 'method'
84
-
85
- mthd = MODLMethod.new
86
- map = pair.map if pair.map
87
- map = pair.valueItem&.value&.map if pair.valueItem&.value&.map
88
-
89
- map.mapItems.each do |item|
90
- next unless item&.pair&.type
91
-
92
- case item&.pair&.type
93
- when 'id'
94
- mthd.id = item.pair.valueItem.value.primitive.string.string
95
- when 'transform'
96
- mthd.transform = item.pair.valueItem.value.primitive.string.string
97
- when 'name'
98
- mthd.name = item.pair.valueItem.value.primitive.string.string
99
- else
100
- raise InterpreterError, 'Invalid *method - only *id, *name, and *transform fields expected'
101
- end
102
- end
103
-
104
- raise InterpreterError, 'Missing id for method' if mthd.id.nil?
105
- raise InterpreterError, 'Missing name for method' if mthd.name.nil?
106
- raise InterpreterError, 'Duplicate method name or id: ' + mthd.name if global.has_user_method?(mthd.name)
107
- raise InterpreterError, 'Duplicate method name or id: ' + mthd.id if global.has_user_method?(mthd.id)
108
-
109
- # store the methods by id and name to make them easier to find later
110
- global.user_method_id(mthd.id, mthd)
111
- global.user_method(mthd.name, mthd)
112
- end
113
- end
114
-
115
- class StandardMethods
116
-
117
- @@mthd_names = %w(u d i s e r t p upcase downcase initcap sentence urlencode replace trim punydecode)
118
-
119
- def self.run_method(mthd, str)
120
- m = mthd.match(/\w*/)[0]
121
- case m
122
- when 'u', 'upcase'
123
- str.upcase
124
- when 'd', 'downcase'
125
- str.downcase
126
- when 'i', 'initcap'
127
- str.split.map(&:capitalize) * ' '
128
- when 's', 'sentence'
129
- split = str.split
130
- split[0].capitalize!
131
- split.join(' ')
132
- when 'e', 'urlencode'
133
- CGI.escape(str)
134
- when 'r', 'replace'
135
- s1, s2 = get_subst_parts(mthd)
136
- str.gsub(s1, s2)
137
- when 't', 'trim'
138
- s1 = extract_params mthd
139
- i = str.index(s1)
140
- Sutil.head(str, i)
141
- when 'p', 'punydecode'
142
- Punycode.decode(str)
143
- else
144
- str.nil? ? '.' + mthd : str.to_s + '.' + mthd
145
- end
146
- end
147
-
148
- # Extract the method parameters
149
- def self.get_subst_parts(s)
150
- # should be of the form .r(s1,s2)
151
- result = extract_params(s).split(',')
152
- result[0] = '' if result.length.zero? || result[0].nil?
153
- result[1] = '' if result.length == 1 || result[1].nil?
154
- result
155
- end
156
-
157
- # Extract the method parameter
158
- def self.extract_params(str)
159
- # should be of the form .r(s1,s2)
160
- Sutil.between(str, '<', '>')
161
- end
162
-
163
- def self.valid_method?(mthd)
164
- m = mthd
165
- if m.include?('<')
166
- m = Sutil.until(m, '<')
167
- end
168
- return @@mthd_names.include?(m)
169
- end
170
- end
171
- end
172
- end
@@ -1,88 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # The MIT License (MIT)
4
- #
5
- # Copyright (c) 2019 NUM Technology Ltd
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the "Software"), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in
15
- # all copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- # THE SOFTWARE.
24
-
25
- module MODL
26
- module Parser
27
- # Store any files for up to 1 hour by default.
28
- class ObjectCache
29
- # A cache record to keep track of the time since an object was last cached.
30
- class CacheEntry
31
- TTL_ONE_HOUR = 3_600 # seconds
32
-
33
- attr_reader :object
34
-
35
- # Initialiase the CacheEntry with an object and an optional ttl in seconds (default 1 hour)
36
- def initialize(object, ttl = nil)
37
- ttl = TTL_ONE_HOUR if ttl.nil?
38
- @object = object
39
- @expiry_time = Time.now + ttl
40
- end
41
-
42
- # Check whether the CacheEntry is live
43
- def expired?
44
- @expiry_time < Time.now
45
- end
46
- end
47
-
48
- # Set up and empty cache.
49
- def initialize
50
- @cache = {}
51
- end
52
-
53
- # Cache an object with the given key and optional ttl in seconds (default 1 hour)
54
- def put(key, object, ttl = nil)
55
- @cache[key] = CacheEntry.new(object, ttl) unless key.nil? || object.nil?
56
- end
57
-
58
- # Evict a cache entry
59
- def evict(key)
60
- @cache.delete(key) unless key.nil?
61
- end
62
-
63
- # Return the object with the given key if one exists and has not expired.
64
- def get(key)
65
- # Return nothing if not in the cache or it has expired.
66
- return if key.nil?
67
-
68
- entry = @cache[key]
69
- return unless entry
70
- return if entry.expired?
71
-
72
- # Otherwise return the cached object.
73
- # We don't delete the cached entry because we might need to force its use if its expired and offline
74
- entry.object
75
- end
76
-
77
- # If the file is offline this can be used to retrieve the cached version if we have one.
78
- def force_get(key)
79
- # Return nothing if not in the cache or it has expired.
80
- return if key.nil?
81
-
82
- entry = @cache[key]
83
- return unless entry
84
- entry.object
85
- end
86
- end
87
- end
88
- end
@@ -1,98 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # The MIT License (MIT)
4
- #
5
- # Copyright (c) 2019 NUM Technology Ltd
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the "Software"), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in
15
- # all copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- # THE SOFTWARE.
24
-
25
- module MODL
26
- module Parser
27
- class OrphanHandler
28
- #
29
- # Return true if all strings start with '_'
30
- #
31
- def self.all_hidden(str_array)
32
- if str_array && str_array.length > 0
33
- str_array.each do |s|
34
- return false unless s.start_with?('_')
35
- end
36
- end
37
- true
38
- end
39
-
40
- #
41
- # Look for any orphan pairs at the top level and adopt them into a map
42
- # Its an error if there are duplicate keys or mixed types at the top.
43
- #
44
- def self.adopt(global, structures)
45
- #
46
- # Separate out any top-level pairs into a separate hash, checking for duplicates on the way.
47
- #
48
- if structures
49
- pairs = Hash.new
50
-
51
- # This will replace the existing structures array
52
- new_structures = []
53
-
54
- structures.each do |s|
55
- if s.pair
56
- # skip hidden pairs and instructions
57
- if s.pair.key.start_with?('*') || s.pair.key.start_with?('_') || s.pair.key == '?'
58
- new_structures.push(s)
59
- next
60
- end
61
-
62
- if pairs.has_key?(s.pair.key)
63
- raise InterpreterError, 'Duplicate top level keys are not allowed.'
64
- else
65
- pairs[s.pair.key] = s
66
- end
67
- else
68
- if pairs.length > 0 && !all_hidden(pairs.keys) && !s.top_level_conditional
69
- raise InterpreterError, 'Mixed top-level types are not allowed.'
70
- else
71
- new_structures.push(s)
72
- end
73
- end
74
- end
75
-
76
- if pairs.length > 0
77
- #
78
- # Create a map for the pairs and insert them into it.
79
- #
80
- new_map = MODL::Parser::Parsed::ParsedMap.new(global)
81
- pairs.values.each do |p|
82
- new_map.mapItems.push p unless p.pair.key.start_with?('_')
83
- end
84
-
85
- # Add the map to a new structure and insert it at the front of the structures list
86
- new_struct = MODL::Parser::Parsed::ParsedStructure.new(global)
87
- new_struct.map = new_map
88
- new_structures.unshift(new_struct)
89
-
90
- # Replace the existing structures with the new structures.
91
- return new_structures
92
- end
93
- end
94
- structures
95
- end
96
- end
97
- end
98
- end