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
@@ -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