wlang 0.8.5 → 0.9.1
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/CHANGELOG.rdoc +65 -0
- data/README.rdoc +85 -31
- data/bin/wlang +5 -0
- data/doc/specification/dialect.wtpl +14 -0
- data/doc/specification/dialects.wtpl +3 -0
- data/doc/specification/glossary.wtpl +4 -4
- data/doc/specification/rulesets.wtpl +9 -9
- data/doc/specification/specification.css +1 -0
- data/doc/specification/specification.html +68 -5
- data/doc/specification/specification.wtpl +12 -12
- data/doc/specification/specification.yml +9 -9
- data/doc/specification/symbols.wtpl +8 -8
- data/lib/wlang.rb +297 -75
- data/lib/wlang/dialect.rb +7 -3
- data/lib/wlang/dialects/coderay_dialect.rb +4 -4
- data/lib/wlang/dialects/plain_text_dialect.rb +13 -19
- data/lib/wlang/dialects/redcloth_dialect.rb +16 -0
- data/lib/wlang/dialects/ruby_dialect.rb +16 -2
- data/lib/wlang/dialects/standard_dialects.rb +20 -0
- data/lib/wlang/dialects/xhtml_dialect.rb +24 -1
- data/lib/wlang/encoder.rb +1 -1
- data/lib/wlang/encoder_set.rb +5 -0
- data/lib/wlang/errors.rb +70 -6
- data/lib/wlang/ext/hash_methodize.rb +13 -0
- data/lib/wlang/{ruby_extensions.rb → ext/string.rb} +9 -5
- data/lib/wlang/hash_scope.rb +89 -0
- data/lib/wlang/hosted_language.rb +146 -0
- data/lib/wlang/parser.rb +189 -126
- data/lib/wlang/parser_state.rb +94 -0
- data/lib/wlang/rule_set.rb +16 -3
- data/lib/wlang/rulesets/basic_ruleset.rb +14 -6
- data/lib/wlang/rulesets/buffering_ruleset.rb +20 -29
- data/lib/wlang/rulesets/context_ruleset.rb +16 -20
- data/lib/wlang/rulesets/imperative_ruleset.rb +4 -4
- data/lib/wlang/rulesets/ruleset_utils.rb +26 -5
- data/lib/wlang/template.rb +16 -34
- data/lib/wlang/wlang_command.rb +4 -7
- data/lib/wlang/wlang_command_options.rb +5 -0
- data/test/blackbox/basic/execution_1.exp +1 -0
- data/test/blackbox/basic/execution_1.tpl +1 -0
- data/test/blackbox/basic/execution_2.exp +1 -0
- data/test/blackbox/basic/execution_2.tpl +1 -0
- data/test/blackbox/basic/execution_3.exp +1 -0
- data/test/blackbox/basic/execution_3.tpl +1 -0
- data/test/blackbox/basic/execution_4.exp +1 -0
- data/test/blackbox/basic/execution_4.tpl +1 -0
- data/test/blackbox/basic/inclusion_1.exp +1 -0
- data/test/blackbox/basic/inclusion_1.tpl +1 -0
- data/test/blackbox/basic/inclusion_2.exp +1 -0
- data/test/blackbox/basic/inclusion_2.tpl +1 -0
- data/test/blackbox/basic/injection_1.exp +1 -0
- data/test/blackbox/basic/injection_1.tpl +1 -0
- data/test/blackbox/basic/injection_2.exp +1 -0
- data/test/blackbox/basic/injection_2.tpl +1 -0
- data/test/blackbox/basic/modulation_1.exp +1 -0
- data/test/blackbox/basic/modulation_1.tpl +1 -0
- data/test/blackbox/basic/modulation_2.exp +1 -0
- data/test/blackbox/basic/modulation_2.tpl +1 -0
- data/test/blackbox/basic/recursive_app_1.exp +1 -0
- data/test/blackbox/basic/recursive_app_1.tpl +1 -0
- data/test/blackbox/basic/recursive_app_2.exp +1 -0
- data/test/blackbox/basic/recursive_app_2.tpl +1 -0
- data/test/blackbox/buffering/data_1.rb +1 -0
- data/test/blackbox/buffering/data_assignment_1.exp +1 -0
- data/test/blackbox/buffering/data_assignment_1.tpl +1 -0
- data/test/blackbox/buffering/data_assignment_2.exp +1 -0
- data/test/blackbox/buffering/data_assignment_2.tpl +1 -0
- data/test/blackbox/buffering/data_assignment_3.exp +1 -0
- data/test/blackbox/buffering/data_assignment_3.tpl +1 -0
- data/test/blackbox/buffering/data_assignment_4.exp +1 -0
- data/test/blackbox/buffering/data_assignment_4.tpl +1 -0
- data/test/blackbox/buffering/input_1.exp +1 -0
- data/test/blackbox/buffering/input_1.tpl +1 -0
- data/test/blackbox/buffering/input_2.exp +1 -0
- data/test/blackbox/buffering/input_2.tpl +1 -0
- data/test/blackbox/buffering/input_3.exp +1 -0
- data/test/blackbox/buffering/input_3.tpl +1 -0
- data/test/blackbox/buffering/input_inclusion.exp +1 -0
- data/test/blackbox/buffering/input_inclusion.tpl +1 -0
- data/test/blackbox/buffering/input_inclusion_1.exp +0 -0
- data/test/blackbox/buffering/input_inclusion_1.tpl +1 -0
- data/test/blackbox/buffering/input_inclusion_2.exp +1 -0
- data/test/blackbox/buffering/input_inclusion_2.tpl +1 -0
- data/test/blackbox/buffering/input_inclusion_3.exp +1 -0
- data/test/blackbox/buffering/input_inclusion_3.tpl +1 -0
- data/test/blackbox/buffering/input_inclusion_4.exp +0 -0
- data/test/blackbox/buffering/input_inclusion_4.tpl +1 -0
- data/test/blackbox/buffering/input_inclusion_5.exp +1 -0
- data/test/blackbox/buffering/input_inclusion_5.tpl +1 -0
- data/test/blackbox/buffering/input_inclusion_6.exp +1 -0
- data/test/blackbox/buffering/input_inclusion_6.tpl +1 -0
- data/test/blackbox/buffering/input_inclusion_7.exp +0 -0
- data/test/blackbox/buffering/input_inclusion_7.tpl +1 -0
- data/test/blackbox/buffering/text_1.txt +1 -0
- data/test/blackbox/buffering/wlang.txt +1 -0
- data/test/blackbox/context/assignment_1.exp +1 -0
- data/test/blackbox/context/assignment_1.tpl +1 -0
- data/test/blackbox/context/assignment_2.exp +1 -0
- data/test/blackbox/context/assignment_2.tpl +1 -0
- data/test/blackbox/context/assignment_3.exp +2 -0
- data/test/blackbox/context/assignment_3.tpl +2 -0
- data/test/blackbox/context/assignment_4.exp +1 -0
- data/test/blackbox/context/assignment_4.tpl +1 -0
- data/test/blackbox/context/block_assignment_1.exp +1 -0
- data/test/blackbox/context/block_assignment_1.tpl +1 -0
- data/test/blackbox/context/block_assignment_2.exp +1 -0
- data/test/blackbox/context/block_assignment_2.tpl +1 -0
- data/test/blackbox/context/modulo_assignment_1.exp +1 -0
- data/test/blackbox/context/modulo_assignment_1.tpl +1 -0
- data/test/blackbox/context/modulo_assignment_2.exp +1 -0
- data/test/blackbox/context/modulo_assignment_2.tpl +1 -0
- data/test/blackbox/data_1.rb +1 -0
- data/test/blackbox/test_all.rb +59 -0
- data/test/spec/basic_object.spec +40 -0
- data/test/spec/global_extensions.rb +2 -0
- data/test/spec/hash_scope.spec +76 -0
- data/test/spec/redcloth_dialect.spec +24 -0
- data/test/spec/test_all.rb +8 -0
- data/test/spec/wlang.spec +53 -0
- data/test/spec/xhtml_dialect.spec +23 -0
- data/test/{test_all.rb → unit/test_all.rb} +1 -1
- data/test/{wlang → unit/wlang}/anagram_bugs_test.rb +2 -2
- data/test/{wlang → unit/wlang}/basic_ruleset_test.rb +1 -1
- data/test/{wlang → unit/wlang}/buffering_ruleset_test.rb +4 -4
- data/test/{wlang → unit/wlang}/buffering_template1.wtpl +0 -0
- data/test/{wlang → unit/wlang}/buffering_template2.wtpl +0 -0
- data/test/{wlang → unit/wlang}/buffering_template3.wtpl +0 -0
- data/test/unit/wlang/buffering_template4.wtpl +1 -0
- data/test/unit/wlang/buffering_template5.wtpl +1 -0
- data/test/{wlang → unit/wlang}/context_ruleset_test.rb +0 -0
- data/test/{wlang → unit/wlang}/data.rb +0 -0
- data/test/{wlang → unit/wlang}/encoder_set_test.rb +0 -0
- data/test/{wlang → unit/wlang}/imperative_ruleset_test.rb +0 -0
- data/test/{wlang → unit/wlang}/intelligent_buffer_test.rb +0 -0
- data/test/{wlang → unit/wlang}/othersymbols_test.rb +0 -0
- data/test/{wlang → unit/wlang}/parser_test.rb +10 -11
- data/test/{wlang → unit/wlang}/plain_text_dialect_test.rb +0 -0
- data/test/{wlang → unit/wlang}/ruby_dialect_test.rb +0 -0
- data/test/{wlang → unit/wlang}/ruby_expected.rb +0 -0
- data/test/{wlang → unit/wlang}/ruby_template.wrb +0 -0
- data/test/{wlang → unit/wlang}/ruleset_utils_test.rb +0 -0
- data/test/{wlang → unit/wlang}/specification_examples_test.rb +2 -2
- data/test/{wlang → unit/wlang}/test_utils.rb +1 -1
- data/test/{wlang → unit/wlang}/wlang_test.rb +0 -0
- metadata +135 -42
- data/lib/wlang/basic_object.rb +0 -19
- data/lib/wlang/parser_context.rb +0 -139
- data/test/sandbox.rb +0 -1
- data/test/wlang/buffering_template4.wtpl +0 -1
- data/test/wlang/buffering_template5.wtpl +0 -1
- data/test/wlang/parser_context_test.rb +0 -29
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
module WLang
|
|
2
|
+
#
|
|
3
|
+
# Implements the hosted language abstraction of WLang. The hosted language is
|
|
4
|
+
# mainly responsible of evaluating expressions (see evaluate). This abstraction
|
|
5
|
+
# may be implemented by a user's own class, providing that it respected the
|
|
6
|
+
# evaluate method specification.
|
|
7
|
+
#
|
|
8
|
+
# This default implementation implements the ruby hosted language. It works
|
|
9
|
+
# with ::WLang::HostedLanguage::DSL, which uses instance_eval for evaluating
|
|
10
|
+
# the expression. Calls to missing methods (without parameter and block) are
|
|
11
|
+
# converted in scope lookups. The DSL class is strictly private, as it uses
|
|
12
|
+
# a somewhat complex ruby introspection mechanism to ensure that scoping will
|
|
13
|
+
# not be perturbated by Kernel methods and Object private methods even if
|
|
14
|
+
# additional gems are loaded later.
|
|
15
|
+
#
|
|
16
|
+
# If you want to intall low-priority variables and tools available in all wlang
|
|
17
|
+
# templates (global scoping) you can reopen the HostedLanguage class itself and
|
|
18
|
+
# override variable_missing. Note that "low-priority" means that these methods
|
|
19
|
+
# will be hidden if a user installs a variable with the same name in its template.
|
|
20
|
+
#
|
|
21
|
+
# class WLang::HostedLanguage
|
|
22
|
+
#
|
|
23
|
+
# # Low-priority now variable
|
|
24
|
+
# def now
|
|
25
|
+
# Time.now
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# # Low-priority variables are checked before raising an UndefinedVariableError
|
|
29
|
+
# def variable_missing(name)
|
|
30
|
+
# case name
|
|
31
|
+
# when :who, ...
|
|
32
|
+
# self.send(name)
|
|
33
|
+
# else
|
|
34
|
+
# raise ::WLang::UndefinedVariableError.new(nil, nil, nil, name)
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# This class is thread safe, meaning that the same hosting language instance may be
|
|
41
|
+
# safely shared by concurrent wlang parsers. Extending or re-opening this class and using
|
|
42
|
+
# instance variables will make it non thread-safe.
|
|
43
|
+
#
|
|
44
|
+
class HostedLanguage
|
|
45
|
+
|
|
46
|
+
# The hosted language DSL, interpreting expressions
|
|
47
|
+
class DSL
|
|
48
|
+
|
|
49
|
+
# Methods that we keep
|
|
50
|
+
KEPT_METHODS = [ "__send__", "__id__", "instance_eval", "initialize", "object_id",
|
|
51
|
+
"singleton_method_added", "singleton_method_undefined", "method_missing",
|
|
52
|
+
"__evaluate__", "knows?"]
|
|
53
|
+
|
|
54
|
+
class << self
|
|
55
|
+
def __clean_scope__
|
|
56
|
+
# Removes all methods that are not needed to the class
|
|
57
|
+
(instance_methods + private_instance_methods).each do |m|
|
|
58
|
+
m_to_s = m.to_s
|
|
59
|
+
undef_method(m_to_s.to_sym) unless ('__' == m_to_s[0..1]) or KEPT_METHODS.include?(m_to_s)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Creates a DSL instance for a given hosted language and
|
|
65
|
+
# parser state
|
|
66
|
+
def initialize(hosted, parser_state)
|
|
67
|
+
@hosted, @parser_state = hosted, parser_state
|
|
68
|
+
class << self
|
|
69
|
+
__clean_scope__
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Delegates the missing lookup to the current parser scope or raises an
|
|
74
|
+
# UndefinedVariableError (calls @hosted.variable_missing precisely)
|
|
75
|
+
def method_missing(name, *args, &block)
|
|
76
|
+
if @parser_state and args.empty? and block.nil?
|
|
77
|
+
if effname = knows?(name)
|
|
78
|
+
@parser_state.scope[effname]
|
|
79
|
+
else
|
|
80
|
+
@hosted.variable_missing(name)
|
|
81
|
+
end
|
|
82
|
+
else
|
|
83
|
+
super(name, *args, &block)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Checks if a variable is known in the current parser scope
|
|
88
|
+
def knows?(name)
|
|
89
|
+
if @parser_state.scope.has_key?(name)
|
|
90
|
+
name
|
|
91
|
+
elsif @parser_state.scope.has_key?(name.to_s)
|
|
92
|
+
name.to_s
|
|
93
|
+
else
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Evaluates an expression
|
|
99
|
+
def __evaluate__(__expression__)
|
|
100
|
+
__result__ = instance_eval(__expression__)
|
|
101
|
+
|
|
102
|
+
# backward compatibility with >= 0.8.4 where 'using self'
|
|
103
|
+
# was allowed. This will be removed in wlang 1.0.0
|
|
104
|
+
if __result__.object_id == self.object_id
|
|
105
|
+
Kernel.puts "Warning: using deprecated 'using self' syntax (#{@parser_state.where})\n"\
|
|
106
|
+
"will be removed in wlang 1.0.0. Use 'share all', extends "\
|
|
107
|
+
"::WLang::HostedLanguage::DSL with useful methods or create your own"\
|
|
108
|
+
" hosted language."
|
|
109
|
+
__result__ = @parser_state.scope.to_h
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
__result__
|
|
113
|
+
rescue ::WLang::Error => ex
|
|
114
|
+
ex.parser_state = @parser_state
|
|
115
|
+
ex.expression = __expression__ if ex.respond_to?(:expression=)
|
|
116
|
+
Kernel.raise ex
|
|
117
|
+
rescue Exception => ex
|
|
118
|
+
Kernel.raise ::WLang::EvalError.new(ex.message, @parser_state, __expression__, ex)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
end # class DSL
|
|
122
|
+
|
|
123
|
+
#
|
|
124
|
+
# Called when a variable cannot be found (name is a Symbol object). This default
|
|
125
|
+
# implementation raises an UndefinedVariableError. This method is intended to be
|
|
126
|
+
# overriden for handling such a situation more friendly or for installing
|
|
127
|
+
# low-priority global variables (see class documentation).
|
|
128
|
+
#
|
|
129
|
+
def variable_missing(name)
|
|
130
|
+
raise ::WLang::UndefinedVariableError.new(nil, nil, nil, name)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
#
|
|
134
|
+
# Evaluates a given expression in the context of a given
|
|
135
|
+
# parser state.
|
|
136
|
+
#
|
|
137
|
+
# This method should always raise
|
|
138
|
+
# - an UndefinedVariableError when a given template variable cannot be found.
|
|
139
|
+
# - an EvalError when something more severe occurs
|
|
140
|
+
#
|
|
141
|
+
def evaluate(expression, parser_state)
|
|
142
|
+
::WLang::HostedLanguage::DSL.new(self, parser_state).__evaluate__(expression)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
end # class HostedLanguage
|
|
146
|
+
end # module WLang
|
data/lib/wlang/parser.rb
CHANGED
|
@@ -4,7 +4,6 @@ require 'wlang/rule_set'
|
|
|
4
4
|
require 'wlang/errors'
|
|
5
5
|
require 'wlang/template'
|
|
6
6
|
module WLang
|
|
7
|
-
|
|
8
7
|
#
|
|
9
8
|
# Parser for wlang templates.
|
|
10
9
|
#
|
|
@@ -13,86 +12,143 @@ module WLang
|
|
|
13
12
|
# using instantiate. All other methods (parse, parse_block, has_block?) and the
|
|
14
13
|
# like are callbacks for rules and should not be used by users themselve.
|
|
15
14
|
#
|
|
16
|
-
# Obtaining a parser MUST be made through Parser.instantiator (new is private).
|
|
17
|
-
#
|
|
18
15
|
# == Detailed API
|
|
19
16
|
class Parser
|
|
20
17
|
|
|
21
|
-
#
|
|
22
|
-
def
|
|
23
|
-
|
|
18
|
+
# Initializes a parser instance.
|
|
19
|
+
def initialize(hosted, template, scope)
|
|
20
|
+
raise(ArgumentError, "Hosted language is mandatory (a ::WLang::HostedLanguage)") unless ::WLang::HostedLanguage===hosted
|
|
21
|
+
raise(ArgumentError, "Template is mandatory (a ::WLang::Template)") unless ::WLang::Template===template
|
|
22
|
+
raise(ArgumentError, "Scope is mandatory (a Hash)") unless ::Hash===scope
|
|
23
|
+
@state = ::WLang::Parser::State.new(self).branch(
|
|
24
|
+
:hosted => hosted,
|
|
25
|
+
:template => template,
|
|
26
|
+
:dialect => template.dialect,
|
|
27
|
+
:offset => 0,
|
|
28
|
+
:shared => :none,
|
|
29
|
+
:scope => scope,
|
|
30
|
+
:buffer => template.dialect.factor_buffer)
|
|
24
31
|
end
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
#
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
32
|
+
|
|
33
|
+
###################################################################### Facade on the parser state
|
|
34
|
+
|
|
35
|
+
# Returns the current parser state
|
|
36
|
+
def state(); @state; end
|
|
37
|
+
|
|
38
|
+
# Returns the current template
|
|
39
|
+
def template() state.template; end
|
|
40
|
+
|
|
41
|
+
# Returns the current buffer
|
|
42
|
+
def dialect() state.dialect; end
|
|
43
|
+
|
|
44
|
+
# Returns the current template's source text
|
|
45
|
+
def source_text() state.template.source_text; end
|
|
46
|
+
|
|
47
|
+
# Returns the current offset
|
|
48
|
+
def offset() state.offset; end
|
|
49
|
+
|
|
50
|
+
# Sets the current offset of the parser
|
|
51
|
+
def offset=(offset) state.offset = offset; end
|
|
52
|
+
|
|
53
|
+
# Returns the current buffer
|
|
54
|
+
def buffer() state.buffer; end
|
|
55
|
+
|
|
56
|
+
# Returns the current hosted language
|
|
57
|
+
def hosted() state.hosted; end
|
|
58
|
+
|
|
59
|
+
# Branches the current parser
|
|
60
|
+
def branch(opts = {})
|
|
61
|
+
raise ArgumentError, "Parser branching requires a block" unless block_given?
|
|
62
|
+
@state = @state.branch(opts)
|
|
63
|
+
result = yield(@state)
|
|
64
|
+
@state = @state.parent
|
|
65
|
+
result
|
|
53
66
|
end
|
|
54
67
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
68
|
+
###################################################################### Facade on the file system
|
|
69
|
+
|
|
70
|
+
# Resolves an URI throught the current template
|
|
71
|
+
def file_resolve(uri)
|
|
72
|
+
# TODO: refactor me to handle absolute URIs
|
|
73
|
+
template.file_resolve(uri)
|
|
58
74
|
end
|
|
59
75
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
###################################################################### Facade on wlang itself
|
|
77
|
+
|
|
78
|
+
# Factors a template instance for a given file
|
|
79
|
+
def file_template(file, dialect = nil, block_symbols = :braces)
|
|
80
|
+
WLang::file_template(file, dialect, block_symbols)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Finds a real dialect instance from an argument (Dialect instance or
|
|
84
|
+
# qualified name)
|
|
85
|
+
def ensure_dialect(dialect)
|
|
86
|
+
if String===dialect
|
|
87
|
+
dname, dialect = dialect, WLang::dialect(dialect)
|
|
88
|
+
raise(ParseError,"Unknown modulation dialect: #{dname}") if dialect.nil?
|
|
89
|
+
elsif not(Dialect===dialect)
|
|
90
|
+
raise(ParseError,"Unknown modulation dialect: #{dialect}")
|
|
91
|
+
end
|
|
92
|
+
dialect
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Finds a real ecoder instance from an argument (Encoder instance or
|
|
96
|
+
# qualified or unqualified name)
|
|
97
|
+
def ensure_encoder(encoder)
|
|
98
|
+
if String===encoder
|
|
99
|
+
if encoder.include?("/")
|
|
100
|
+
ename, encoder = encoder, WLang::encoder(encoder)
|
|
101
|
+
raise(ParseError,"Unknown encoder: #{ename}") if encoder.nil?
|
|
102
|
+
else
|
|
103
|
+
ename, encoder = encoder, self.dialect.find_encoder(encoder)
|
|
104
|
+
raise(ParseError,"Unknown encoder: #{ename}") if encoder.nil?
|
|
105
|
+
end
|
|
106
|
+
elsif not(Encoder===encoder)
|
|
107
|
+
raise(ParseError,"Unknown encoder: #{encoder}")
|
|
66
108
|
end
|
|
109
|
+
encoder
|
|
67
110
|
end
|
|
68
111
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
112
|
+
###################################################################### Main parser methods
|
|
113
|
+
|
|
114
|
+
# Checks the result of a given rule
|
|
115
|
+
def launch_rule(dialect, rule_symbol, rule, offset)
|
|
116
|
+
result = rule.start_tag(self, offset)
|
|
117
|
+
raise WLang::Error, "Bad rule implementation #{dialect.qualified_name} #{rule_symbol}{}\n#{result.inspect}"\
|
|
118
|
+
unless result.size == 2 and String===result[0] and Integer===result[1]
|
|
119
|
+
result
|
|
72
120
|
end
|
|
73
|
-
|
|
74
|
-
# Parses the text
|
|
121
|
+
|
|
122
|
+
# Parses the template's text and instantiate it
|
|
75
123
|
def instantiate
|
|
76
|
-
# Main variables:
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
124
|
+
# Main variables put in local scope for efficiency:
|
|
125
|
+
# - template: current parsed template
|
|
126
|
+
# - source_text: current template's source text
|
|
127
|
+
# - offset: matching current position
|
|
128
|
+
# - pattern: current dialect's regexp pattern
|
|
129
|
+
# - rules: handlers of '{' currently opened
|
|
130
|
+
template = self.template
|
|
131
|
+
symbols = self.template.block_symbols
|
|
132
|
+
source_text = self.source_text
|
|
133
|
+
dialect = self.dialect
|
|
134
|
+
buffer = self.buffer
|
|
135
|
+
pattern = dialect.pattern(template.block_symbols)
|
|
136
|
+
rules = []
|
|
81
137
|
|
|
82
138
|
# we start matching everything in the ruleset
|
|
83
|
-
while match_at
|
|
139
|
+
while match_at=source_text.index(pattern, self.offset)
|
|
84
140
|
match, match_length = $~[0], $~[0].length
|
|
85
141
|
|
|
86
142
|
# puts pre_match (we can't use $~.pre_match !)
|
|
87
|
-
self.<<(
|
|
143
|
+
self.<<(source_text[self.offset, match_at-self.offset], false) if match_at>0
|
|
88
144
|
|
|
89
|
-
if
|
|
145
|
+
if source_text[match_at,1]=='\\' # escaping sequence
|
|
90
146
|
self.<<(match[1..-1], false)
|
|
91
|
-
offset = match_at + match_length
|
|
147
|
+
self.offset = match_at + match_length
|
|
92
148
|
|
|
93
149
|
elsif match.length==1 # simple '{' or '}' here
|
|
94
|
-
offset = match_at + match_length
|
|
95
|
-
if match==Template::BLOCK_SYMBOLS[
|
|
150
|
+
self.offset = match_at + match_length
|
|
151
|
+
if match==Template::BLOCK_SYMBOLS[symbols][0]
|
|
96
152
|
self.<<(match, false) # simple '{' are always pushed
|
|
97
153
|
# we push '{' in rules to recognize it's associated '}'
|
|
98
154
|
# that must be pushed on buffer also
|
|
@@ -104,41 +160,35 @@ module WLang
|
|
|
104
160
|
self.<<(match, false) unless Rule===rules.pop
|
|
105
161
|
end
|
|
106
162
|
|
|
107
|
-
elsif match[-1,1]==Template::BLOCK_SYMBOLS[
|
|
163
|
+
elsif match[-1,1]==Template::BLOCK_SYMBOLS[symbols][0] # opening special tag
|
|
108
164
|
# following line should never return nil as the matching pattern comes
|
|
109
165
|
# from the ruleset itself!
|
|
110
|
-
|
|
166
|
+
rule_symbol = match[0..-2]
|
|
167
|
+
rule = dialect.ruleset[rule_symbol]
|
|
111
168
|
rules << rule
|
|
112
169
|
|
|
170
|
+
# Just added to get the last position in case of an error
|
|
171
|
+
self.offset = match_at + match_length
|
|
172
|
+
|
|
113
173
|
# lauch that rule, get it's replacement and my new offset
|
|
114
|
-
replacement, offset =
|
|
115
|
-
|
|
116
|
-
raise "Bad implementation of rule #{match[0..-2]}" if offset.nil?
|
|
117
|
-
|
|
174
|
+
replacement, self.offset = launch_rule(dialect, rule_symbol, rule, self.offset)
|
|
175
|
+
|
|
118
176
|
# push replacement
|
|
119
177
|
self.<<(replacement, true) unless replacement.empty?
|
|
120
178
|
end
|
|
121
179
|
|
|
122
180
|
end # while match_at=...
|
|
123
181
|
|
|
124
|
-
# trailing data (end of
|
|
182
|
+
# trailing data (end of template reached only if no match_at)
|
|
125
183
|
unless match_at
|
|
126
|
-
unexpected_eof(
|
|
127
|
-
self.<<(
|
|
128
|
-
offset =
|
|
184
|
+
unexpected_eof(source_text.length, '}') unless rules.empty?
|
|
185
|
+
self.<<(source_text[self.offset, 1+source_text.length-self.offset], false)
|
|
186
|
+
self.offset = source_text.length
|
|
129
187
|
end
|
|
130
|
-
[
|
|
188
|
+
[buffer, self.offset-1]
|
|
131
189
|
end
|
|
132
190
|
|
|
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
|
-
rescue Exception => ex
|
|
140
|
-
raise ::WLang::EvalError, "#{template.where(@offset)} evaluation of '#{expression}' failed", ex.backtrace
|
|
141
|
-
end
|
|
191
|
+
###################################################################### Callbacks for rule sets
|
|
142
192
|
|
|
143
193
|
#
|
|
144
194
|
# Launches a child parser for instantiation at a given _offset_ in given
|
|
@@ -146,15 +196,11 @@ module WLang
|
|
|
146
196
|
# _buffer_.
|
|
147
197
|
#
|
|
148
198
|
def parse(offset, dialect=nil, buffer=nil)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
raise(ParseError,"Unknown modulation dialect: #{dname}") if dialect.nil?
|
|
154
|
-
elsif not(Dialect===dialect)
|
|
155
|
-
raise(ParseError,"Unknown modulation dialect: #{dialect}")
|
|
199
|
+
dialect = ensure_dialect(dialect.nil? ? self.dialect : dialect)
|
|
200
|
+
buffer = dialect.factor_buffer if buffer.nil?
|
|
201
|
+
branch(:offset => offset, :dialect => dialect, :buffer => buffer) do
|
|
202
|
+
instantiate
|
|
156
203
|
end
|
|
157
|
-
Parser.send(:new, self, @template, dialect, offset, buffer).instantiate
|
|
158
204
|
end
|
|
159
205
|
|
|
160
206
|
#
|
|
@@ -164,7 +210,7 @@ module WLang
|
|
|
164
210
|
# parsing on a '}')
|
|
165
211
|
#
|
|
166
212
|
def has_block?(offset)
|
|
167
|
-
|
|
213
|
+
self.source_text[offset,2]=='}{'
|
|
168
214
|
end
|
|
169
215
|
|
|
170
216
|
#
|
|
@@ -179,6 +225,54 @@ module WLang
|
|
|
179
225
|
parse(offset+2, dialect, buffer)
|
|
180
226
|
end
|
|
181
227
|
|
|
228
|
+
###################################################################### Facade on the buffer
|
|
229
|
+
|
|
230
|
+
# Appends on a given buffer
|
|
231
|
+
def append_buffer(buffer, str, block)
|
|
232
|
+
if buffer.respond_to?(:wlang_append)
|
|
233
|
+
buffer.wlang_append(str, block)
|
|
234
|
+
else
|
|
235
|
+
buffer << str
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Pushes a given string on the output buffer
|
|
240
|
+
def <<(str, block)
|
|
241
|
+
append_buffer(buffer, str, block)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
###################################################################### Facade on the scope
|
|
245
|
+
|
|
246
|
+
# Yields the block in a new scope branch, pushing pairing values on it.
|
|
247
|
+
# Original scope is restored after that. Returns what the yielded block
|
|
248
|
+
# returned.
|
|
249
|
+
def branch_scope(pairing = {}, which = :all)
|
|
250
|
+
raise ArgumentError, "Parser.branch_scope expects a block" unless block_given?
|
|
251
|
+
branch(:scope => pairing, :shared => which) { yield }
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Adds a key/value pair on the current scope.
|
|
255
|
+
def scope_define(key, value)
|
|
256
|
+
state.scope[key] = value
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
###################################################################### Facade on the hosted language
|
|
260
|
+
|
|
261
|
+
#
|
|
262
|
+
# Evaluates a ruby expression on the current context.
|
|
263
|
+
# See WLang::Parser::Context#evaluate.
|
|
264
|
+
#
|
|
265
|
+
def evaluate(expression)
|
|
266
|
+
hosted.evaluate(expression, state)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
###################################################################### Facade on the dialect
|
|
270
|
+
|
|
271
|
+
# Factors a specific buffer on the current dialect
|
|
272
|
+
def factor_buffer
|
|
273
|
+
self.dialect.factor_buffer
|
|
274
|
+
end
|
|
275
|
+
|
|
182
276
|
#
|
|
183
277
|
# Encodes a given text using an encoder, that may be a qualified name or an
|
|
184
278
|
# Encoder instance.
|
|
@@ -187,20 +281,11 @@ module WLang
|
|
|
187
281
|
options = {} unless options
|
|
188
282
|
options['_encoder_'] = encoder
|
|
189
283
|
options['_template_'] = template
|
|
190
|
-
|
|
191
|
-
if encoder.include?("/")
|
|
192
|
-
ename, encoder = encoder, WLang::encoder(encoder)
|
|
193
|
-
raise(ParseError,"Unknown encoder: #{ename}") if encoder.nil?
|
|
194
|
-
else
|
|
195
|
-
ename, encoder = encoder, @dialect.find_encoder(encoder)
|
|
196
|
-
raise(ParseError,"Unknown encoder: #{ename}") if encoder.nil?
|
|
197
|
-
end
|
|
198
|
-
elsif not(Encoder===encoder)
|
|
199
|
-
raise(ParseError,"Unknown encoder: #{encoder}")
|
|
200
|
-
end
|
|
201
|
-
encoder.encode(src, options)
|
|
284
|
+
ensure_encoder(encoder).encode(src, options)
|
|
202
285
|
end
|
|
203
286
|
|
|
287
|
+
###################################################################### About errors
|
|
288
|
+
|
|
204
289
|
# Raises an exception with a friendly message
|
|
205
290
|
def error(offset, message)
|
|
206
291
|
template.error(offset, message)
|
|
@@ -229,31 +314,9 @@ module WLang
|
|
|
229
314
|
def unexpected_eof(offset, expected)
|
|
230
315
|
template.parse_error(offset, "#{expected} expected, EOF found")
|
|
231
316
|
end
|
|
232
|
-
|
|
233
|
-
#
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
#
|
|
237
|
-
def context_define(key, value)
|
|
238
|
-
@context.define(key,value)
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
#
|
|
242
|
-
# Pushes a new scope on the current context stack. See Parser::Context::push
|
|
243
|
-
# for details.
|
|
244
|
-
#
|
|
245
|
-
def context_push(context)
|
|
246
|
-
@context.push(context)
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
#
|
|
250
|
-
# Pops the top scope of the context stack. See Parser::Context::pop for details.
|
|
251
|
-
#
|
|
252
|
-
def context_pop
|
|
253
|
-
@context.pop
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
private_class_method :new
|
|
317
|
+
|
|
318
|
+
# Protected methods are...
|
|
319
|
+
protected :hosted, :offset, :source_text, :buffer, :dialect
|
|
320
|
+
|
|
257
321
|
end # class Parser
|
|
258
|
-
|
|
259
322
|
end # module WLang
|