wlang 0.8.4

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 (73) hide show
  1. data/LICENCE.rdoc +25 -0
  2. data/README.rdoc +111 -0
  3. data/bin/wlang +24 -0
  4. data/doc/specification/about.rdoc +61 -0
  5. data/doc/specification/dialects.wtpl +0 -0
  6. data/doc/specification/examples.rb +3 -0
  7. data/doc/specification/glossary.wtpl +14 -0
  8. data/doc/specification/hosting.rdoc +0 -0
  9. data/doc/specification/overview.rdoc +116 -0
  10. data/doc/specification/rulesets.wtpl +87 -0
  11. data/doc/specification/specification.css +52 -0
  12. data/doc/specification/specification.html +1361 -0
  13. data/doc/specification/specification.js +8 -0
  14. data/doc/specification/specification.wtpl +41 -0
  15. data/doc/specification/specification.yml +430 -0
  16. data/doc/specification/symbols.wtpl +16 -0
  17. data/lib/wlang.rb +186 -0
  18. data/lib/wlang/basic_object.rb +19 -0
  19. data/lib/wlang/dialect.rb +230 -0
  20. data/lib/wlang/dialect_dsl.rb +136 -0
  21. data/lib/wlang/dialect_loader.rb +69 -0
  22. data/lib/wlang/dialects/coderay_dialect.rb +35 -0
  23. data/lib/wlang/dialects/plain_text_dialect.rb +75 -0
  24. data/lib/wlang/dialects/rdoc_dialect.rb +33 -0
  25. data/lib/wlang/dialects/ruby_dialect.rb +35 -0
  26. data/lib/wlang/dialects/sql_dialect.rb +38 -0
  27. data/lib/wlang/dialects/standard_dialects.rb +113 -0
  28. data/lib/wlang/dialects/xhtml_dialect.rb +40 -0
  29. data/lib/wlang/encoder.rb +66 -0
  30. data/lib/wlang/encoder_set.rb +117 -0
  31. data/lib/wlang/errors.rb +37 -0
  32. data/lib/wlang/intelligent_buffer.rb +94 -0
  33. data/lib/wlang/parser.rb +251 -0
  34. data/lib/wlang/parser_context.rb +146 -0
  35. data/lib/wlang/ruby_extensions.rb +21 -0
  36. data/lib/wlang/rule.rb +66 -0
  37. data/lib/wlang/rule_set.rb +93 -0
  38. data/lib/wlang/rulesets/basic_ruleset.rb +75 -0
  39. data/lib/wlang/rulesets/buffering_ruleset.rb +103 -0
  40. data/lib/wlang/rulesets/context_ruleset.rb +115 -0
  41. data/lib/wlang/rulesets/encoding_ruleset.rb +73 -0
  42. data/lib/wlang/rulesets/imperative_ruleset.rb +132 -0
  43. data/lib/wlang/rulesets/ruleset_utils.rb +296 -0
  44. data/lib/wlang/template.rb +79 -0
  45. data/lib/wlang/wlang_command.rb +54 -0
  46. data/lib/wlang/wlang_command_options.rb +158 -0
  47. data/test/sandbox.rb +1 -0
  48. data/test/test_all.rb +8 -0
  49. data/test/wlang/anagram_bugs_test.rb +111 -0
  50. data/test/wlang/basic_ruleset_test.rb +52 -0
  51. data/test/wlang/buffering_ruleset_test.rb +102 -0
  52. data/test/wlang/buffering_template1.wtpl +1 -0
  53. data/test/wlang/buffering_template2.wtpl +1 -0
  54. data/test/wlang/buffering_template3.wtpl +1 -0
  55. data/test/wlang/buffering_template4.wtpl +1 -0
  56. data/test/wlang/buffering_template5.wtpl +1 -0
  57. data/test/wlang/context_ruleset_test.rb +32 -0
  58. data/test/wlang/data.rb +3 -0
  59. data/test/wlang/encoder_set_test.rb +42 -0
  60. data/test/wlang/imperative_ruleset_test.rb +107 -0
  61. data/test/wlang/intelligent_buffer_test.rb +194 -0
  62. data/test/wlang/othersymbols_test.rb +16 -0
  63. data/test/wlang/parser_context_test.rb +29 -0
  64. data/test/wlang/parser_test.rb +89 -0
  65. data/test/wlang/plain_text_dialect_test.rb +21 -0
  66. data/test/wlang/ruby_dialect_test.rb +100 -0
  67. data/test/wlang/ruby_expected.rb +3 -0
  68. data/test/wlang/ruby_template.wrb +3 -0
  69. data/test/wlang/ruleset_utils_test.rb +245 -0
  70. data/test/wlang/specification_examples_test.rb +52 -0
  71. data/test/wlang/test_utils.rb +25 -0
  72. data/test/wlang/wlang_test.rb +80 -0
  73. metadata +136 -0
@@ -0,0 +1,146 @@
1
+ require 'wlang/basic_object'
2
+ module WLang
3
+ class Parser
4
+
5
+ #
6
+ # Execution context of the Parser for <tt>!{wlang/hosted}</tt> and associated
7
+ # tags. The execution context defines the semantics of executed code in those
8
+ # tags as well as utilities to handle semantical scoping.
9
+ #
10
+ # TODO: define context and scoping semantics more precisely and extend the
11
+ # documentation
12
+ #
13
+ class Context
14
+
15
+ # Common methods of scope instances
16
+ class Scope < WLang::BasicObject
17
+ attr_accessor :__parent
18
+
19
+ end # module Scope
20
+
21
+ # Hash scoping mechanism
22
+ class HashScope < Scope
23
+ attr_reader :__hash
24
+
25
+ # Decorates a hash as a scope.
26
+ def initialize(hash={}, parent=nil)
27
+ @__hash = hash
28
+ @__parent = parent
29
+ end
30
+
31
+ def nil?
32
+ false
33
+ end
34
+
35
+ def dup
36
+ HashScope.new(@__hash.dup, @__parent)
37
+ end
38
+
39
+ def __branch(other)
40
+ HashScope.new(other, self)
41
+ end
42
+
43
+ # Tries to convert found value to a given variable.
44
+ def method_missing(symbol, *args)
45
+ varname = symbol.to_s
46
+ if @__hash.has_key?(varname)
47
+ @__hash[varname]
48
+ elsif @__parent
49
+ @__parent.method_missing(symbol, *args)
50
+ else
51
+ nil
52
+ end
53
+ end
54
+
55
+ # Returns underlying object
56
+ def __underlying
57
+ return @__hash
58
+ end
59
+
60
+ # See Scope#__evaluate
61
+ def __evaluate(expr)
62
+ self.instance_eval(expr)
63
+ end
64
+
65
+ # See Scope#__define
66
+ def __define(key, value);
67
+ @__hash[key]=value;
68
+ end
69
+
70
+ def /(symb)
71
+ s = symb.to_s
72
+ if @__hash.has_key?(s)
73
+ return WLang::Parser::Context.to_scope(@__hash[s])
74
+ else
75
+ return nil
76
+ end
77
+ end
78
+
79
+ def inspect
80
+ "#{__underlying.inspect} with parent #{@__parent.inspect}"
81
+ end
82
+
83
+ end # class HashScope
84
+
85
+ # Creates an empty context on an init scope object.
86
+ def initialize(init=nil)
87
+ if (Scope===init)
88
+ @current_scope = init
89
+ else
90
+ @current_scope = HashScope.new
91
+ push(init) unless init.nil?
92
+ end
93
+ end
94
+
95
+ # Evaluates a ruby expression on the current context.
96
+ def evaluate(expression)
97
+ if "self" == expression.strip
98
+ @current_scope
99
+ elsif /[a-z]+(\/[a-z]+)+/ =~ expression
100
+ expression = ("self/" + expression).gsub(/\//,"/:")
101
+ expr = @current_scope.__evaluate(expression)
102
+ expr = expr.__underlying if Scope===expr
103
+ return expr
104
+ else
105
+ @current_scope.__evaluate(expression)
106
+ end
107
+ rescue Exception => ex
108
+ puts "Warning, some wlang exception when evaluating the expression\n#{expression}"
109
+ puts "Message was: #{ex.message}"
110
+ puts ex.backtrace.join("\n")
111
+ puts "Current scope was:\n"
112
+ puts @current_scope.__underlying.inspect
113
+ return nil
114
+ end
115
+
116
+ # Pushes a new scope instance.
117
+ def push(who={})
118
+ who = WLang::Parser::Context.to_scope(who)
119
+ who.__parent = @current_scope
120
+ @current_scope = who
121
+ end
122
+
123
+ # Pops the last added scope.
124
+ def pop
125
+ parent = @current_scope.__parent
126
+ raise "Bad scope usage, nothing to pop" if parent.nil?
127
+ @current_scope = parent
128
+ end
129
+
130
+ # Defines a variable/value mapping in the current scope.
131
+ def define(key, value)
132
+ @current_scope.__define(key,value)
133
+ end
134
+
135
+ # Converts a given instance to a Scope.
136
+ def self.to_scope(who)
137
+ return who if Scope===who
138
+ return who.to_wlang_scope if who.respond_to?(:to_wlang_scope)
139
+ return HashScope.new(who) if Hash===who
140
+ return who
141
+ end
142
+
143
+ end # class Context
144
+
145
+ end
146
+ end
@@ -0,0 +1,21 @@
1
+ #
2
+ # Installs _wlang_ utility methods on Ruby String.
3
+ #
4
+ class String
5
+
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)
9
+ end
10
+
11
+ #
12
+ # Instantiates the string as a wlang template using
13
+ # a context object and a dialect.
14
+ #
15
+ def wlang_instantiate(context=nil, dialect="wlang/active-string", block_symbols=:braces)
16
+ wlang_template(dialect, context, block_symbols).instantiate
17
+ end
18
+ alias :wlang :wlang_instantiate
19
+
20
+ end
21
+
data/lib/wlang/rule.rb ADDED
@@ -0,0 +1,66 @@
1
+ module WLang
2
+
3
+ #
4
+ # A Rule is designed to perform a replacement job when the special tag associated
5
+ # with it is found in a Template. Rules are always installed on a RuleSet (using
6
+ # RuleSet#add_rule), which is itself installed on a Dialect. Note that the method
7
+ # mentionned previously provides a DRY shortcut, allowing not using this class
8
+ # directly.
9
+ #
10
+ # Example:
11
+ # # Rule subclassing can be avoided by providing a block to new
12
+ # # The following rule job is to upcase the text inside +{...} tags:
13
+ # rule = Rule.new do |parser,offset|
14
+ # parsed, reached = parser.parse(offset)
15
+ # [parsed.upcase, reached]
16
+ # end
17
+ #
18
+ # Creating a a new rule can be made in two ways: by subclassing this class and
19
+ # overriding the start_tag method or by passing a block to new. In both cases,
20
+ # <b>rules should always be stateless</b>, to allow reusable dialects that could
21
+ # even be used in a multi-threading environment. Implementing a rule correctly
22
+ # must be considered non trivial due to the strong protocol between the parser
23
+ # and its rules and the stateless convention. Always have a look to helpers
24
+ # provided by RuleSet (to create simple rules easily) before deciding to implement
25
+ # a rule using this class.
26
+ #
27
+ # == Detailed API
28
+ class Rule
29
+
30
+ #
31
+ # Creates a new rule. If no block is given, the invocation of new MUST be made
32
+ # on a subclass overriding start_tag. Otherwise, the block is considered as the
33
+ # effective stateless implementation of start_tag and will be called with the
34
+ # same arguments.
35
+ #
36
+ def initialize(&block)
37
+ unless block.nil?
38
+ raise(ArgumentError, "Expected a rule block of arity 2")\
39
+ unless block.arity==2
40
+ end
41
+ @block = block
42
+ end
43
+
44
+ #
45
+ # Fired when the parser has reached an offset matching this rule.
46
+ #
47
+ # This method MUST return an array [_replacement_, _offset_] where _replacement_
48
+ # is what replaces the tag itself in the resulting text and _offset_ is the
49
+ # new offset reached in the source text (where parsing will continue).
50
+ # _offset_ should always be such that <tt>text[offset,1]=='}'</tt> to allow
51
+ # higher stages to continue their job correctly. Utility methods for parsing
52
+ # text parts are provided by the parser itself (see WLang::Parser).
53
+ #
54
+ # Arguments:
55
+ # - parser: WLang parser currently parsing the text.
56
+ # - offset: offset reached in the text, corresponding to the first character
57
+ # of the first block associated with the matching tag.
58
+ #
59
+ def start_tag(parser, offset)
60
+ raise(NotImplementedError) unless @block
61
+ @block.call(parser, offset)
62
+ end
63
+
64
+ end # class Rule
65
+
66
+ end # module WLang
@@ -0,0 +1,93 @@
1
+ require 'wlang/rule'
2
+ module WLang
3
+
4
+ #
5
+ # This class allows grouping matching rules together to build a given dialect.
6
+ # Rules are always added with add_rule, which also allows creating simple rules
7
+ # on the fly (that is, without subclassing Rule).
8
+ #
9
+ # Examples:
10
+ # # we will create a simple dialect with a special tag:
11
+ # # <tt>+{...}</tt> which will uppercase its contents
12
+ # upcaser = RuleSet.new
13
+ # upcaser.add_rule '+' do |parser,offset|
14
+ # parsed, offset = parser.parse(offset)
15
+ # [parsed.upcase, offset]
16
+ # end
17
+ #
18
+ # == Detailed API
19
+ class RuleSet
20
+
21
+ #
22
+ # Creates an new dialect rule set.
23
+ #
24
+ def initialize() @rules, @pattern = {}, nil; end
25
+
26
+ #
27
+ # Adds a tag matching rule to this rule set. _tag_ must be a String with the
28
+ # tag associated to the rule (without the '{', that is '$' for the tag ${...}
29
+ # for example. If rule is ommited and a block is given, a new Rule instance is
30
+ # created on the fly with _block_ as implementation (see Rule#new).
31
+ # Otherwise rule is expected to be a Rule instance. This method check its
32
+ # arguments, raising an ArgumentError if incorrect.
33
+ #
34
+ def add_rule(tag, rule=nil, &block)
35
+ if rule.nil?
36
+ raise(ArgumentError,"Block required") unless block_given?
37
+ rule = Rule.new(&block)
38
+ end
39
+ raise(ArgumentError, "Rule expected") unless Rule===rule
40
+ @rules[tag] = rule
41
+ @pattern = nil
42
+ end
43
+
44
+ #
45
+ # Add rules defined in a given RuleSet module.
46
+ #
47
+ def add_rules(mod, pairs=nil)
48
+ raise(ArgumentError,"Module expected") unless Module===mod
49
+ pairs = mod::DEFAULT_RULESET if pairs.nil?
50
+ pairs.each_pair do |symbol,method|
51
+ meth = mod.method(method)
52
+ raise(ArgumentError,"No such method: #{method}") if meth.nil?
53
+ add_rule(symbol, &meth.to_proc)
54
+ end
55
+ end
56
+
57
+ #
58
+ # Returns a Regexp instance with recognizes all tags installed in the rule set.
59
+ # The returned Regexp is backslashing aware (it matches <tt>\${</tt> for example)
60
+ # as well as '{' and '}' aware. This pattern is used by WLang::Parser and is
61
+ # not intended to be used by users themselve.
62
+ #
63
+ def pattern(block_symbols)
64
+ build_pattern(block_symbols);
65
+ end
66
+
67
+ #
68
+ # Returns the Rule associated with a given tag, _nil_ if no such rule.
69
+ #
70
+ def [](tag) @rules[tag]; end
71
+
72
+
73
+ ### protected section ######################################################
74
+ protected
75
+
76
+ # Internal implementation of pattern.
77
+ def build_pattern(block_symbols)
78
+ start, stop = WLang::Template::BLOCK_SYMBOLS[block_symbols]
79
+ start, stop = Regexp.escape(start), Regexp.escape(stop)
80
+ s = '([\\\\]{0,2}('
81
+ i=0
82
+ @rules.each_key do |tag|
83
+ s << '|' if i>0
84
+ s << '(' << Regexp.escape(tag) << ')'
85
+ i += 1
86
+ end
87
+ s << ")#{start})|[\\\\]{0,2}#{start}|[\\\\]{0,2}#{stop}"
88
+ Regexp.new(s)
89
+ end
90
+
91
+ end # class RuleSet
92
+
93
+ end # module WLang
@@ -0,0 +1,75 @@
1
+ require 'wlang/rulesets/ruleset_utils'
2
+ module WLang
3
+ class RuleSet
4
+
5
+ #
6
+ # Basic ruleset, commonly included by any wlang dialect (but some tags, like
7
+ # <tt>${...}</tt> may be overriden). This ruleset is often installed conjointly
8
+ # with WLang::RuleSet::Encoding which provides interresting overridings of
9
+ # <tt>${...}</tt> and <tt>+{...}</tt>.
10
+ #
11
+ # For an overview of this ruleset, see the wlang {specification file}[link://files/specification.html].
12
+ #
13
+ module Basic
14
+ U=WLang::RuleSet::Utils
15
+
16
+ # Default mapping between tag symbols and methods
17
+ DEFAULT_RULESET = {'!' => :execution, '%' => :modulation, '^' => :encoding,
18
+ '+' => :inclusion, '$' => :injection, '%!' => :recursive_application}
19
+
20
+ # Rule implementation of <tt>!{wlang/ruby}</tt>.
21
+ def self.execution(parser, offset)
22
+ expression, reached = parser.parse(offset, "wlang/ruby")
23
+ value = parser.evaluate(expression)
24
+ result = value.nil? ? "" : value.to_s
25
+ [result, reached]
26
+ end
27
+
28
+ # Rule implementation of <tt>%{wlang/active-string}{...}</tt>.
29
+ def self.modulation(parser, offset)
30
+ dialect, reached = parser.parse(offset, "wlang/active-string")
31
+ result, reached = parser.parse_block(reached, dialect)
32
+ [result, reached]
33
+ end
34
+
35
+ # Rule implementation of <tt>^{wlang/active-string}{...}</tt>
36
+ def self.encoding(parser, offset)
37
+ encoder, reached = parser.parse(offset, "wlang/active-string")
38
+ result, reached = parser.parse_block(reached)
39
+ result = parser.encode(result, encoder)
40
+ [result, reached]
41
+ end
42
+
43
+ # Rule implementation of <tt>${wlang/ruby}</tt>
44
+ def self.injection(parser, offset)
45
+ execution(parser, offset)
46
+ end
47
+
48
+ # Rule implementation of <tt>+{wlang/ruby}</tt>
49
+ def self.inclusion(parser, offset)
50
+ execution(parser, offset)
51
+ end
52
+
53
+ # Rule implementation of <tt>%!{wlang/ruby using ... with ...}</tt>
54
+ def self.recursive_application(parser, offset)
55
+ dialect, reached = parser.parse(offset, "wlang/active-string")
56
+ text, reached = parser.parse_block(reached)
57
+
58
+ # decode expression
59
+ decoded = U.expr(:qdialect,
60
+ ["using", :expr, false],
61
+ ["with", :with, false]).decode(dialect, parser)
62
+ parser.syntax_error(offset) if decoded.nil?
63
+
64
+ # build context
65
+ context = U.context_from_using_and_with(decoded)
66
+
67
+ # instantiate
68
+ instantiated = WLang::instantiate(text, context, decoded[:qdialect])
69
+ [instantiated, reached]
70
+ end
71
+
72
+ end # module Basic
73
+
74
+ end
75
+ end
@@ -0,0 +1,103 @@
1
+ require 'wlang/rulesets/ruleset_utils'
2
+ require 'fileutils'
3
+
4
+ module WLang
5
+ class RuleSet
6
+
7
+ #
8
+ # Buffering ruleset, providing special tags to load/instantiate accessible files
9
+ # and outputting instantiation results in other files.
10
+ #
11
+ # For an overview of this ruleset, see the wlang {specification file}[link://files/specification.html].
12
+ #
13
+ module Buffering
14
+ U=WLang::RuleSet::Utils
15
+
16
+ # Default mapping between tag symbols and methods
17
+ DEFAULT_RULESET = {'<<' => :input, '>>' => :output,
18
+ '<<=' => :data_assignment, '<<+' => :input_inclusion}
19
+
20
+ # Rule implementation of <tt><<{wlang/uri}</tt>
21
+ def self.input(parser, offset)
22
+ uri, reached = parser.parse(offset, "wlang/uri")
23
+ file = parser.template.file_resolve(uri, true)
24
+ [File.read(file), reached]
25
+ end
26
+
27
+ # Rule implementation of <tt>>>{wlang/uri}</tt>
28
+ def self.output(parser, offset)
29
+ uri, reached = parser.parse(offset, "wlang/uri")
30
+ file = parser.template.file_resolve(uri, false)
31
+ dir = File.dirname(file)
32
+ FileUtils.mkdir_p(dir) unless File.exists?(dir)
33
+ File.open(file, "w") do |file|
34
+ text, reached = parser.parse_block(reached, nil, file)
35
+ end
36
+ ["", reached]
37
+ end
38
+
39
+ # Rule implementation of <<={wlang/uri as x}{...}
40
+ def self.data_assignment(parser, offset)
41
+ uri, reached = parser.parse(offset, "wlang/uri")
42
+
43
+ # decode expression
44
+ decoded = U.decode_uri_as(uri)
45
+ parser.syntax_error(offset) if decoded.nil?
46
+
47
+ file = parser.template.file_resolve(decoded[:uri], true)
48
+ data = WLang::load_data(file)
49
+
50
+ # handle two different cases
51
+ if parser.has_block?(reached)
52
+ parser.context_push(decoded[:variable] => data)
53
+ text, reached = parser.parse_block(reached)
54
+ parser.context_pop
55
+ [text, reached]
56
+ else
57
+ parser.context_define(decoded[:variable], data)
58
+ ["", reached]
59
+ end
60
+ end
61
+
62
+ # Rule implementation of <<+{wlang/uri with ...}
63
+ def self.input_inclusion(parser, offset)
64
+ uri, reached = parser.parse(offset, "wlang/uri")
65
+
66
+ # decode expression
67
+ decoded = U.expr(:uri,
68
+ ["using", :using, false],
69
+ ["with", :with, false]).decode(uri, parser)
70
+ parser.syntax_error(offset) if decoded.nil?
71
+
72
+ # handle wit
73
+ context = nil
74
+ if decoded[:using]
75
+ raise "<<+ does not support multiple with for now." if decoded[:using].size != 1
76
+ context = decoded[:using][0]
77
+ raise "Unexpected nil context when duplicated" if context.nil?
78
+ else
79
+ context = {}
80
+ end
81
+
82
+ # handle using now
83
+ if decoded[:with]
84
+ case context
85
+ when WLang::Parser::Context::HashScope
86
+ context = context.__branch(decoded[:with])
87
+ when Hash
88
+ context = context.merge(decoded[:with])
89
+ else
90
+ raise "Unexpected context #{context}"
91
+ end
92
+ end
93
+
94
+ file = parser.template.file_resolve(decoded[:uri], true)
95
+ instantiated = WLang::file_instantiate(file, context)
96
+ [instantiated, reached]
97
+ end
98
+
99
+
100
+ end # module Buffering
101
+
102
+ end
103
+ end