Class: Glaemscribe::API::RuleGroup

Inherits:
Object
  • Object
show all
Defined in:
lib/api/rule_group.rb

Constant Summary collapse

VAR_NAME_REGEXP =
/{([0-9A-Z_]+)}/
UNICODE_VAR_NAME_REGEXP_IN =
/^UNI_([0-9A-F]+)$/
UNICODE_VAR_NAME_REGEXP_OUT =
/{UNI_([0-9A-F]+)}/
VAR_DECL_REGEXP =
/^\s*{([0-9A-Z_]+)}\s+===\s+(.+?)\s*$/
POINTER_VAR_DECL_REGEXP =
/^\s*{([0-9A-Z_]+)}\s+<=>\s+(.+?)\s*$/
RULE_REGEXP =
/^\s*(.*?)\s+-->\s+(.+?)\s*$/
CROSS_SCHEMA_REGEXP =
/[0-9]+(\s*,\s*[0-9]+)*/
CROSS_RULE_REGEXP =
/^\s*(.*?)\s+-->\s+(#{CROSS_SCHEMA_REGEXP}|#{VAR_NAME_REGEXP}|identity)\s+-->\s+(.+?)\s*$/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(mode, name) ⇒ RuleGroup

Returns a new instance of RuleGroup.



56
57
58
59
60
61
# File 'lib/api/rule_group.rb', line 56

def initialize(mode,name)
  @name             = name
  @mode             = mode
  @macros           = {}
  @root_code_block  = IfTree::CodeBlock.new       
end

Instance Attribute Details

#in_charsetObject (readonly)

Returns the value of attribute in_charset.



54
55
56
# File 'lib/api/rule_group.rb', line 54

def in_charset
  @in_charset
end

#macrosObject (readonly)

Returns the value of attribute macros.



54
55
56
# File 'lib/api/rule_group.rb', line 54

def macros
  @macros
end

#modeObject (readonly)

Returns the value of attribute mode.



54
55
56
# File 'lib/api/rule_group.rb', line 54

def mode
  @mode
end

#nameObject (readonly)

Returns the value of attribute name.



54
55
56
# File 'lib/api/rule_group.rb', line 54

def name
  @name
end

#root_code_blockObject (readonly)

Returns the value of attribute root_code_block.



54
55
56
# File 'lib/api/rule_group.rb', line 54

def root_code_block
  @root_code_block
end

#rulesObject (readonly)

Returns the value of attribute rules.



54
55
56
# File 'lib/api/rule_group.rb', line 54

def rules
  @rules
end

Instance Method Details

#add_var(var_name, value, is_pointer) ⇒ Object



63
64
65
# File 'lib/api/rule_group.rb', line 63

def add_var(var_name, value, is_pointer)
  @vars[var_name] = RuleGroupVar.new(var_name, value, is_pointer)
end

#apply_vars(line, string, allow_unicode_vars = false) ⇒ Object

Replace all vars in expression



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/api/rule_group.rb', line 68

def apply_vars(line, string, allow_unicode_vars=false)
  
  ret               = string
  stack_depth       = 0
  had_replacements  = true
  
  while had_replacements
    
    had_replacements = false
    ret = ret.gsub(VAR_NAME_REGEXP) { |cap_var|
      vname = $1
      v     = @vars[vname]
      if !v
        if vname =~ UNICODE_VAR_NAME_REGEXP_IN
          # A unicode variable.
          if allow_unicode_vars
            # Just keep this variable intact, it will be replaced at the last moment of the parsing
            rep = cap_var
          else
            @mode.errors << Glaeml::Error.new(line, "In expression: #{string}: making wrong use of unicode variable: #{cap_var}. Unicode vars can only be used in source members of a rule or in the definition of another variable.") 
            return nil
          end
        else
          @mode.errors << Glaeml::Error.new(line, "In expression: #{string}: failed to evaluate variable: #{cap_var}.") 
          return nil
        end
      else
        rep = v.value
        # Only count replacements on non unicode vars
        had_replacements = true
      end
      rep
    }
    stack_depth += 1
                     
    break if !had_replacements
    
    if stack_depth > 16
      @mode.errors << Glaeml::Error.new(line, "In expression: #{string}: evaluation stack overflow.") 
      return nil
    end
  end
          
  ret
end

#descend_if_tree(code_block, trans_options) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/api/rule_group.rb', line 114

def descend_if_tree(code_block, trans_options)
  code_block.terms.each{ |term|         
    if(term.is_code_lines?)
      term.code_lines.each{ |cl|
        finalize_code_line(cl) 
      } 
    elsif(term.is_macro_deploy?)
                
      # Ok this is a bit dirty but I don't want to rewrite the error managamenet
      # So add an error and if it's still the last (meaning there were no error) one remove it
      possible_error = Glaeml::Error.new(term.line, ">> Macro backtrace : #{term.macro.name}")
      @mode.errors << possible_error
      
      # First, test if variable is pushable
      arg_values = []
      term.macro.arg_names.each_with_index { |arg_name, i|
        
        var_value = nil
        
        if @vars[arg_name]
          @mode.errors << Glaeml::Error.new(term.line, "Local variable #{arg_name} hinders a variable with the same name in this context. Use only local variable names in macros!")
        else
          # Evaluate local var
          var_value_ex  = term.arg_value_expressions[i]
          var_value     = apply_vars(term.line, var_value_ex, true)      
          
          if !var_value
            @mode.errors << Glaeml::Error.new(term.line, "Thus, variable {#{arg_name}} could not be declared.")
          end              
        end
      
        arg_values << {name: arg_name, val: var_value}
      }
      
      # We push local vars after the whole loop to avoid interferences between them when evaluating them
      arg_values.each { |v| 
        if v[:val]
          add_var(v[:name],v[:val],false)
        end
      }
  
      descend_if_tree(term.macro.root_code_block, trans_options)
      
      # Remove the local vars from the scope (only if they were leggit)
      arg_values.each { |v| 
        if v[:val]
          @vars[v[:name]] = nil
        end
      }
                
      if mode.errors.last == possible_error
        # Remove the error scope if there were no errors
        mode.errors.pop
      else
        # Add another one to close the context
        @mode.errors << Glaeml::Error.new(term.line, "<< Macro backtrace : #{term.macro.name}")
      end
                  
    else
      term.if_conds.each{ |if_cond|
        
        if_eval = Eval::Parser.new()
        
        begin
          if(if_eval.parse(if_cond.expression,trans_options) == true)
            descend_if_tree(if_cond.child_code_block, trans_options)
            break
          end
        rescue Eval::IfEvalError => e
          @mode.errors << Glaeml::Error.new(if_cond.line, "Failed to evaluate condition '#{if_cond.expression}' (#{e})")
        end 
          
      }
    end
  }
end

#finalize(trans_options) ⇒ Object



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/api/rule_group.rb', line 268

def finalize(trans_options)
  @vars             = {}       
  @in_charset       = {}
  @rules            = []
  
  add_var("NULL","",false)
  
  # Characters that are not easily entered or visible in a text editor
  add_var("NBSP",           "{UNI_A0}",   false)
  add_var("WJ",             "{UNI_2060}", false)
  add_var("ZWSP",           "{UNI_200B}", false)
  add_var("ZWNJ",           "{UNI_200C}", false)        

  # The following characters are used by the mode syntax.
  # Redefine some convenient tools.
  add_var("UNDERSCORE",     "{UNI_5F}",  false)
  add_var("ASTERISK",       "{UNI_2A}",  false)
  add_var("COMMA",          "{UNI_2C}",  false)
  add_var("LPAREN",         "{UNI_28}",  false)
  add_var("RPAREN",         "{UNI_29}",  false)
  add_var("LBRACKET",       "{UNI_5B}",  false)
  add_var("RBRACKET",       "{UNI_5D}",  false)
 
  descend_if_tree(@root_code_block, trans_options)
                      
  # Now that we have selected our rules, create the in_charset of the rule_group 
  rules.each{ |r| 
    r.sub_rules.each { |sr|
      sr.src_combination.join("").split(//).each{ |inchar|
        # Add the character to the map of input characters
        # Ignore '\u0000' (bounds of word) and '|' (word breaker)
        @in_charset[inchar] = self if inchar != WORD_BREAKER && inchar != WORD_BOUNDARY_TREE
      }
    }
  }
end

#finalize_code_line(code_line) ⇒ Object



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/api/rule_group.rb', line 207

def finalize_code_line(code_line)
  begin
    
    if code_line.expression =~ VAR_DECL_REGEXP

      var_name      = $1
      var_value_ex  = $2
      var_value     = apply_vars(code_line.line, var_value_ex, true)
  
      if !var_value
        @mode.errors << Glaeml::Error.new(code_line.line, "Thus, variable {#{var_name}} could not be declared.")
        return
      end
  
      add_var(var_name, var_value, false)
 
    elsif code_line.expression =~ POINTER_VAR_DECL_REGEXP
      
      var_name      = $1
      var_value_ex  = $2
 
      add_var(var_name, var_value_ex, true)
      
    elsif code_line.expression =~ CROSS_RULE_REGEXP
  
      match         = $1
      cross         = $2
      var_name      = $4
      replacement   = $5      
      
      if var_name
        # This was a variable declaration           
        var_value = apply_vars(code_line.line, cross, false)
        if !var_value
          @mode.errors << Glaeml::Error.new(code_line.line, "Thus, variable {#{var_name}} could not be declared.")
          return
        end
        cross = var_value
      end
      
      if cross == "identity"
        cross = nil              
      end

      finalize_rule(code_line.line, match, replacement, cross)

    elsif code_line.expression =~ RULE_REGEXP
  
      match         = $1
      replacement   = $2

      finalize_rule(code_line.line, match, replacement)
   
    elsif code_line.expression.empty?
      # puts "Empty"
    else
      @mode.errors << Glaeml::Error.new(code_line.line,"Cannot understand: #{code_line.expression}")
    end  
  end
end

#finalize_rule(line, match_exp, replacement_exp, cross_schema = nil) ⇒ Object



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/api/rule_group.rb', line 191

def finalize_rule(line, match_exp, replacement_exp, cross_schema = nil)
  
  match                 = apply_vars(line, match_exp, true)
  replacement           = apply_vars(line, replacement_exp, false)
  
  return if !match || !replacement # Failed
        
  rule                  = Rule.new(line, self)                             
  rule.src_sheaf_chain  = SheafChain.new(rule,match,true)
  rule.dst_sheaf_chain  = SheafChain.new(rule,replacement,false)
        
  rule.finalize(cross_schema)

  self.rules << rule
end