wlang 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/LICENCE.rdoc +25 -0
  2. data/README.rdoc +111 -0
  3. data/bin/wlang +24 -0
  4. data/doc/specification/about.rdoc +61 -0
  5. data/doc/specification/dialects.wtpl +0 -0
  6. data/doc/specification/examples.rb +3 -0
  7. data/doc/specification/glossary.wtpl +14 -0
  8. data/doc/specification/hosting.rdoc +0 -0
  9. data/doc/specification/overview.rdoc +116 -0
  10. data/doc/specification/rulesets.wtpl +87 -0
  11. data/doc/specification/specification.css +52 -0
  12. data/doc/specification/specification.html +1361 -0
  13. data/doc/specification/specification.js +8 -0
  14. data/doc/specification/specification.wtpl +41 -0
  15. data/doc/specification/specification.yml +430 -0
  16. data/doc/specification/symbols.wtpl +16 -0
  17. data/lib/wlang.rb +186 -0
  18. data/lib/wlang/basic_object.rb +19 -0
  19. data/lib/wlang/dialect.rb +230 -0
  20. data/lib/wlang/dialect_dsl.rb +136 -0
  21. data/lib/wlang/dialect_loader.rb +69 -0
  22. data/lib/wlang/dialects/coderay_dialect.rb +35 -0
  23. data/lib/wlang/dialects/plain_text_dialect.rb +75 -0
  24. data/lib/wlang/dialects/rdoc_dialect.rb +33 -0
  25. data/lib/wlang/dialects/ruby_dialect.rb +35 -0
  26. data/lib/wlang/dialects/sql_dialect.rb +38 -0
  27. data/lib/wlang/dialects/standard_dialects.rb +113 -0
  28. data/lib/wlang/dialects/xhtml_dialect.rb +40 -0
  29. data/lib/wlang/encoder.rb +66 -0
  30. data/lib/wlang/encoder_set.rb +117 -0
  31. data/lib/wlang/errors.rb +37 -0
  32. data/lib/wlang/intelligent_buffer.rb +94 -0
  33. data/lib/wlang/parser.rb +251 -0
  34. data/lib/wlang/parser_context.rb +146 -0
  35. data/lib/wlang/ruby_extensions.rb +21 -0
  36. data/lib/wlang/rule.rb +66 -0
  37. data/lib/wlang/rule_set.rb +93 -0
  38. data/lib/wlang/rulesets/basic_ruleset.rb +75 -0
  39. data/lib/wlang/rulesets/buffering_ruleset.rb +103 -0
  40. data/lib/wlang/rulesets/context_ruleset.rb +115 -0
  41. data/lib/wlang/rulesets/encoding_ruleset.rb +73 -0
  42. data/lib/wlang/rulesets/imperative_ruleset.rb +132 -0
  43. data/lib/wlang/rulesets/ruleset_utils.rb +296 -0
  44. data/lib/wlang/template.rb +79 -0
  45. data/lib/wlang/wlang_command.rb +54 -0
  46. data/lib/wlang/wlang_command_options.rb +158 -0
  47. data/test/sandbox.rb +1 -0
  48. data/test/test_all.rb +8 -0
  49. data/test/wlang/anagram_bugs_test.rb +111 -0
  50. data/test/wlang/basic_ruleset_test.rb +52 -0
  51. data/test/wlang/buffering_ruleset_test.rb +102 -0
  52. data/test/wlang/buffering_template1.wtpl +1 -0
  53. data/test/wlang/buffering_template2.wtpl +1 -0
  54. data/test/wlang/buffering_template3.wtpl +1 -0
  55. data/test/wlang/buffering_template4.wtpl +1 -0
  56. data/test/wlang/buffering_template5.wtpl +1 -0
  57. data/test/wlang/context_ruleset_test.rb +32 -0
  58. data/test/wlang/data.rb +3 -0
  59. data/test/wlang/encoder_set_test.rb +42 -0
  60. data/test/wlang/imperative_ruleset_test.rb +107 -0
  61. data/test/wlang/intelligent_buffer_test.rb +194 -0
  62. data/test/wlang/othersymbols_test.rb +16 -0
  63. data/test/wlang/parser_context_test.rb +29 -0
  64. data/test/wlang/parser_test.rb +89 -0
  65. data/test/wlang/plain_text_dialect_test.rb +21 -0
  66. data/test/wlang/ruby_dialect_test.rb +100 -0
  67. data/test/wlang/ruby_expected.rb +3 -0
  68. data/test/wlang/ruby_template.wrb +3 -0
  69. data/test/wlang/ruleset_utils_test.rb +245 -0
  70. data/test/wlang/specification_examples_test.rb +52 -0
  71. data/test/wlang/test_utils.rb +25 -0
  72. data/test/wlang/wlang_test.rb +80 -0
  73. metadata +136 -0
@@ -0,0 +1,40 @@
1
+ require 'cgi'
2
+ module WLang
3
+ class EncoderSet
4
+
5
+ # Defines encoders of the whtml dialect
6
+ module XHtml
7
+
8
+ # Default encoders
9
+ DEFAULT_ENCODERS = {"main-encoding" => :entities_encoding,
10
+ "single-quoting" => :single_quoting,
11
+ "double-quoting" => :double_quoting,
12
+ "entities-encoding" => :entities_encoding}
13
+
14
+
15
+ # Single-quoting encoding
16
+ def self.single_quoting(src, options); src.gsub(/([^\\])'/,%q{\1\\\'}); end
17
+
18
+ # Double-quoting encoding
19
+ def self.double_quoting(src, options); src.gsub('"','\"'); end
20
+
21
+ # Entities-encoding
22
+ def self.entities_encoding(src, options);
23
+ CGI::escapeHTML(src)
24
+ end
25
+
26
+ end # module XHtml
27
+ end
28
+ class RuleSet
29
+
30
+ # Defines rulset of the wlang/xhtml dialect
31
+ module XHtml
32
+
33
+ # Default mapping between tag symbols and methods
34
+ DEFAULT_RULESET = {}
35
+
36
+ end # module XHtml
37
+
38
+ end
39
+ end
40
+
@@ -0,0 +1,66 @@
1
+ module WLang
2
+
3
+ #
4
+ # Encapsulates some encoding algorithm. This class is to EncoderSet what Rule
5
+ # is to RuleSet. Encoders are always installed on a EncoderSet (using EncoderSet#add_encoder),
6
+ # which is itself installed on a Dialect. Note that the method mentionned previously
7
+ # provides a DRY shortcut, allowing not using this class directly.
8
+ #
9
+ # Example:
10
+ # # Encoder subclassing can be avoided by providing a block to new
11
+ # # The following encoder job is to upcase the text:
12
+ # encoder = Encoder.new do |src,options|
13
+ # src.upcase
14
+ # end
15
+ #
16
+ # # It is even better to use the DSL
17
+ # WLang::dialect("mydialect") do
18
+ # # The following encoder job is to upcase the text and will be installed
19
+ # # under mydialect/myupcaser qualified name
20
+ # encoder("myupcaser") do |src,options|
21
+ # src.upcase
22
+ # end
23
+ # end
24
+ #
25
+ # Creating a a new encoder can be made in two ways: by subclassing this class and
26
+ # overriding the encoder method or by passing a block to new. In both cases,
27
+ # <b>encoders should always be stateless</b>, to allow reusable dialects that could
28
+ # even be used in a multi-threading environment.
29
+ #
30
+ # == Detailed API
31
+ class Encoder
32
+
33
+ #
34
+ # Creates a new encoder. If no block is given, the invocation of new MUST be made
35
+ # on a subclass overriding encoder. Otherwise, the block is considered as the
36
+ # effective stateless implementation of encoder and will be called with the
37
+ # same arguments.
38
+ #
39
+ def initialize(&block)
40
+ unless block.nil?
41
+ raise(ArgumentError, "Expected a rule block of arity 2")\
42
+ unless block.arity==2
43
+ end
44
+ @block = block
45
+ end
46
+
47
+ #
48
+ # Fired by the parser when a rule request encoding of some instantiated text.
49
+ # Typical example is the standard tag <tt>^{encoder/qualified/name}{...}</tt>
50
+ # (see WLang::RuleSet::Basic) which requires the second block instantiation to
51
+ # be encoded by the encoder whose qualified name is given by the first block.
52
+ # This method must simply return the encoded text.
53
+ #
54
+ # Arguments:
55
+ # - src: source text to encode.
56
+ # - options: encoding options through a Hash. Available options are documented
57
+ # by encoders themselve.
58
+ #
59
+ def encode(src, options)
60
+ raise(NotImplementedError) unless @block
61
+ @block.call(src, options)
62
+ end
63
+
64
+ end # class Encoder
65
+
66
+ end
@@ -0,0 +1,117 @@
1
+ require 'wlang/encoder'
2
+ module WLang
3
+
4
+ #
5
+ # This class allows grouping encoders together to build a given dialect.
6
+ # Encoders are always added with add_encoder, which also allows creating simple
7
+ # encoders on the fly (that is, without subclassing Encoder).
8
+ #
9
+ # Examples:
10
+ # # we will create a simple encoder set with two encoders, one for upcasing
11
+ # # the other for downcasing.
12
+ # encoders = EncoderSet.new
13
+ # encoder.add_encoder 'upcaser' do |src,options|
14
+ # src.upcase
15
+ # end
16
+ # encoder.add_encoder 'downcaser' do |src,options|
17
+ # src.downcase
18
+ # end
19
+ #
20
+ # == Detailed API
21
+ class EncoderSet
22
+
23
+ # Creates an empty encoder set.
24
+ def initialize
25
+ @encoders = {}
26
+ end
27
+
28
+ #
29
+ # Adds an encoder under a specific name. Supported signatures are as follows:
30
+ # - _name_ is a symbol: _encoder_ and _block_ are ignored and encoder is
31
+ # interpreted as being a method of the String class whose name is the symbol.
32
+ # - _name_ is a String and a block is provided: encoder is expected to be
33
+ # implemented by the block which takes |src,options| arguments.
34
+ # - _name_ is a String and _encoder_ is a Proc: same as if _encoder_ was a
35
+ # given block.
36
+ #
37
+ # Examples:
38
+ # encoders = EncoderSet.new
39
+ # # add an encoder by reusing String method
40
+ # encoders.add_encoder(:upcase)
41
+ #
42
+ # # add an encoder by providing a block
43
+ # encoders.add_encoder("ucase") do |src,options|
44
+ # src.upcase
45
+ # end
46
+ #
47
+ # # add an encoder by providing a Proc
48
+ # upcaser = Proc.new {|src,options| src.upcase}
49
+ # encoders.add_encoder("upcase", upcaser)
50
+ #
51
+ def add_encoder(name, encoder=nil, &block)
52
+ # handle String method through symbol
53
+ if Symbol===name
54
+ encoder, block = nil, Proc.new {|src,options| src.send(name)}
55
+ name = name.to_s
56
+ elsif Proc===encoder
57
+ encoder, block = nil, encoder
58
+ end
59
+
60
+ # check arguments
61
+ if encoder.nil?
62
+ raise(ArgumentError,"Block required") if block.nil?
63
+ encoder = Encoder.new(&block)
64
+ end
65
+ raise(ArgumentError, "Encoder expected") unless Encoder===encoder
66
+
67
+ # save encoder
68
+ @encoders[name] = encoder
69
+ end
70
+
71
+ #
72
+ # Adds reusable encoders defined in a Ruby module. _mod_ must be a Module instance.
73
+ # If _pairs_ is ommitted (nil), all encoders of the module are added, under their
74
+ # standard names (see DEFAULT_ENCODERS under WLang::EncoderSet::XHtml for example).
75
+ # Otherwise, _pairs_ is expected to be a Hash providing a mapping between encoder
76
+ # names and _mod_ methods (whose names are given by ruby symbols).
77
+ #
78
+ def add_encoders(mod, pairs=nil)
79
+ raise(ArgumentError,"Module expected",caller) unless Module===mod
80
+ pairs = mod::DEFAULT_ENCODERS if pairs.nil?
81
+ pairs.each_pair do |name,method|
82
+ meth = mod.method(method)
83
+ raise(ArgumentError,"No such method: #{method} in #{mod}") if meth.nil?
84
+ add_encoder(name, &meth.to_proc)
85
+ end
86
+ end
87
+
88
+ # Checks if an encoder is installed under _name.
89
+ def has_encoder?(name)
90
+ @encoders.has_key?(name)
91
+ end
92
+
93
+ # Returns an encoder by its name, nil if no such encoder.
94
+ def get_encoder(name)
95
+ @encoders[name]
96
+ end
97
+
98
+ #
99
+ # Shortcut for <tt>get_encoder(encoder).encode(source,options)</tt>
100
+ #
101
+ def encode(encoder, src, options=nil)
102
+ if String===encoder then
103
+ ename, encoder = encoder, get_encoder(encoder)
104
+ raise(ArgumentError,"No such encoder: #{ename}") if encoder.nil?
105
+ end
106
+ raise(ArgumentError,"Invalid encoder: #{encoder}") unless Encoder===encoder
107
+ encoder.encode(src, options)
108
+ end
109
+
110
+ # Returns a string with comma separated encoder names.
111
+ def to_s
112
+ @encoders.keys.join(", ")
113
+ end
114
+
115
+ end # class EncoderSet
116
+
117
+ end
@@ -0,0 +1,37 @@
1
+ module WLang
2
+
3
+ # Main error of all WLang errors.
4
+ class Error < StandardError; end
5
+
6
+ # Error raised by a WLang parser instanciation when an error occurs.
7
+ class ParseError < StandardError;
8
+
9
+ attr_reader :line, :column
10
+
11
+ # Creates an error with offset information
12
+ def initialize(message, offset=nil, template=nil)
13
+ @offset = offset
14
+ @line, @column = parse(template) if template
15
+ unless template and offset
16
+ super(message)
17
+ else
18
+ super("ParseError at #{@line}:#{@column} : #{message}")
19
+ end
20
+
21
+ end
22
+
23
+ # Reparses the template and finds line:column locations
24
+ def parse(template)
25
+ template = template[0,@offset]
26
+ if template =~ /\n/ then
27
+ lines = template[0,@offset].split(/\n/)
28
+ else
29
+ lines = [template]
30
+ end
31
+ line, column = lines.length, lines.last.length
32
+ return [line, column]
33
+ end
34
+
35
+ end # class ParseError
36
+
37
+ end # module WLang
@@ -0,0 +1,94 @@
1
+ module WLang
2
+
3
+ # Provides an intelligent output buffer
4
+ class IntelligentBuffer < String
5
+
6
+ # Some string utilities
7
+ module Methods
8
+
9
+ # Aligns _str_ at left offset _n_
10
+ # Credits: Treetop and Facets 2.0.2
11
+ def tabto(str, n)
12
+ if str =~ /^( *)\S/
13
+ indent(str, n - $1.length)
14
+ else
15
+ str
16
+ end
17
+ end
18
+
19
+ # Positive or negative indentation of _str_
20
+ # Credits: Treetop and Facets 2.0.2
21
+ def indent(str, n)
22
+ if n >= 0
23
+ str.gsub(/^/, ' ' * n)
24
+ else
25
+ str.gsub(/^ {0,#{-n}}/, "")
26
+ end
27
+ end
28
+
29
+ # Checks if _str_ contains multiple lines
30
+ def is_multiline?(str)
31
+ str =~ /\n/ ? true : false
32
+ end
33
+
34
+ #
35
+ # Strips a multiline block.
36
+ #
37
+ # Example:
38
+ # ([\t ]*\n)?
39
+ # ([\t ]\n)*
40
+ # [\t ]*some text here\n
41
+ # [\t ]* indented also\n?
42
+ # [\t ]*
43
+ #
44
+ # becomes:
45
+ # some text here\n
46
+ # indented also\n
47
+ #
48
+ def strip_block(str)
49
+ match = str.match(/\A[\t ]*\n?/)
50
+ str = match.post_match if match
51
+ match = str.match(/\n[\t ]*\Z/)
52
+ str = (match.pre_match << "\n") if match
53
+ str
54
+ end
55
+
56
+ # Returns column number of a specific offset
57
+ # Credits: Treetop and Facets 2.0.2
58
+ def column_of(str, index)
59
+ return 1 if index == 0
60
+ newline_index = str.rindex("\n", index - 1)
61
+ if newline_index
62
+ index - newline_index
63
+ else
64
+ index + 1
65
+ end
66
+ end
67
+
68
+ # Returns the column of the last character
69
+ def last_column(str)
70
+ column_of(str, str.length)
71
+ end
72
+
73
+ # Pushes a string, aligning it first
74
+ def <<(str, block=false)
75
+ if block and is_multiline?(str) and stripped = strip_block(str)
76
+ str = tabto(stripped, last_column(self)-1)
77
+ str = str.match(/\A[\t ]*/).post_match
78
+ end
79
+ super(str)
80
+ end
81
+
82
+ # WLang explicit appending
83
+ def wlang_append(str, block)
84
+ self.<<(str, block)
85
+ end
86
+
87
+ end
88
+
89
+ # Include utilities
90
+ include Methods
91
+
92
+ end
93
+
94
+ end
@@ -0,0 +1,251 @@
1
+ require 'stringio'
2
+ require 'wlang/rule'
3
+ require 'wlang/rule_set'
4
+ require 'wlang/errors'
5
+ require 'wlang/template'
6
+ module WLang
7
+
8
+ #
9
+ # Parser for wlang templates.
10
+ #
11
+ # This class implements the parsing algorithm of wlang, recognizing special tags
12
+ # and replacing them using installed rules. Instanciating a template is done
13
+ # using instantiate. All other methods (parse, parse_block, has_block?) and the
14
+ # like are callbacks for rules and should not be used by users themselve.
15
+ #
16
+ # Obtaining a parser MUST be made through Parser.instantiator (new is private).
17
+ #
18
+ # == Detailed API
19
+ class Parser
20
+
21
+ # Factors a parser instance for a given template and an output buffer.
22
+ def self.instantiator(template, buffer=nil)
23
+ Parser.send(:new, nil, template, nil, 0, buffer)
24
+ end
25
+
26
+ # Current parsed template
27
+ attr_reader :template
28
+
29
+ # Current execution context
30
+ attr_reader :context
31
+
32
+ # Current buffer
33
+ attr_reader :buffer
34
+
35
+ #
36
+ # Initializes a parser instance. _parent_ is the Parser instance of the higher
37
+ # parsing stage. _template_ is the current instantiated template, _offset_ is
38
+ # where the parsing must start in the template and _buffer_ is the output buffer
39
+ # where the instantiation result must be pushed.
40
+ #
41
+ def initialize(parent, template, dialect, offset, buffer)
42
+ raise(ArgumentError, "Template is mandatory") unless WLang::Template===template
43
+ raise(ArgumentError, "Offset is mandatory") unless Integer===offset
44
+ dialect = template.dialect if dialect.nil?
45
+ buffer = dialect.factor_buffer if buffer.nil?
46
+ raise(ArgumentError, "Buffer is mandatory") unless buffer.respond_to?(:<<)
47
+ @parent = parent
48
+ @template = template
49
+ @context = template.context
50
+ @offset = offset
51
+ @dialect = dialect
52
+ @buffer = buffer
53
+ end
54
+
55
+ # Factors a specific buffer on the current dialect
56
+ def factor_buffer
57
+ @dialect.factor_buffer
58
+ end
59
+
60
+ # Appends on a given buffer
61
+ def append_buffer(buffer, str, block)
62
+ if buffer.respond_to?(:wlang_append)
63
+ buffer.wlang_append(str, block)
64
+ else
65
+ buffer << str
66
+ end
67
+ end
68
+
69
+ # Pushes a given string on the output buffer
70
+ def <<(str, block)
71
+ append_buffer(@buffer, str, block)
72
+ end
73
+
74
+ # Parses the text
75
+ def instantiate
76
+ # Main variables:
77
+ # - offset: matching current position
78
+ # - rules: handlers of '{' currently opened
79
+ offset, pattern, rules = @offset, @dialect.pattern(@template.block_symbols), []
80
+ @source_text = template.source_text
81
+
82
+ # we start matching everything in the ruleset
83
+ while match_at=@source_text.index(pattern,offset)
84
+ match, match_length = $~[0], $~[0].length
85
+
86
+ # puts pre_match (we can't use $~.pre_match !)
87
+ self.<<(@source_text[offset, match_at-offset], false) if match_at>0
88
+
89
+ if @source_text[match_at,1]=='\\' # escaping sequence
90
+ self.<<(match[1..-1], false)
91
+ offset = match_at + match_length
92
+
93
+ elsif match.length==1 # simple '{' or '}' here
94
+ offset = match_at + match_length
95
+ if match==Template::BLOCK_SYMBOLS[template.block_symbols][0]
96
+ self.<<(match, false) # simple '{' are always pushed
97
+ # we push '{' in rules to recognize it's associated '}'
98
+ # that must be pushed on buffer also
99
+ rules << match
100
+ else
101
+ # end of my job if I can't pop a previous rule
102
+ break if rules.empty?
103
+ # otherwise, push '}' only if associated to a simple '{'
104
+ self.<<(match, false) unless Rule===rules.pop
105
+ end
106
+
107
+ elsif match[-1,1]==Template::BLOCK_SYMBOLS[template.block_symbols][0] # opening special tag
108
+ # following line should never return nil as the matching pattern comes
109
+ # from the ruleset itself!
110
+ rule = @dialect.ruleset[match[0..-2]]
111
+ rules << rule
112
+
113
+ # lauch that rule, get it's replacement and my new offset
114
+ replacement, offset = rule.start_tag(self, match_at + match_length)
115
+ replacement = "" if replacement.nil?
116
+ raise "Bad implementation of rule #{match[0..-2]}" if offset.nil?
117
+
118
+ # push replacement
119
+ self.<<(replacement, true) unless replacement.empty?
120
+ end
121
+
122
+ end # while match_at=...
123
+
124
+ # trailing data (end of @template reached only if no match_at)
125
+ unless match_at
126
+ unexpected_eof(@source_text.length, '}') unless rules.empty?
127
+ self.<<(@source_text[offset, 1+@source_text.length-offset], false)
128
+ offset = @source_text.length
129
+ end
130
+ [@buffer, offset-1]
131
+ end
132
+
133
+ #
134
+ # Evaluates a ruby expression on the current context.
135
+ # See WLang::Parser::Context#evaluate.
136
+ #
137
+ def evaluate(expression)
138
+ @context.evaluate(expression)
139
+ end
140
+
141
+ #
142
+ # Launches a child parser for instantiation at a given _offset_ in given
143
+ # _dialect_ (same dialect than self if dialect is nil) and with an output
144
+ # _buffer_.
145
+ #
146
+ def parse(offset, dialect=nil, buffer=nil)
147
+ if dialect.nil?
148
+ dialect = @dialect
149
+ elsif String===dialect
150
+ dname, dialect = dialect, WLang::dialect(dialect)
151
+ raise(ParseError,"Unknown modulation dialect: #{dname}") if dialect.nil?
152
+ elsif not(Dialect===dialect)
153
+ raise(ParseError,"Unknown modulation dialect: #{dialect}")
154
+ end
155
+ Parser.send(:new, self, @template, dialect, offset, buffer).instantiate
156
+ end
157
+
158
+ #
159
+ # Checks if a given offset is a starting block. For easy implementation of rules
160
+ # the check applied here is that text starting at _offset_ in the template is precisely
161
+ # '}{' (the reason for that is that instantiate, parse, parse_block always stop
162
+ # parsing on a '}')
163
+ #
164
+ def has_block?(offset)
165
+ @source_text[offset,2]=='}{'
166
+ end
167
+
168
+ #
169
+ # Parses a given block starting at a given _offset_, expressed in a given
170
+ # _dialect_ and using an output _buffer_. This method raises a ParseError if
171
+ # there is no block at the offset. It implies that we are on a '}{', see
172
+ # has_block? for details. Rules may thus force mandatory block parsing without
173
+ # having to check anything. Optional blocks must be handled by rules themselve.
174
+ #
175
+ def parse_block(offset, dialect=nil, buffer=nil)
176
+ block_missing_error(offset+2) unless has_block?(offset)
177
+ parse(offset+2, dialect, buffer)
178
+ end
179
+
180
+ #
181
+ # Encodes a given text using an encoder, that may be a qualified name or an
182
+ # Encoder instance.
183
+ #
184
+ def encode(src, encoder, options=nil)
185
+ options = {} unless options
186
+ options['_encoder_'] = encoder
187
+ options['_template_'] = template
188
+ if String===encoder
189
+ if encoder.include?("/")
190
+ ename, encoder = encoder, WLang::encoder(encoder)
191
+ raise(ParseError,"Unknown encoder: #{ename}") if encoder.nil?
192
+ else
193
+ ename, encoder = encoder, @dialect.find_encoder(encoder)
194
+ raise(ParseError,"Unknown encoder: #{ename}") if encoder.nil?
195
+ end
196
+ elsif not(Encoder===encoder)
197
+ raise(ParseError,"Unknown encoder: #{encoder}")
198
+ end
199
+ encoder.encode(src, options)
200
+ end
201
+
202
+ #
203
+ # Raises a ParseError at a given offset.
204
+ #
205
+ def syntax_error(offset, msg=nil)
206
+ text = self.parse(offset, "wlang/dummy", "")
207
+ raise ParseError, "Parse error at #{offset} on '#{text}': #{msg}"
208
+ end
209
+
210
+ #
211
+ # Raises a ParseError at a given offset for a missing block
212
+ #
213
+ def block_missing_error(offset)
214
+ raise ParseError.new("Block expected", offset, @source_text)
215
+ end
216
+
217
+ #
218
+ # Raises a ParseError at a given offset for a unexpected EOF
219
+ # specif. the expected character when EOF found
220
+ #
221
+ def unexpected_eof(offset, expected)
222
+ raise ParseError.new("'#{expected}' expected, EOF found.", offset, @source_text)
223
+ end
224
+
225
+ #
226
+ # Puts a key/value pair in the current context. See Parser::Context::define
227
+ # for details.
228
+ #
229
+ def context_define(key, value)
230
+ @context.define(key,value)
231
+ end
232
+
233
+ #
234
+ # Pushes a new scope on the current context stack. See Parser::Context::push
235
+ # for details.
236
+ #
237
+ def context_push(context)
238
+ @context.push(context)
239
+ end
240
+
241
+ #
242
+ # Pops the top scope of the context stack. See Parser::Context::pop for details.
243
+ #
244
+ def context_pop
245
+ @context.pop
246
+ end
247
+
248
+ private_class_method :new
249
+ end # class Parser
250
+
251
+ end # module WLang