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
@@ -3,8 +3,8 @@ require 'wlang/rule_set'
3
3
  module WLang
4
4
 
5
5
  #
6
- # Implements the _dialect_ abstraction (see {README}[link://files/README.html]).
7
- # A dialect instance is a aggregation of encoders and ruleset (through EncoderSet
6
+ # Implements the _dialect_ abstraction (see {README}[link://files/README.rdoc]).
7
+ # A dialect instance is an aggregation of encoders and ruleset (through EncoderSet
8
8
  # and RuleSet classes). A dialect is also a node in the dialect tree and has a
9
9
  # qualified name through this tree. For example <tt>wlang/xhtml</tt> is the
10
10
  # qualified name of a <tt>xhtml</tt> dialect which is a child dialect of
@@ -14,6 +14,7 @@ module WLang
14
14
  # Language instead (see WLang::Dialect::DSL).
15
15
  #
16
16
  # === For developers only
17
+ #
17
18
  # In order to avoid having users to install all required gems of all dialects
18
19
  # wlang implements a lazy load design pattern on the dialect tree, through the
19
20
  # WLang::Dialect::DSL and WLang::Dialect::Loader classes. The former only creates
@@ -26,7 +27,7 @@ module WLang
26
27
  # Standard dialect obtention methods (WLang#dialect as well as WLang::Dialect#dialect)
27
28
  # ensure that returned dialects are built. If you obtain dialects another way,
28
29
  # be sure that they are built before using them (is_built? and build! are your
29
- # friends to achive that goal).
30
+ # friends to achieve that goal).
30
31
  #
31
32
  # Moreover, child dialects may require tools of their ancestors. The following
32
33
  # invariant should always be respected: if a dialect is built, all its ancestors
@@ -47,6 +48,9 @@ module WLang
47
48
  # Parent dialect
48
49
  attr_reader :parent
49
50
 
51
+ # Sub dialects by name
52
+ attr_reader :dialects
53
+
50
54
  #
51
55
  # Creates a dialect instance. _builder_ block is a chunk of code of the DSL
52
56
  # that will be executed twice: once at construction time to create sub dialects
@@ -13,7 +13,9 @@ module WLang
13
13
 
14
14
  # Default encoders
15
15
  DEFAULT_ENCODERS = {"java" => :coderay, "ruby" => :coderay, "html" => :coderay,
16
- "yaml" => :coderay}
16
+ "yaml" => :coderay, "sql" => :coderay, "css" => :coderay,
17
+ "javascript" => :coderay, "json" => :coderay, "php" => :coderay,
18
+ "xml" => :coderay}
17
19
 
18
20
  # Upcase encoding
19
21
  def self.coderay(src, options);
@@ -21,10 +23,8 @@ module WLang
21
23
  encoder = $1.to_sym
22
24
  tokens = CodeRay.scan src, encoder
23
25
  highlighted = tokens.html({
24
- :line_numbers => :inline,
25
26
  :wrap => :div,
26
- :css => :style,
27
- :style => :cygnus}
27
+ :css => :class}
28
28
  )
29
29
  return highlighted
30
30
  end
@@ -5,20 +5,14 @@ module WLang
5
5
  module PlainText
6
6
 
7
7
  # Default encoders
8
- DEFAULT_ENCODERS = {"upcase" => :upcase,
9
- "downcase" => :downcase,
10
- "capitalize" => :capitalize,
11
- "camel-case" => :camel_case}
8
+ DEFAULT_ENCODERS = {"upcase" => :upcase,
9
+ "downcase" => :downcase,
10
+ "capitalize" => :capitalize,
11
+ "camel" => :camel_case,
12
+ "camel-case" => :camel_case,
13
+ "upper-camel" => :camel_case,
14
+ "lower-camel" => :lower_camel_case}
12
15
 
13
- # Accents to replace when camel-casing
14
- # ACCENTS = { ['á','à','â','ä','ã','Ã','Ä','Â','À'] => 'a',
15
- # ['é','è','ê','ë','Ë','É','È','Ê'] => 'e',
16
- # ['í','ì','î','ï','I','Î','Ì'] => 'i',
17
- # ['ó','ò','ô','ö','õ','Õ','Ö','Ô','Ò'] => 'o',
18
- # ['œ'] => 'oe',
19
- # ['ß'] => 'ss',
20
- # ['ú','ù','û','ü','U','Û','Ù'] => 'u'}
21
-
22
16
  # Upcase encoding
23
17
  def self.upcase(src, options); src.upcase; end
24
18
 
@@ -30,15 +24,15 @@ module WLang
30
24
 
31
25
  # Converts a string as CamelCase
32
26
  def self.camel_case(src, options)
33
- # ACCENTS.each do |ac,rep|
34
- # ac.each do |s|
35
- # src.gsub!(s, rep)
36
- # end
37
- # end
38
- src.gsub!(/[^a-zA-Z ]/," ")
27
+ src.gsub!(/[^a-zA-Z\s]/," ")
39
28
  src = " " + src.split.join(" ")
40
29
  src.gsub!(/ (.)/) { $1.upcase }
41
30
  end
31
+
32
+ # Converts a string to lower camelCase
33
+ def self.lower_camel_case(src, options)
34
+ camel_case(src, options).gsub(/^([A-Z])/){ $1.downcase }
35
+ end
42
36
 
43
37
  end # module PlainText
44
38
 
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ module WLang
3
+ class EncoderSet
4
+ module RedClothEncoders
5
+
6
+ # Default encoders
7
+ DEFAULT_ENCODERS = {"xhtml" => :xhtml_encoding}
8
+
9
+ # RDoc encoding
10
+ def self.xhtml_encoding(src, options)
11
+ RedCloth.new(src).to_html
12
+ end
13
+
14
+ end # RedClothEncoders
15
+ end # module EncoderSet
16
+ end # module WLang
@@ -5,9 +5,14 @@ module WLang
5
5
  module Ruby
6
6
 
7
7
  # Default encoders
8
- DEFAULT_ENCODERS = {"single-quoting" => :single_quoting,
8
+ DEFAULT_ENCODERS = {"main-encoding" => :main_encoding,
9
+ "single-quoting" => :single_quoting,
9
10
  "double-quoting" => :double_quoting,
10
- "regex-escaping" => :regex_escaping}
11
+ "regex-escaping" => :regex_escaping,
12
+ "method-case" => :method_case}
13
+
14
+ # No-op encoding here
15
+ def self.main_encoding(src, options); src; end
11
16
 
12
17
  # Single-quoting encoding
13
18
  def self.single_quoting(src, options); src.gsub(/([^\\])'/,%q{\1\\\'}); end
@@ -17,6 +22,15 @@ module WLang
17
22
 
18
23
  # Regexp-escaping encoding
19
24
  def self.regex_escaping(src, options); Regexp.escape(src); end
25
+
26
+ # Converts any source to a typical ruby method name
27
+ def self.method_case(src, options)
28
+ src.strip.gsub(/[^a-zA-Z0-9\s]/," ").
29
+ gsub(/([A-Z])/){ " " + $1.downcase}.
30
+ strip.
31
+ gsub(/^([^a-z])/){ "_" + $1 }.
32
+ gsub(/\s+/){"_"}
33
+ end
20
34
 
21
35
  end # module Ruby
22
36
 
@@ -27,6 +27,18 @@ WLang::dialect("ruby", ".rb", ".ruby") do
27
27
  end
28
28
  end
29
29
 
30
+ # sql dialect
31
+ WLang::dialect("sql", ".sql") do
32
+ ruby_require "wlang/dialects/sql_dialect" do
33
+ encoders WLang::EncoderSet::SQL
34
+ end
35
+ dialect("sybase", '.sybsql') do
36
+ ruby_require "wlang/dialects/sql_dialect" do
37
+ encoders WLang::EncoderSet::SQL::Sybase
38
+ end
39
+ end
40
+ end
41
+
30
42
  # ruby dialect
31
43
  WLang::dialect("xhtml", ".html", ".xhtml", ".htm") do
32
44
  ruby_require "cgi", "wlang/dialects/xhtml_dialect" do
@@ -46,6 +58,13 @@ WLang::dialect("rdoc") do
46
58
  end
47
59
  end
48
60
 
61
+ # rdoc dialect
62
+ WLang::dialect("redcloth") do
63
+ ruby_require "RedCloth", "wlang/dialects/redcloth_dialect" do
64
+ encoders WLang::EncoderSet::RedClothEncoders
65
+ end
66
+ end
67
+
49
68
  # wlang dialects
50
69
  WLang::dialect("wlang") do
51
70
 
@@ -56,6 +75,7 @@ WLang::dialect("wlang") do
56
75
  # wlang/active-string dialect
57
76
  dialect("active-string") do
58
77
  rules WLang::RuleSet::Basic
78
+ rules WLang::RuleSet::Imperative
59
79
  end
60
80
 
61
81
  # wlang/uri dialect
@@ -31,7 +31,30 @@ module WLang
31
31
  module XHtml
32
32
 
33
33
  # Default mapping between tag symbols and methods
34
- DEFAULT_RULESET = {}
34
+ DEFAULT_RULESET = {'@' => :at}
35
+
36
+ def self.at(parser, offset)
37
+ # parse the url
38
+ url, reached = parser.parse(offset, 'wlang/active-string')
39
+ url = WLang::encode(url, 'wlang/xhtml/double-quoting')
40
+
41
+ # parse the label if there is one
42
+ label = nil
43
+ if parser.has_block?(reached)
44
+ label, reached = parser.parse_block(reached)
45
+ label = WLang::encode(label, 'wlang/xhtml/entities-encoding')
46
+ end
47
+
48
+ if label and url.respond_to?(:to_xhtml_link)
49
+ [url.to_xhtml_link(url, label), reached]
50
+ elsif url.respond_to?(:to_xhtml_href)
51
+ [url.to_xhtml_href(url), reached]
52
+ elsif label
53
+ ["<a href=\"#{url}\">#{label}</a>", reached]
54
+ else
55
+ [url, reached]
56
+ end
57
+ end
35
58
 
36
59
  end # module XHtml
37
60
 
@@ -56,7 +56,7 @@ module WLang
56
56
  # - options: encoding options through a Hash. Available options are documented
57
57
  # by encoders themselve.
58
58
  #
59
- def encode(src, options)
59
+ def encode(src, options = {})
60
60
  raise(NotImplementedError) unless @block
61
61
  @block.call(src, options)
62
62
  end
@@ -25,6 +25,11 @@ module WLang
25
25
  @encoders = {}
26
26
  end
27
27
 
28
+ # Yields the block with name, encoder pairs
29
+ def each
30
+ @encoders.each_pair{|name,encoder| yield(name, encoder)}
31
+ end
32
+
28
33
  #
29
34
  # Adds an encoder under a specific name. Supported signatures are as follows:
30
35
  # - _name_ is a symbol: _encoder_ and _block_ are ignored and encoder is
@@ -1,16 +1,80 @@
1
1
  module WLang
2
2
 
3
3
  # Main error of all WLang errors.
4
- class Error < StandardError; end
4
+ class Error < StandardError
5
+
6
+ # The parser state whe this error has been raised
7
+ attr_accessor :parser_state
8
+
9
+ # Optional cause (other lower level exception)
10
+ attr_accessor :cause
11
+
12
+ # Creates an error instance with a given parser state
13
+ def initialize(msg = nil, parser_state = nil, cause = nil)
14
+ raise ArgumentError, "msg expected to be nil or a String" unless (msg.nil? or String===msg)
15
+ raise ArgumentError, "parser_state expected to be nil or a State"\
16
+ unless (parser_state.nil? or ::WLang::Parser::State===parser_state)
17
+ super(msg)
18
+ @parser_state = parser_state
19
+ @cause = cause
20
+ end
21
+
22
+ # Returns a friendly wlang backtrace
23
+ def wlang_backtrace
24
+ parser_state ? parser_state.backtrace : ["no backtrace information, sorry"]
25
+ end
26
+
27
+ end # class Error
28
+
29
+ #
30
+ # Raised by hosted languages when something fails during
31
+ # evaluation.
32
+ #
33
+ class EvalError < ::WLang::Error
34
+
35
+ # The expression whose evaluation failed
36
+ attr_accessor :expression
37
+
38
+ # Creates an error instance with an optional expression that
39
+ # failed
40
+ def initialize(msg = nil, parser_state = nil, expression = nil, cause = nil)
41
+ super(msg, parser_state, cause)
42
+ @expression = expression
43
+ end
44
+
45
+ def to_s
46
+ "Evaluation of #{@expression} failed, #{@cause ? @cause.message : ''}"
47
+ end
48
+
49
+ end # class EvalError
5
50
 
6
- # Raise when something fails when evaluting through the parser context
7
- class EvalError < StandardError; end
51
+ #
52
+ # Raised when a variable may not be found in the current
53
+ # parser scope
54
+ #
55
+ class UndefinedVariableError < ::WLang::EvalError
56
+
57
+ # Name of the variable that could not be found
58
+ attr_accessor :variable
59
+
60
+ # Creates an error instance with an optional variable name
61
+ def initialize(msg = nil, parser_state = nil, expression = nil, variable = nil)
62
+ super(msg, parser_state, expression)
63
+ @variable = variable
64
+ end
65
+
66
+ def to_s
67
+ "Unable to find variable #{@variable}"
68
+ end
69
+
70
+ end # class UndefinedVariableError
8
71
 
9
72
  # Error raised by a WLang parser instanciation when an error occurs.
10
- class ParseError < StandardError;
73
+ class ParseError < ::WLang::Error
11
74
 
75
+ # Where did the parsing failed
12
76
  attr_accessor :line, :column
13
77
 
14
- end
78
+ end # class ParseError
15
79
 
16
- end # module WLang
80
+ end # module WLang
@@ -0,0 +1,13 @@
1
+ class Hash
2
+
3
+ # Allows using hash.key as a synonym for hash[:key] and
4
+ # hash['key']
5
+ def method_missing(name, *args, &block)
6
+ if args.empty? and block.nil?
7
+ self[name] || self[name.to_s]
8
+ else
9
+ super(name, *args, &block)
10
+ end
11
+ end
12
+
13
+ end
@@ -4,19 +4,21 @@
4
4
  class String
5
5
 
6
6
  # Converts the string to a wlang template
7
- def wlang_template(dialect="wlang/active-string", context=nil, block_symbols=:braces)
8
- WLang::Template.new(self, dialect, context, block_symbols)
7
+ def wlang_template(dialect = "wlang/active-string", block_symbols = :braces)
8
+ WLang::template(self, dialect, block_symbols)
9
9
  end
10
10
 
11
11
  #
12
12
  # Instantiates the string as a wlang template using
13
13
  # a context object and a dialect.
14
14
  #
15
- def wlang_instantiate(context=nil, dialect="wlang/active-string", block_symbols=:braces)
16
- wlang_template(dialect, context, block_symbols).instantiate
15
+ def wlang_instantiate(context = nil, dialect = "wlang/active-string", block_symbols = :braces)
16
+ WLang::instantiate(self, context, dialect, block_symbols)
17
17
  end
18
18
  alias :wlang :wlang_instantiate
19
-
19
+
20
+ # Computes the column number for a given offset in
21
+ # this string
20
22
  def __wlang_column_of(index)
21
23
  return 1 if index == 0
22
24
  newline_index = rindex("\n", index - 1)
@@ -27,6 +29,8 @@ class String
27
29
  end
28
30
  end
29
31
 
32
+ # Computes the line number for a given offset in
33
+ # this string
30
34
  def __wlang_line_of(index)
31
35
  self[0...index].count("\n") + 1
32
36
  end
@@ -0,0 +1,89 @@
1
+ require 'delegate'
2
+ module WLang
3
+ #
4
+ # Implements a scoping mechanism on top of a ruby hash (accessible through pairing).
5
+ # Such a scope mimics hashses for has_key?, [] and []= methods. A scope has an
6
+ # accessible parent. Scopes form a tree, the root being accessible using its
7
+ # natural accessor. Scope lookup (has_key? and []) uses the hierarchy to find
8
+ # accessible variables.
9
+ #
10
+ # Branching a scope allows installing new variables that hide variables with the
11
+ # same name in the parent scope. Branching is made easy through the branch methods
12
+ # that accepts a block, passing the child as first argument:
13
+ #
14
+ # scope = HashScope.new(:name => 'wlang')
15
+ # puts scope[:name] # prints 'wlang'
16
+ # scope.branch(:name => 'other') do |child|
17
+ # puts child[:name] # prints 'other'
18
+ # end
19
+ # puts scope[:name] # prints 'wlang'
20
+ #
21
+ # This branching mechanism is intended to be used to keep a current scope as instance
22
+ # variable of a using class:
23
+ #
24
+ # # We create an initial scope at construction
25
+ # def initialize
26
+ # @scope = HashScope.new
27
+ # end
28
+ #
29
+ # # Appends the current scope with new key/value pairs. Yields the block
30
+ # # with the new scope and restore the original one after that.
31
+ # def do_something_with_a_new_scope(hash = {})
32
+ # @scope = @scope.branch(:name => 'other')
33
+ # yield if block_given?
34
+ # @scope = @scope.parent
35
+ # end
36
+ #
37
+ class HashScope
38
+
39
+ # The parent scope, or nil if no such parent
40
+ attr_reader :parent
41
+
42
+ # The key/value pairing inside this scope
43
+ attr_reader :pairing
44
+
45
+ # Creates a scope instance with a parent and initial
46
+ # pairing through a Hash
47
+ def initialize(pairing = nil, parent = nil)
48
+ raise ArgumentError, "Hash expected for pairing #{pairing.class} received" unless (pairing.nil? or Hash===pairing)
49
+ @pairing = pairing || {}
50
+ @parent = parent
51
+ end
52
+
53
+ # Returns the root scope
54
+ def root
55
+ @root ||= (parent ? parent.root : self)
56
+ end
57
+
58
+ # Checks if a key exists in this scope, delegating to
59
+ # parent if not found and allowed
60
+ def has_key?(key, delegate = true)
61
+ pairing.has_key?(key) || (!delegate.nil? && !parent.nil? && parent.has_key?(key))
62
+ end
63
+
64
+ # Returns the value associated to a key, delegating to parent
65
+ # if not found and allowed
66
+ def [](key, delegate = true)
67
+ pairing.has_key?(key) ? pairing[key] : (delegate && parent && parent[key])
68
+ end
69
+
70
+ # Associates a key to a value inside this scope
71
+ def []=(key, value)
72
+ pairing[key] = value
73
+ end
74
+
75
+ # Creates a new child scope and returns it. If a block is given,
76
+ # yields the block with the child scope
77
+ def branch(pairing = nil)
78
+ child = HashScope.new(pairing, self)
79
+ yield child if block_given?
80
+ child
81
+ end
82
+
83
+ # Converts this scope to a full hash with all variables
84
+ def to_h
85
+ parent ? parent.to_h.merge(pairing) : pairing.dup
86
+ end
87
+
88
+ end # class HashScope
89
+ end # module WLang