wlang 0.8.4

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