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.
Files changed (60) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +15 -0
  3. data/.idea/vcs.xml +6 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +5 -0
  6. data/.travis.yml +7 -0
  7. data/CHANGELOG.md +4 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +9 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +52 -0
  12. data/Rakefile +6 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/grammar_tests/1.modl +1 -0
  16. data/grammar_tests/2.modl +1 -0
  17. data/grammar_tests/3.modl +1 -0
  18. data/grammar_tests/a.modl +1 -0
  19. data/grammar_tests/b.modl +1 -0
  20. data/grammar_tests/base_tests.json +996 -0
  21. data/grammar_tests/c.modl +1 -0
  22. data/grammar_tests/demo_config.modl +9 -0
  23. data/grammar_tests/error_tests.json +70 -0
  24. data/grammar_tests/import_config.modl +9 -0
  25. data/grammar_tests/test_import_dir/nested_import1.txt +1 -0
  26. data/grammar_tests/test_import_dir/nested_import2.txt +1 -0
  27. data/grammar_tests/test_import_dir/nested_import3.txt +1 -0
  28. data/grammar_tests/test_import_dir/test_import.txt +9 -0
  29. data/lib/modl/interpreter.rb +10 -0
  30. data/lib/modl/parser/MODLLexer.interp +136 -0
  31. data/lib/modl/parser/MODLLexer.rb +324 -0
  32. data/lib/modl/parser/MODLLexer.tokens +41 -0
  33. data/lib/modl/parser/MODLParser.interp +95 -0
  34. data/lib/modl/parser/MODLParser.rb +2504 -0
  35. data/lib/modl/parser/MODLParser.tokens +41 -0
  36. data/lib/modl/parser/MODLParserBaseListener.rb +164 -0
  37. data/lib/modl/parser/MODLParserBaseVisitor.rb +107 -0
  38. data/lib/modl/parser/MODLParserListener.rb +151 -0
  39. data/lib/modl/parser/MODLParserVisitor.rb +56 -0
  40. data/lib/modl/parser/class_processor.rb +159 -0
  41. data/lib/modl/parser/evaluator.rb +164 -0
  42. data/lib/modl/parser/file_importer.rb +64 -0
  43. data/lib/modl/parser/global_parse_context.rb +249 -0
  44. data/lib/modl/parser/instruction_processor.rb +58 -0
  45. data/lib/modl/parser/interpreter.rb +38 -0
  46. data/lib/modl/parser/modl_class.rb +102 -0
  47. data/lib/modl/parser/modl_index.rb +30 -0
  48. data/lib/modl/parser/modl_keylist.rb +43 -0
  49. data/lib/modl/parser/modl_method.rb +132 -0
  50. data/lib/modl/parser/object_cache.rb +54 -0
  51. data/lib/modl/parser/parsed.rb +1410 -0
  52. data/lib/modl/parser/parser.rb +42 -0
  53. data/lib/modl/parser/ref_processor.rb +139 -0
  54. data/lib/modl/parser/substitutions.rb +67 -0
  55. data/lib/modl/parser/sutil.rb +78 -0
  56. data/lib/modl/parser/throwing_error_listener.rb +20 -0
  57. data/lib/modl/parser/version.rb +5 -0
  58. data/modl.gemspec +32 -0
  59. metadata +138 -11
  60. 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