wlang 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|