wlang 0.8.5 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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