wlang 0.8.4
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENCE.rdoc +25 -0
- data/README.rdoc +111 -0
- data/bin/wlang +24 -0
- data/doc/specification/about.rdoc +61 -0
- data/doc/specification/dialects.wtpl +0 -0
- data/doc/specification/examples.rb +3 -0
- data/doc/specification/glossary.wtpl +14 -0
- data/doc/specification/hosting.rdoc +0 -0
- data/doc/specification/overview.rdoc +116 -0
- data/doc/specification/rulesets.wtpl +87 -0
- data/doc/specification/specification.css +52 -0
- data/doc/specification/specification.html +1361 -0
- data/doc/specification/specification.js +8 -0
- data/doc/specification/specification.wtpl +41 -0
- data/doc/specification/specification.yml +430 -0
- data/doc/specification/symbols.wtpl +16 -0
- data/lib/wlang.rb +186 -0
- data/lib/wlang/basic_object.rb +19 -0
- data/lib/wlang/dialect.rb +230 -0
- data/lib/wlang/dialect_dsl.rb +136 -0
- data/lib/wlang/dialect_loader.rb +69 -0
- data/lib/wlang/dialects/coderay_dialect.rb +35 -0
- data/lib/wlang/dialects/plain_text_dialect.rb +75 -0
- data/lib/wlang/dialects/rdoc_dialect.rb +33 -0
- data/lib/wlang/dialects/ruby_dialect.rb +35 -0
- data/lib/wlang/dialects/sql_dialect.rb +38 -0
- data/lib/wlang/dialects/standard_dialects.rb +113 -0
- data/lib/wlang/dialects/xhtml_dialect.rb +40 -0
- data/lib/wlang/encoder.rb +66 -0
- data/lib/wlang/encoder_set.rb +117 -0
- data/lib/wlang/errors.rb +37 -0
- data/lib/wlang/intelligent_buffer.rb +94 -0
- data/lib/wlang/parser.rb +251 -0
- data/lib/wlang/parser_context.rb +146 -0
- data/lib/wlang/ruby_extensions.rb +21 -0
- data/lib/wlang/rule.rb +66 -0
- data/lib/wlang/rule_set.rb +93 -0
- data/lib/wlang/rulesets/basic_ruleset.rb +75 -0
- data/lib/wlang/rulesets/buffering_ruleset.rb +103 -0
- data/lib/wlang/rulesets/context_ruleset.rb +115 -0
- data/lib/wlang/rulesets/encoding_ruleset.rb +73 -0
- data/lib/wlang/rulesets/imperative_ruleset.rb +132 -0
- data/lib/wlang/rulesets/ruleset_utils.rb +296 -0
- data/lib/wlang/template.rb +79 -0
- data/lib/wlang/wlang_command.rb +54 -0
- data/lib/wlang/wlang_command_options.rb +158 -0
- data/test/sandbox.rb +1 -0
- data/test/test_all.rb +8 -0
- data/test/wlang/anagram_bugs_test.rb +111 -0
- data/test/wlang/basic_ruleset_test.rb +52 -0
- data/test/wlang/buffering_ruleset_test.rb +102 -0
- data/test/wlang/buffering_template1.wtpl +1 -0
- data/test/wlang/buffering_template2.wtpl +1 -0
- data/test/wlang/buffering_template3.wtpl +1 -0
- data/test/wlang/buffering_template4.wtpl +1 -0
- data/test/wlang/buffering_template5.wtpl +1 -0
- data/test/wlang/context_ruleset_test.rb +32 -0
- data/test/wlang/data.rb +3 -0
- data/test/wlang/encoder_set_test.rb +42 -0
- data/test/wlang/imperative_ruleset_test.rb +107 -0
- data/test/wlang/intelligent_buffer_test.rb +194 -0
- data/test/wlang/othersymbols_test.rb +16 -0
- data/test/wlang/parser_context_test.rb +29 -0
- data/test/wlang/parser_test.rb +89 -0
- data/test/wlang/plain_text_dialect_test.rb +21 -0
- data/test/wlang/ruby_dialect_test.rb +100 -0
- data/test/wlang/ruby_expected.rb +3 -0
- data/test/wlang/ruby_template.wrb +3 -0
- data/test/wlang/ruleset_utils_test.rb +245 -0
- data/test/wlang/specification_examples_test.rb +52 -0
- data/test/wlang/test_utils.rb +25 -0
- data/test/wlang/wlang_test.rb +80 -0
- 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
|
data/lib/wlang/errors.rb
ADDED
@@ -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
|
data/lib/wlang/parser.rb
ADDED
@@ -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
|