Class: WLang::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/wlang/parser.rb,
lib/wlang/parser_state.rb

Overview

Parser for wlang templates.

This class implements the parsing algorithm of wlang, recognizing special tags and replacing them using installed rules. Instanciating a template is done using instantiate. All other methods (parse, parse_block, has_block?) and the like are callbacks for rules and should not be used by users themselve.

Detailed API

Defined Under Namespace

Classes: State

Instance Method Summary collapse

Constructor Details

#initialize(hosted, template, scope) ⇒ Parser

Initializes a parser instance.

Raises:

  • (ArgumentError)


19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/wlang/parser.rb', line 19

def initialize(hosted, template, scope)
  raise(ArgumentError, "Hosted language is mandatory (a ::WLang::HostedLanguage)") unless ::WLang::HostedLanguage===hosted
  raise(ArgumentError, "Template is mandatory (a ::WLang::Template)") unless ::WLang::Template===template
  raise(ArgumentError, "Scope is mandatory (a Hash)") unless ::Hash===scope
  @state = ::WLang::Parser::State.new(self).branch(
    :hosted   => hosted,
    :template => template,
    :dialect  => template.dialect,
    :offset   => 0,
    :shared   => :none,
    :scope    => scope,
    :buffer   => template.dialect.factor_buffer)
end

Instance Method Details

#<<(str, block) ⇒ Object

Pushes a given string on the output buffer



250
251
252
# File 'lib/wlang/parser.rb', line 250

def <<(str, block)
  append_buffer(buffer, str, block)
end

#append_buffer(buffer, str, block) ⇒ Object

Appends on a given buffer



241
242
243
244
245
246
247
# File 'lib/wlang/parser.rb', line 241

def append_buffer(buffer, str, block)
  if buffer.respond_to?(:wlang_append)
    buffer.wlang_append(str, block)
  else
    buffer << str
  end
end

#block_missing_error(offset) ⇒ Object

Raises a ParseError at a given offset for a missing block



316
317
318
# File 'lib/wlang/parser.rb', line 316

def block_missing_error(offset)
  template.parse_error(offset, "parse error, block was expected")
end

#branch(opts = {}) ⇒ Object

Branches the current parser

Raises:

  • (ArgumentError)


60
61
62
63
64
65
66
# File 'lib/wlang/parser.rb', line 60

def branch(opts = {}) 
  raise ArgumentError, "Parser branching requires a block" unless block_given?
  @state = @state.branch(opts)
  result = yield(@state)
  @state = @state.parent
  result
end

#branch_scope(pairing = {}, which = :all) ⇒ Object

Yields the block in a new scope branch, pushing pairing values on it. Original scope is restored after that. Returns what the yielded block returned.

Raises:

  • (ArgumentError)


259
260
261
262
# File 'lib/wlang/parser.rb', line 259

def branch_scope(pairing = {}, which = :all)
  raise ArgumentError, "Parser.branch_scope expects a block" unless block_given?
  branch(:scope => pairing, :shared => which) { yield }
end

#bufferObject

Returns the current buffer



54
# File 'lib/wlang/parser.rb', line 54

def buffer() state.buffer; end

#encode(src, encoder, options = nil) ⇒ Object

Encodes a given text using an encoder, that may be a qualified name or an Encoder instance.



290
291
292
293
294
295
# File 'lib/wlang/parser.rb', line 290

def encode(src, encoder, options=nil)
  options = {} unless options
  options['_encoder_'] = encoder
  options['_template_'] = template
  ensure_encoder(encoder).encode(src, options)
end

#ensure_dialect(dialect) ⇒ Object

Finds a real dialect instance from an argument (Dialect instance or qualified name)



84
85
86
87
88
89
90
91
92
# File 'lib/wlang/parser.rb', line 84

def ensure_dialect(dialect)
  if String===dialect
    dname, dialect = dialect, WLang::dialect(dialect)
    raise(ParseError,"Unknown modulation dialect: #{dname}") if dialect.nil?
  elsif not(Dialect===dialect)
    raise(ParseError,"Unknown modulation dialect: #{dialect}")
  end    
  dialect  
end

#ensure_encoder(encoder) ⇒ Object

Finds a real ecoder instance from an argument (Encoder instance or qualified or unqualified name)



96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/wlang/parser.rb', line 96

def ensure_encoder(encoder)
  if String===encoder
    if encoder.include?("/")
      ename, encoder = encoder, WLang::encoder(encoder)
      raise(ParseError,"Unknown encoder: #{ename}") if encoder.nil?
    else
      ename, encoder = encoder, self.dialect.find_encoder(encoder)
      raise(ParseError,"Unknown encoder: #{ename}") if encoder.nil?
    end
  elsif not(Encoder===encoder)
    raise(ParseError,"Unknown encoder: #{encoder}")
  end
  encoder
end

#error(offset, message) ⇒ Object

Raises an exception with a friendly message



300
301
302
# File 'lib/wlang/parser.rb', line 300

def error(offset, message)
  template.error(offset, message)
end

#evaluate(expression) ⇒ Object

Evaluates a ruby expression on the current context. See WLang::Parser::Context#evaluate.



275
276
277
# File 'lib/wlang/parser.rb', line 275

def evaluate(expression)
  hosted.evaluate(expression, state)
end

#factor_bufferObject

Factors a specific buffer on the current dialect



282
283
284
# File 'lib/wlang/parser.rb', line 282

def factor_buffer
  self.dialect.factor_buffer
end

#file_resolve(uri) ⇒ Object

Resolves an URI throught the current template



71
72
73
# File 'lib/wlang/parser.rb', line 71

def file_resolve(uri)
  template.file_resolve(uri)
end

#file_template(file, dialect = nil, block_symbols = :braces) ⇒ Object

Factors a template instance for a given file



78
79
80
# File 'lib/wlang/parser.rb', line 78

def file_template(file, dialect = nil, block_symbols = :braces)
  WLang::file_template(file, dialect, block_symbols)
end

#has_block?(offset) ⇒ Boolean

Checks if a given offset is a starting block. For easy implementation of rules the check applied here is that text starting at offset in the template is precisely '}(the reason for that is that instantiate, parse, parse_block always stop parsing on a '')

Returns:

  • (Boolean)


222
223
224
# File 'lib/wlang/parser.rb', line 222

def has_block?(offset)
  self.source_text[offset,2] == template.block_endstart
end

#instantiate(apply_posttransform = true) ⇒ Object

Parses the template's text and instantiate it. Dialect post_transformer is only applied of apply_posttransform is set to true.



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
190
191
192
193
194
195
196
197
# File 'lib/wlang/parser.rb', line 125

def instantiate(apply_posttransform = true)
  # Main variables put in local scope for efficiency:
  #   - template:     current parsed template
  #   - source_text:  current template's source text
  #   - offset:       matching current position
  #   - pattern:      current dialect's regexp pattern
  #   - rules:        handlers of '{' currently opened
  template    = self.template
  symbols     = self.template.block_symbols
  source_text = self.source_text
  dialect     = self.dialect
  buffer      = self.buffer
  pattern     = dialect.pattern(template.block_symbols)
  rules       = []

  # we start matching everything in the ruleset
  while match_at=source_text.index(pattern, self.offset)
    match, match_length = $~[0], $~[0].length
  
    # puts pre_match (we can't use $~.pre_match !)
    self.<<(source_text[self.offset, match_at-self.offset], false) if match_at>0
  
    if source_text[match_at,1]=='\\'           # escaping sequence
      self.<<(match[1..-1], false)
      self.offset = match_at + match_length
    
    elsif match.length==1               # simple '{' or '}' here
      self.offset = match_at + match_length
      if match==Template::BLOCK_SYMBOLS[symbols][0]
        self.<<(match, false)  # simple '{' are always pushed
        # we push '{' in rules to recognize it's associated '}'
        # that must be pushed on buffer also
        rules << match   
      else
        # end of my job if I can't pop a previous rule
        break if rules.empty?
        # otherwise, push '}' only if associated to a simple '{'
        self.<<(match, false) unless Rule===rules.pop
      end
    
    elsif match[-1,1]==Template::BLOCK_SYMBOLS[symbols][0] # opening special tag
      # following line should never return nil as the matching pattern comes 
      # from the ruleset itself!
      rule_symbol = match[0..-2]
      rule = dialect.ruleset[rule_symbol]     
      rules << rule
    
      # Just added to get the last position in case of an error
      self.offset = match_at + match_length

      # lauch that rule, get it's replacement and my new offset
      replacement, self.offset = launch_rule(dialect, rule_symbol, rule, self.offset)
  
      # push replacement
      self.<<(replacement, true) unless replacement.empty?
    end
  
  end  # while match_at=...

  # trailing data (end of template reached only if no match_at)
  unless match_at
    unexpected_eof(source_text.length, '}') unless rules.empty?
    self.<<(source_text[self.offset, 1+source_text.length-self.offset], false)
    self.offset = source_text.length
  end
  
  # Apply post-transformation only if required
  if apply_posttransform
    [dialect.apply_post_transform(buffer), self.offset-1]
  else
   [buffer, self.offset-1]
  end
end

#launch_rule(dialect, rule_symbol, rule, offset) ⇒ Object

Checks the result of a given rule

Raises:



114
115
116
117
118
119
# File 'lib/wlang/parser.rb', line 114

def launch_rule(dialect, rule_symbol, rule, offset)
  result = rule.start_tag(self, offset)
  raise WLang::Error, "Bad rule implementation #{dialect.qualified_name} #{rule_symbol}{}\n#{result.inspect}"\
    unless result.size == 2 and String===result[0] and Integer===result[1]
  result
end

#offset=(offset) ⇒ Object

Sets the current offset of the parser



51
# File 'lib/wlang/parser.rb', line 51

def offset=(offset) state.offset = offset; end

#parse(offset, req_dialect = nil, req_buffer = nil) ⇒ Object

Launches a child parser for instantiation at a given offset in given dialect (same dialect than self if dialect is nil) and with an output buffer.



206
207
208
209
210
211
212
213
214
# File 'lib/wlang/parser.rb', line 206

def parse(offset, req_dialect = nil, req_buffer = nil)
  dialect = ensure_dialect(req_dialect.nil? ? self.dialect : req_dialect)
  buffer  = (req_buffer.nil? ? dialect.factor_buffer : req_buffer)
  branch(:offset => offset, 
         :dialect => dialect, 
         :buffer => buffer) do
    instantiate(!req_dialect.nil?)
  end
end

#parse_block(offset, dialect = nil, buffer = nil) ⇒ Object

Parses a given block starting at a given offset, expressed in a given dialect and using an output buffer. This method raises a ParseError if there is no block at the offset. It implies that we are on a '}{', see has_block? for details. Rules may thus force mandatory block parsing without having to check anything. Optional blocks must be handled by rules themselve.



233
234
235
236
# File 'lib/wlang/parser.rb', line 233

def parse_block(offset, dialect=nil, buffer=nil)
  block_missing_error(offset+2) unless has_block?(offset)
  parse(offset+2, dialect, buffer)
end

#scope_define(key, value) ⇒ Object

Adds a key/value pair on the current scope.



265
266
267
# File 'lib/wlang/parser.rb', line 265

def scope_define(key, value)
  state.scope[key] = value
end

#stateObject

Returns the current parser state



36
# File 'lib/wlang/parser.rb', line 36

def state(); @state; end

#syntax_error(offset, msg = nil) ⇒ Object

Raises a ParseError at a given offset.



307
308
309
310
311
# File 'lib/wlang/parser.rb', line 307

def syntax_error(offset, msg=nil)
  text = self.parse(offset, "wlang/dummy", "")
  msg = msg.nil? ? '' : ": #{msg}"
  template.parse_error(offset, "parse error on '#{text}'#{msg}")
end

#templateObject

Returns the current template



39
# File 'lib/wlang/parser.rb', line 39

def template() state.template; end

#unexpected_eof(offset, expected) ⇒ Object

Raises a ParseError at a given offset for a unexpected EOF specif. the expected character when EOF found



324
325
326
# File 'lib/wlang/parser.rb', line 324

def unexpected_eof(offset, expected)
  template.parse_error(offset, "#{expected} expected, EOF found")
end