posxml_parser 1.3.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+