posxml_parser 1.3.1 → 2.0.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.
@@ -0,0 +1,23 @@
1
+ module PosxmlCompiler
2
+ class Function
3
+ attr_reader :number, :functions
4
+
5
+ def initialize
6
+ @functions = {}
7
+ @number = 0
8
+ end
9
+
10
+ def get(name)
11
+ return if name.to_s.empty?
12
+ if @functions[name]
13
+ @functions[name]
14
+ else
15
+ @functions[name] = next_number
16
+ end
17
+ end
18
+
19
+ def next_number
20
+ (@number += 1).to_s
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ module PosxmlCompiler
2
+ class InstructionNotFoundError < PosxmlCompilerError
3
+ end
4
+ end
5
+
@@ -0,0 +1,97 @@
1
+ module PosxmlCompiler
2
+ class Jump
3
+ INSTRUCTION_FUNCTION = "function"
4
+ INSTRUCTION_CALLFUNCTION = "callfunction"
5
+ INSTRUCTION_FUNCTION_END = "/function"
6
+
7
+ INSTRUCTION_WHILE = "while"
8
+ INSTRUCTION_BREAK = "break"
9
+ INSTRUCTION_WHILE_END = "/while"
10
+
11
+ INSTRUCTION_IF = "if"
12
+ INSTRUCTION_ELSE = "else"
13
+ INSTRUCTION_IF_END = "/if"
14
+
15
+ attr_reader :instructions, :jumps
16
+
17
+ def initialize(instructions_array)
18
+ @instructions = instructions_array
19
+ @jumps = []
20
+ self.parse
21
+ end
22
+
23
+ def persist!
24
+ self.jumps.each do |jump|
25
+ line = self.instructions[jump.persistence][:line]
26
+ self.instructions[jump.persistence][:line] = line.insert(1, "#{jump.jump_value}\n")
27
+ end
28
+ end
29
+
30
+ def parse
31
+ instructions_indexed = self.instructions.each_with_index.to_a.reverse
32
+ stack_function = []
33
+ stack_while = []
34
+ stack_if = []
35
+
36
+ # It's necessary parse the list of functions first because a functions can
37
+ # be called from anywhere, already declared or not
38
+ tree_function = instructions_indexed.select do |instruction, index|
39
+ instruction[:name] == INSTRUCTION_FUNCTION
40
+ end.group_by do |instruction, index|
41
+ instruction[:parameters]["name"][:original]
42
+ end
43
+
44
+ # instruction[:point] + 1 - Because of "\n"
45
+ instructions_indexed.each do |instruction, index|
46
+ case instruction[:name]
47
+ when INSTRUCTION_FUNCTION_END
48
+ stack_function << [index, instruction[:point]]
49
+ when INSTRUCTION_FUNCTION
50
+ reference, point = stack_function.pop
51
+ @jumps << JumpPoint.new(self, INSTRUCTION_FUNCTION, index, reference, point)
52
+ when INSTRUCTION_CALLFUNCTION
53
+ reference, point = check_function_tree(tree_function, instruction)
54
+ @jumps << JumpPoint.new(self, INSTRUCTION_CALLFUNCTION, index, reference, point)
55
+ when INSTRUCTION_WHILE_END
56
+ stack_while << [index, instruction[:point]]
57
+ when INSTRUCTION_BREAK
58
+ reference, point = stack_while.last
59
+ raise PosxmlCompilerError.new("While not found for <break") unless reference
60
+ @jumps << JumpPoint.new(self, INSTRUCTION_BREAK, index, reference, point)
61
+ when INSTRUCTION_WHILE
62
+ reference, point = stack_while.pop
63
+ @jumps << JumpPoint.new(self, INSTRUCTION_WHILE, index, reference, point)
64
+ @jumps << JumpPoint.new(self, INSTRUCTION_WHILE_END, reference, index, instruction[:point] - 1)
65
+ when INSTRUCTION_IF_END
66
+ stack_if << [index, instruction[:point]]
67
+ when INSTRUCTION_ELSE
68
+ reference, point = stack_if.pop
69
+ @jumps << JumpPoint.new(self, INSTRUCTION_ELSE, index, reference, point)
70
+ stack_if << [index, instruction[:point], true]
71
+ when INSTRUCTION_IF
72
+ reference, point, is_else = stack_if.pop
73
+ @jumps << JumpPoint.new(self, INSTRUCTION_IF, index, reference, point)
74
+ end
75
+ end
76
+ # TODO add treatment for open/end statments
77
+ check_addition
78
+ end
79
+
80
+ private
81
+ def check_addition
82
+ self.jumps.each do |jump|
83
+ jump.check_itself!
84
+ end
85
+ end
86
+
87
+ def check_function_tree(tree, instruction)
88
+ function_name = instruction[:parameters]["name"][:original]
89
+ unless function = tree[function_name]
90
+ raise PosxmlCompilerError.new("Function #{function_name} not found") unless reference
91
+ end
92
+ function_indexed = tree[function_name][0]
93
+ [function_indexed[1], function_indexed[0][:point]]
94
+ end
95
+ end
96
+ end
97
+
@@ -0,0 +1,54 @@
1
+ module PosxmlCompiler
2
+ class JumpPoint
3
+ INSTRUCTION_WHILE = "while"
4
+
5
+ attr_reader :persistence, :reference, :number, :type, :jump, :changed
6
+
7
+ def initialize(jump, type, persistence, reference, number)
8
+ @jump = jump
9
+ @type = type
10
+ @persistence = persistence
11
+ @reference = reference
12
+ @number = number
13
+ end
14
+
15
+ def changed?
16
+ @changed
17
+ end
18
+
19
+ def check_itself!
20
+ chars = (@number.to_s.size + 1) # \n separator between parameters
21
+ # chars sizes already changed and propogate, so 1 char must not included
22
+ chars -= 1 if changed?
23
+ self.check_others(chars)
24
+ end
25
+
26
+ def add(index, value)
27
+ return if index.nil? || value.nil?
28
+ if self.reference > index
29
+ old_number = @number
30
+ @number += value
31
+ check_chars(old_number, @number)
32
+ end
33
+ end
34
+
35
+ def check_others(value)
36
+ self.jump.jumps.each do |jump_point|
37
+ jump_point.add(self.persistence, value)
38
+ end
39
+ end
40
+
41
+ def check_chars(old, new)
42
+ addition = new.to_s.size - old.to_s.size
43
+ if addition > 0
44
+ @changed = true
45
+ check_others(addition)
46
+ end
47
+ end
48
+
49
+ def jump_value
50
+ self.number.to_s
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,182 @@
1
+ # Treat for variables - ok
2
+ # Treat for functions - ok
3
+ # Treat number of variables - ok
4
+ # Treat string size - ok
5
+ # Check if variable is created before execute
6
+ module PosxmlCompiler
7
+ class Parser
8
+ LIMIT_DEFAULT = 32000
9
+
10
+ attr_accessor :text, :xsd, :bytecodes, :variables, :limit
11
+
12
+ def initialize(source, xsd)
13
+ @xsd = xsd
14
+ @text = source
15
+ @variables = PosxmlCompiler::Variable.new(xsd, PosxmlCompiler::Function.new)
16
+ @bytecodes = parse(self.text)
17
+ @limit = LIMIT_DEFAULT
18
+ end
19
+
20
+ def posxml
21
+ jump = PosxmlCompiler::Jump.new(self.bytecodes.dup)
22
+ jump.persist!
23
+ txt = jump.instructions.inject("") { |string, instruction| string << instruction[:line] }
24
+ if txt.size > self.limit
25
+ puts "Application size limit(#{self.limit}) exceed"
26
+ end
27
+ txt
28
+ end
29
+
30
+ private
31
+ # Clean text
32
+ # from:
33
+ # <if variable="$(sKeyTouchScreen)" operator="equalto" value="KEY_CLEAR">
34
+ # <else />
35
+ # </if>
36
+ # to:
37
+ # <if variable="$(sKeyTouchScreen)" operator="equalto" value="KEY_CLEAR"
38
+ #
39
+ # <else
40
+ #
41
+ # </if
42
+ def sanitize(txt)
43
+ txt.gsub("\r\n", "\n").gsub("\n\r", "\n").gsub("><", ">\n<")
44
+ end
45
+
46
+ # Check if an instruction end in the next line, then split by \n
47
+ def split_instructions(txt)
48
+ txt_new = ""
49
+ ignore = false
50
+ txt.split("\n").compact.each do |str|
51
+ line = str.strip
52
+ if line[0..3] == "<!--" || ignore # Check comentaries
53
+ ignore = true
54
+ ignore = false if line.include?("-->")
55
+ elsif line.include?("<!--") # Check end of comentaries
56
+ line_without_comment = line.split("<!--")[0].strip
57
+ txt_new << instruction_check_close(line_without_comment)
58
+ elsif line.include?("<") || line.include?(">")
59
+ txt_new << instruction_check_close(line)
60
+ else !ignore # For middle of instruction between lines
61
+ # Avoid this <pinpad.getpindukptmessage="$(sDisplayMsg)"type="3"pan="$(sPAN)"maxlen="12"variablereturnpin="$(sPIN)"variablereturnksn="$(sKSN)"variablereturn="$(iRet)"
62
+ txt_new << " " if txt_new[-1] != " "
63
+ txt_new << instruction_check_close(line)
64
+ end
65
+ end
66
+ txt_new.split("\n")
67
+ end
68
+
69
+ def instruction_check_close(str)
70
+ if str[-1] == ">"
71
+ "#{str[0..-2]}\n"
72
+ else
73
+ str
74
+ end
75
+ end
76
+
77
+ def parse(txt)
78
+ point = 0
79
+ # Split in lines and remove nil's
80
+ split_instructions(sanitize(txt)).inject([]) do |array, str|
81
+ value = str.strip
82
+ next(array) if value.empty?
83
+ # => "if", {"variable" => "$(sKeyTouchScreen)", "operator" => "equalto", "value" => "KEY_CLEAR"}
84
+ name, parameters = parse_line(value)
85
+
86
+ # => "if", {"variable" => {:original => "$(sKeyTouchScreen)", :value => "$(1)", :type => :string}, "operator" => {:original => "equalto", :value => "equalto", :type => :string}, "value" => {:original => "KEY_CLEAR", :value => "KEY_CLEAR", :type => :string}
87
+ parse_references(name, parameters)
88
+
89
+ line = parse_instruction(name, parameters)
90
+ size = line.size
91
+ array << {
92
+ :name => name, :parameters => parameters, :line => line, :size => size, :point => point
93
+ }
94
+ point += size
95
+ array
96
+ end
97
+ end
98
+
99
+ # Receive:
100
+ # ("if", {"variable" => {:original => "$(sKeyTouchScreen)", :value => "$(1)", :type => :string}, "operator" => {:original => "equalto", :value => "equalto", :type => :string}, "value" => {:original => "KEY_CLEAR", :value => "KEY_CLEAR", :type => :string})
101
+ # Return:
102
+ # "I$(1)\nequalto\nKEY_CLEAR"
103
+ #
104
+ def parse_instruction(name, parameters)
105
+ self.xsd.to_bytecode(name, parameters)
106
+ end
107
+
108
+ # Receive:
109
+ # ("if", {"variable" => "$(sKeyTouchScreen)", "operator" => "equalto", "value" => "KEY_CLEAR"})
110
+ # ("function", {"name" => "dosomething"})
111
+ # Return (changing parameter key value):
112
+ # "if", {"variable" => {:original => "$(sKeyTouchScreen)", :value => "$(1)", :type => :string}, "operator" => {:original => "equalto", :value => "equalto", :type => :string}, "value" => {:original => "KEY_CLEAR", :value => "KEY_CLEAR", :type => :string})
113
+ # "function", {"name" => {:original => "dosomething", :value => "1", :type => :string}})
114
+ #
115
+ def parse_references(name, parameters)
116
+ parameters.each do |param_name, value|
117
+ parameters[param_name] = self.variables.get(name, param_name, value)
118
+ end
119
+ end
120
+
121
+ # Parse line and return instruction name and parameters
122
+ # from:
123
+ # <if variable="$(sKeyTouchScreen)" operator="equalto" value="KEY_CLEAR"
124
+ # to:
125
+ # ["if", {"variable" => "$(sKeyTouchScreen)", "operator" => "equalto", "value" => "KEY_CLEAR"}]
126
+ #
127
+ def parse_line(str)
128
+ command = (str.strip[-1] == "/") ? str.strip[0..-2] : str.strip
129
+ index = command.index(" ")
130
+
131
+ if index
132
+ [command[1..(index - 1)], parse_parameters(command[index..-1])]
133
+ else # Common to instruction without parameters
134
+ [command[1..-1], {}]
135
+ end
136
+ end
137
+
138
+ # Parse only parameters
139
+ # from:
140
+ # variable="$(sKeyTouchScreen)" operator="equalto" value="KEY_CLEAR"
141
+ # to:
142
+ # {"variable" => "$(sKeyTouchScreen)", "operator" => "equalto", "value" => "KEY_CLEAR"}
143
+ #
144
+ def parse_parameters(str)
145
+ value = ""
146
+ variable = ""
147
+ separator = nil
148
+ bVariable = true
149
+ bValue = false
150
+ str.strip.chars.inject({}) do |hash, ch|
151
+ if ch == separator
152
+ separator = nil
153
+ bVariable = true
154
+ bValue = false
155
+ hash[variable.strip] = replace_xml_constants(value)
156
+ variable = ""
157
+ value = ""
158
+
159
+ elsif ! separator && (ch == "'" || ch == "\"")
160
+ separator = ch
161
+ bVariable = false
162
+ bValue = true
163
+
164
+ elsif bVariable && ch == "="
165
+ bVariable = false
166
+
167
+ elsif bValue
168
+ value << ch
169
+
170
+ elsif bVariable
171
+ variable << ch
172
+ end
173
+ hash
174
+ end
175
+ end
176
+
177
+ def replace_xml_constants(str)
178
+ str.gsub("&quot;", "\"").gsub("&lt;", "<").gsub("&gt;", ">").gsub("&amp;", "&")
179
+ end
180
+ end
181
+ end
182
+
@@ -0,0 +1,4 @@
1
+ module PosxmlCompiler
2
+ class PosxmlCompilerError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ module PosxmlCompiler
2
+ class StringSizeError < PosxmlCompilerError
3
+ end
4
+ end
5
+
@@ -0,0 +1,235 @@
1
+ module PosxmlCompiler
2
+ class Variable
3
+ VARIABLE_REFERENCE = "$("
4
+ VARIABLE_STRING_MAX_SIZE = 2048
5
+ VARIABLE_STRING_LIMIT = 256
6
+ VARIABLE_LIMIT = 512
7
+
8
+ FUNCTION_NAME = "function"
9
+ FUNCTION_PARAMETER = "name"
10
+ CALLFUNCTION_NAME = "callfunction"
11
+ VARIABLE_STRING = "stringvariable"
12
+ VARIABLE_INTEGER = "integervariable"
13
+ VARIABLE_PARAMETER_VALUE = "value"
14
+ VARIABLE_PARAMETER_NAME = "variable"
15
+
16
+ attr_accessor :number_strings, :variables, :number, :xsd, :functions
17
+
18
+ def initialize(xsd, functions)
19
+ @functions = functions
20
+ @number = 0
21
+ @number_strings = 0
22
+ @variables = {}
23
+ @xsd = xsd
24
+ end
25
+
26
+ def check_typing_mismatch(instruction, parameter_name, variable_struct)
27
+ type = self.xsd.parameter_type(instruction, parameter_name)
28
+
29
+ return true if type == :string_or_integer
30
+
31
+ case variable_struct[:type]
32
+ when type # match everything is ok
33
+ when :string_or_integer # match everything is ok
34
+ when nil # not defined yet, it's ok for now
35
+ when :string
36
+ raise PosxmlCompiler::VariableTypeError.new("Variable type #{variable_struct[:type]} mismatch, instruction #{instruction}, parameter #{parameter_name}, xsd type #{type}.")
37
+ when :integer
38
+ raise PosxmlCompiler::VariableTypeError.new("Variable type #{variable_struct[:type]} mismatch, instruction #{instruction}, parameter #{parameter_name}, xsd type #{type}.")
39
+ else
40
+ raise PosxmlCompiler::VariableTypeError.new("Variable type #{variable_struct[:type]} not recognized, instruction #{instruction}, parameter #{parameter_name}, xsd type #{type}.")
41
+ end
42
+
43
+ return true
44
+ end
45
+
46
+ # reference true is a variable that is been referenced without been previously created
47
+ def create(instruction, parameter, value, reference = false)
48
+ validate_size(parameter, value)
49
+ case instruction
50
+ when FUNCTION_NAME
51
+ create_function_variable(value, instruction)
52
+ when CALLFUNCTION_NAME
53
+ create_function_variable(value, instruction)
54
+ when VARIABLE_STRING
55
+ create_type_variable(parameter, value, :string, reference)
56
+ when VARIABLE_INTEGER
57
+ create_type_variable(parameter, value, :integer, reference)
58
+ else
59
+ create_ordinary_variable(instruction, parameter, value, reference)
60
+ end
61
+ end
62
+
63
+ def find(instruction, parameter, name)
64
+ if instruction == VARIABLE_STRING || instruction == VARIABLE_INTEGER
65
+ if parameter == VARIABLE_PARAMETER_NAME
66
+ remove_reference_struct(self.variables[name])
67
+ else # VARIABLE_PARAMETER_VALUE can return a reference for other variable
68
+ self.variables[name]
69
+ end
70
+ else
71
+ self.variables[name]
72
+ end
73
+ end
74
+
75
+ def multiple_reference?(str, reference)
76
+ reference && str.include?("[")
77
+ end
78
+
79
+ # Receive:
80
+ # ("function", "name", "functionbla")
81
+ # ("if", "variable", "$(sKeyTouchScreen)")
82
+ # ("if", "operator", "equalto")
83
+ # ("if", "value", "KEY_CLEAR")
84
+ #
85
+ # Return:
86
+ # {:original => "functionbla", :value => "functionbla", :type => :string}
87
+ # {:original => "$(sKeyTouchScreen)", :value => "$(1)", :type => :string}
88
+ # {:original => "$(!sBufRecv[$(iFound)])", :value => "$(2.$&3)", :type => :string}
89
+ # {:original => "$(sBufRecv[$(iFound)])", :value => "$(2.$3)", :type => :string}
90
+ # {:original => "equalto", :value => "equalto", :type => :string}
91
+ # {:original => "KEY_CLEAR", :value => "KEY_CLEAR", :type => :string}
92
+ #
93
+ # - Important, the struct returned only has a parameter value with $()
94
+ # if isn't the stringvariable and integervariable value. Because those
95
+ # instructions require the variable number without $().
96
+ def get(instruction, parameter, value)
97
+ if value.include? VARIABLE_REFERENCE
98
+ value = remove_reference(value)
99
+ if variable_struct = self.find(instruction, parameter, value)
100
+ check_typing_mismatch(instruction, parameter, variable_struct)
101
+ variable_struct
102
+ else
103
+ self.create(instruction, parameter, value, true)
104
+ end
105
+ else
106
+ # Not a reference for other variable
107
+ self.create(instruction, parameter, value)
108
+ end
109
+ end
110
+
111
+ def parameter_scheme(original = nil, value = nil, type = nil, reference = false)
112
+ if reference
113
+ value, index_variable, range_index = parse_index_variable(value, reference)
114
+ if index_variable
115
+ variable1 = self.get(VARIABLE_STRING, VARIABLE_PARAMETER_VALUE, value)
116
+ variable2 = self.get(VARIABLE_INTEGER, VARIABLE_PARAMETER_VALUE, index_variable)
117
+ format = format_multiple_reference(range_index, variable1[:value], variable2[:value])
118
+
119
+ {:original => original, :value => format, :type => type, :index_variable => index_variable}
120
+ else
121
+ {:original => original, :value => "#{VARIABLE_REFERENCE}#{value})", :type => type}
122
+ end
123
+ else
124
+ {:original => original, :value => value, :type => type}
125
+ end
126
+ end
127
+
128
+ def next_number(name, type)
129
+ value = self.number += 1
130
+ if type == :string
131
+ if (self.number_strings += 1) > VARIABLE_STRING_LIMIT
132
+ raise PosxmlCompiler::VariableStringLimitError.new("#{type} variable #{name} exceed the number of string variables")
133
+ end
134
+ end
135
+ if value > VARIABLE_LIMIT
136
+ raise PosxmlCompiler::VariableLimitError.new("#{type} variable #{name} exceed the number of variables")
137
+ end
138
+ "$(#{value})"
139
+ end
140
+
141
+ private
142
+ def create_type_variable(parameter, value, type, reference)
143
+ if parameter == VARIABLE_PARAMETER_NAME
144
+ unless self.variables[value]
145
+ number = remove_reference(next_number(parameter, type))
146
+ self.variables[value] = self.parameter_scheme(value, number, type, true)
147
+ end
148
+ remove_reference_struct(self.variables[value])
149
+ else
150
+ # If is value, reference and the variable doesn't exists should raise an error because it wasn't declared
151
+ if parameter == VARIABLE_PARAMETER_VALUE && self.variables[value].nil? && reference && ! self.multiple_reference?(value, reference)
152
+ raise PosxmlCompiler::VariableUndefinedError.new("Variable #{value} undefined")
153
+ end
154
+ self.parameter_scheme(value, value, type, reference)
155
+ end
156
+ end
157
+
158
+ def create_function_variable(value, instruction)
159
+ raise PosxmlCompiler::VariableNameError.new("#{instruction} name #{value} can't be a reference") if value.include?(VARIABLE_REFERENCE)
160
+ self.parameter_scheme(value, self.functions.get(value), :string)
161
+ end
162
+
163
+ def create_ordinary_variable(instruction, parameter, value, reference)
164
+ if reference
165
+ type = self.xsd.parameter_type(instruction, parameter)
166
+ if self.multiple_reference?(value, reference)
167
+ self.parameter_scheme(value, value, type, reference)
168
+ else
169
+ raise PosxmlCompiler::VariableUndefinedError.new("Variable #{value} undefined") unless self.variables[value]
170
+ self.variables[value] = self.parameter_scheme(value, value, type, reference)
171
+ end
172
+ else
173
+ # isn't important consider a type in this case
174
+ self.parameter_scheme(value, value)
175
+ end
176
+ end
177
+
178
+ # parse_index_variable
179
+ # receive "!sBufRecv[$(iFound)]"
180
+ # return ["!sBufRecv", iFound, true]
181
+ # or
182
+ # receive "sBufRecv[$(iFound)]"
183
+ # return ["sBufRecv", iFound, false]
184
+ def parse_index_variable(value, reference)
185
+ #if reference && value.include?("[")
186
+ if self.multiple_reference?(value, reference)
187
+ index = "$(#{value.match(/\[\$\((.+)\)\]/)[1]})"
188
+ str = "$(#{value.split("[")[0]})"
189
+
190
+ if str[2] == "!"
191
+ str[2] = ""
192
+ [str, index, true]
193
+ else
194
+ [str, index, false]
195
+ end
196
+ else
197
+ value
198
+ end
199
+ end
200
+
201
+ def format_multiple_reference(range_index, variable1, variable2)
202
+ str1 = variable1.sub(VARIABLE_REFERENCE, "").sub(")", "") # Format value to be like "123"
203
+ str2 = variable2.sub("(", "").sub(")", "") # format value to be "$123")
204
+ # At the end "$(123.$&456)"
205
+ if range_index
206
+ format = "#{VARIABLE_REFERENCE}#{str1}.#{str2.insert(1, "&")})"
207
+ else
208
+ format = "#{VARIABLE_REFERENCE}#{str1}.#{str2})"
209
+ end
210
+ end
211
+
212
+ def validate_size(name, original)
213
+ if original.size > VARIABLE_STRING_MAX_SIZE
214
+ raise PosxmlCompiler::StringSizeError.new("string variable #{name} exceed #{VARIABLE_STRING_MAX_SIZE} bytes [#{original}]")
215
+ end
216
+ end
217
+
218
+ def remove_reference_struct(data)
219
+ return unless data
220
+ struct = data.dup
221
+ struct[:value] = remove_reference(struct[:value])
222
+ struct
223
+ end
224
+
225
+ def remove_reference(str)
226
+ string = str.to_s
227
+ if string.include? VARIABLE_REFERENCE
228
+ string.to_s[2..-2]
229
+ else
230
+ string
231
+ end
232
+ end
233
+ end
234
+ end
235
+