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.
Files changed (151) hide show
  1. data/CHANGELOG.rdoc +65 -0
  2. data/README.rdoc +85 -31
  3. data/bin/wlang +5 -0
  4. data/doc/specification/dialect.wtpl +14 -0
  5. data/doc/specification/dialects.wtpl +3 -0
  6. data/doc/specification/glossary.wtpl +4 -4
  7. data/doc/specification/rulesets.wtpl +9 -9
  8. data/doc/specification/specification.css +1 -0
  9. data/doc/specification/specification.html +68 -5
  10. data/doc/specification/specification.wtpl +12 -12
  11. data/doc/specification/specification.yml +9 -9
  12. data/doc/specification/symbols.wtpl +8 -8
  13. data/lib/wlang.rb +297 -75
  14. data/lib/wlang/dialect.rb +7 -3
  15. data/lib/wlang/dialects/coderay_dialect.rb +4 -4
  16. data/lib/wlang/dialects/plain_text_dialect.rb +13 -19
  17. data/lib/wlang/dialects/redcloth_dialect.rb +16 -0
  18. data/lib/wlang/dialects/ruby_dialect.rb +16 -2
  19. data/lib/wlang/dialects/standard_dialects.rb +20 -0
  20. data/lib/wlang/dialects/xhtml_dialect.rb +24 -1
  21. data/lib/wlang/encoder.rb +1 -1
  22. data/lib/wlang/encoder_set.rb +5 -0
  23. data/lib/wlang/errors.rb +70 -6
  24. data/lib/wlang/ext/hash_methodize.rb +13 -0
  25. data/lib/wlang/{ruby_extensions.rb → ext/string.rb} +9 -5
  26. data/lib/wlang/hash_scope.rb +89 -0
  27. data/lib/wlang/hosted_language.rb +146 -0
  28. data/lib/wlang/parser.rb +189 -126
  29. data/lib/wlang/parser_state.rb +94 -0
  30. data/lib/wlang/rule_set.rb +16 -3
  31. data/lib/wlang/rulesets/basic_ruleset.rb +14 -6
  32. data/lib/wlang/rulesets/buffering_ruleset.rb +20 -29
  33. data/lib/wlang/rulesets/context_ruleset.rb +16 -20
  34. data/lib/wlang/rulesets/imperative_ruleset.rb +4 -4
  35. data/lib/wlang/rulesets/ruleset_utils.rb +26 -5
  36. data/lib/wlang/template.rb +16 -34
  37. data/lib/wlang/wlang_command.rb +4 -7
  38. data/lib/wlang/wlang_command_options.rb +5 -0
  39. data/test/blackbox/basic/execution_1.exp +1 -0
  40. data/test/blackbox/basic/execution_1.tpl +1 -0
  41. data/test/blackbox/basic/execution_2.exp +1 -0
  42. data/test/blackbox/basic/execution_2.tpl +1 -0
  43. data/test/blackbox/basic/execution_3.exp +1 -0
  44. data/test/blackbox/basic/execution_3.tpl +1 -0
  45. data/test/blackbox/basic/execution_4.exp +1 -0
  46. data/test/blackbox/basic/execution_4.tpl +1 -0
  47. data/test/blackbox/basic/inclusion_1.exp +1 -0
  48. data/test/blackbox/basic/inclusion_1.tpl +1 -0
  49. data/test/blackbox/basic/inclusion_2.exp +1 -0
  50. data/test/blackbox/basic/inclusion_2.tpl +1 -0
  51. data/test/blackbox/basic/injection_1.exp +1 -0
  52. data/test/blackbox/basic/injection_1.tpl +1 -0
  53. data/test/blackbox/basic/injection_2.exp +1 -0
  54. data/test/blackbox/basic/injection_2.tpl +1 -0
  55. data/test/blackbox/basic/modulation_1.exp +1 -0
  56. data/test/blackbox/basic/modulation_1.tpl +1 -0
  57. data/test/blackbox/basic/modulation_2.exp +1 -0
  58. data/test/blackbox/basic/modulation_2.tpl +1 -0
  59. data/test/blackbox/basic/recursive_app_1.exp +1 -0
  60. data/test/blackbox/basic/recursive_app_1.tpl +1 -0
  61. data/test/blackbox/basic/recursive_app_2.exp +1 -0
  62. data/test/blackbox/basic/recursive_app_2.tpl +1 -0
  63. data/test/blackbox/buffering/data_1.rb +1 -0
  64. data/test/blackbox/buffering/data_assignment_1.exp +1 -0
  65. data/test/blackbox/buffering/data_assignment_1.tpl +1 -0
  66. data/test/blackbox/buffering/data_assignment_2.exp +1 -0
  67. data/test/blackbox/buffering/data_assignment_2.tpl +1 -0
  68. data/test/blackbox/buffering/data_assignment_3.exp +1 -0
  69. data/test/blackbox/buffering/data_assignment_3.tpl +1 -0
  70. data/test/blackbox/buffering/data_assignment_4.exp +1 -0
  71. data/test/blackbox/buffering/data_assignment_4.tpl +1 -0
  72. data/test/blackbox/buffering/input_1.exp +1 -0
  73. data/test/blackbox/buffering/input_1.tpl +1 -0
  74. data/test/blackbox/buffering/input_2.exp +1 -0
  75. data/test/blackbox/buffering/input_2.tpl +1 -0
  76. data/test/blackbox/buffering/input_3.exp +1 -0
  77. data/test/blackbox/buffering/input_3.tpl +1 -0
  78. data/test/blackbox/buffering/input_inclusion.exp +1 -0
  79. data/test/blackbox/buffering/input_inclusion.tpl +1 -0
  80. data/test/blackbox/buffering/input_inclusion_1.exp +0 -0
  81. data/test/blackbox/buffering/input_inclusion_1.tpl +1 -0
  82. data/test/blackbox/buffering/input_inclusion_2.exp +1 -0
  83. data/test/blackbox/buffering/input_inclusion_2.tpl +1 -0
  84. data/test/blackbox/buffering/input_inclusion_3.exp +1 -0
  85. data/test/blackbox/buffering/input_inclusion_3.tpl +1 -0
  86. data/test/blackbox/buffering/input_inclusion_4.exp +0 -0
  87. data/test/blackbox/buffering/input_inclusion_4.tpl +1 -0
  88. data/test/blackbox/buffering/input_inclusion_5.exp +1 -0
  89. data/test/blackbox/buffering/input_inclusion_5.tpl +1 -0
  90. data/test/blackbox/buffering/input_inclusion_6.exp +1 -0
  91. data/test/blackbox/buffering/input_inclusion_6.tpl +1 -0
  92. data/test/blackbox/buffering/input_inclusion_7.exp +0 -0
  93. data/test/blackbox/buffering/input_inclusion_7.tpl +1 -0
  94. data/test/blackbox/buffering/text_1.txt +1 -0
  95. data/test/blackbox/buffering/wlang.txt +1 -0
  96. data/test/blackbox/context/assignment_1.exp +1 -0
  97. data/test/blackbox/context/assignment_1.tpl +1 -0
  98. data/test/blackbox/context/assignment_2.exp +1 -0
  99. data/test/blackbox/context/assignment_2.tpl +1 -0
  100. data/test/blackbox/context/assignment_3.exp +2 -0
  101. data/test/blackbox/context/assignment_3.tpl +2 -0
  102. data/test/blackbox/context/assignment_4.exp +1 -0
  103. data/test/blackbox/context/assignment_4.tpl +1 -0
  104. data/test/blackbox/context/block_assignment_1.exp +1 -0
  105. data/test/blackbox/context/block_assignment_1.tpl +1 -0
  106. data/test/blackbox/context/block_assignment_2.exp +1 -0
  107. data/test/blackbox/context/block_assignment_2.tpl +1 -0
  108. data/test/blackbox/context/modulo_assignment_1.exp +1 -0
  109. data/test/blackbox/context/modulo_assignment_1.tpl +1 -0
  110. data/test/blackbox/context/modulo_assignment_2.exp +1 -0
  111. data/test/blackbox/context/modulo_assignment_2.tpl +1 -0
  112. data/test/blackbox/data_1.rb +1 -0
  113. data/test/blackbox/test_all.rb +59 -0
  114. data/test/spec/basic_object.spec +40 -0
  115. data/test/spec/global_extensions.rb +2 -0
  116. data/test/spec/hash_scope.spec +76 -0
  117. data/test/spec/redcloth_dialect.spec +24 -0
  118. data/test/spec/test_all.rb +8 -0
  119. data/test/spec/wlang.spec +53 -0
  120. data/test/spec/xhtml_dialect.spec +23 -0
  121. data/test/{test_all.rb → unit/test_all.rb} +1 -1
  122. data/test/{wlang → unit/wlang}/anagram_bugs_test.rb +2 -2
  123. data/test/{wlang → unit/wlang}/basic_ruleset_test.rb +1 -1
  124. data/test/{wlang → unit/wlang}/buffering_ruleset_test.rb +4 -4
  125. data/test/{wlang → unit/wlang}/buffering_template1.wtpl +0 -0
  126. data/test/{wlang → unit/wlang}/buffering_template2.wtpl +0 -0
  127. data/test/{wlang → unit/wlang}/buffering_template3.wtpl +0 -0
  128. data/test/unit/wlang/buffering_template4.wtpl +1 -0
  129. data/test/unit/wlang/buffering_template5.wtpl +1 -0
  130. data/test/{wlang → unit/wlang}/context_ruleset_test.rb +0 -0
  131. data/test/{wlang → unit/wlang}/data.rb +0 -0
  132. data/test/{wlang → unit/wlang}/encoder_set_test.rb +0 -0
  133. data/test/{wlang → unit/wlang}/imperative_ruleset_test.rb +0 -0
  134. data/test/{wlang → unit/wlang}/intelligent_buffer_test.rb +0 -0
  135. data/test/{wlang → unit/wlang}/othersymbols_test.rb +0 -0
  136. data/test/{wlang → unit/wlang}/parser_test.rb +10 -11
  137. data/test/{wlang → unit/wlang}/plain_text_dialect_test.rb +0 -0
  138. data/test/{wlang → unit/wlang}/ruby_dialect_test.rb +0 -0
  139. data/test/{wlang → unit/wlang}/ruby_expected.rb +0 -0
  140. data/test/{wlang → unit/wlang}/ruby_template.wrb +0 -0
  141. data/test/{wlang → unit/wlang}/ruleset_utils_test.rb +0 -0
  142. data/test/{wlang → unit/wlang}/specification_examples_test.rb +2 -2
  143. data/test/{wlang → unit/wlang}/test_utils.rb +1 -1
  144. data/test/{wlang → unit/wlang}/wlang_test.rb +0 -0
  145. metadata +135 -42
  146. data/lib/wlang/basic_object.rb +0 -19
  147. data/lib/wlang/parser_context.rb +0 -139
  148. data/test/sandbox.rb +0 -1
  149. data/test/wlang/buffering_template4.wtpl +0 -1
  150. data/test/wlang/buffering_template5.wtpl +0 -1
  151. 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
@@ -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
- # 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)
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
- # 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
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
- # Factors a specific buffer on the current dialect
56
- def factor_buffer
57
- @dialect.factor_buffer
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
- # 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
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
- # Pushes a given string on the output buffer
70
- def <<(str, block)
71
- append_buffer(@buffer, str, block)
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
- # - 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
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=@source_text.index(pattern,offset)
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.<<(@source_text[offset, match_at-offset], false) if match_at>0
143
+ self.<<(source_text[self.offset, match_at-self.offset], false) if match_at>0
88
144
 
89
- if @source_text[match_at,1]=='\\' # escaping sequence
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[template.block_symbols][0]
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[template.block_symbols][0] # opening special tag
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
- rule = @dialect.ruleset[match[0..-2]]
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 = 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
-
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 @template reached only if no match_at)
182
+ # trailing data (end of template reached only if no match_at)
125
183
  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
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
- [@buffer, offset-1]
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
- if dialect.nil?
150
- dialect = @dialect
151
- elsif String===dialect
152
- dname, dialect = dialect, WLang::dialect(dialect)
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
- @source_text[offset,2]=='}{'
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
- if String===encoder
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
- # Puts a key/value pair in the current context. See Parser::Context::define
235
- # for details.
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