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,16 @@
1
+ <table class="symbols">
2
+ <tr>
3
+ <th class="name">name</th>
4
+ <th class="symbol">symbol</th>
5
+ <th class="meaning">meaning</th>
6
+ <th class="remark">remark</th>
7
+ </tr>
8
+ *{spec/symbols as s}{
9
+ <tr>
10
+ <td><em>${s/name}</em></td>
11
+ <td>${s/symbol}</td>
12
+ <td>${s/meaning}</td>
13
+ <td>${s/remark}</td>
14
+ </tr>
15
+ }
16
+ </table>
data/lib/wlang.rb ADDED
@@ -0,0 +1,186 @@
1
+ require 'wlang/ruby_extensions'
2
+ require 'stringio'
3
+ require 'wlang/rule'
4
+ require 'wlang/rule_set'
5
+ require 'wlang/encoder_set'
6
+ require 'wlang/dialect'
7
+ require 'wlang/dialect_dsl'
8
+ require 'wlang/dialect_loader'
9
+ require 'wlang/parser'
10
+ require 'wlang/parser_context'
11
+ require 'wlang/intelligent_buffer'
12
+
13
+ #
14
+ # Main module of the _wlang_ code generator/template engine, providing a facade
15
+ # on _wlang_ tools. See also the Roadmap section of {README}[link://files/README.html]
16
+ # to enter the library.
17
+ #
18
+ module WLang
19
+
20
+ # Current version of WLang
21
+ VERSION = "0.8.4".freeze
22
+
23
+ # Reusable string for building dialect name based regexps
24
+ DIALECT_NAME_REGEXP_STR = "[-a-z]+"
25
+
26
+ #
27
+ # Regular expression for dialect names.
28
+ #
29
+ DIALECT_NAME_REGEXP = /^([-a-z]+)*$/
30
+
31
+ # Reusable string for building dialect name based regexps
32
+ QUALIFIED_DIALECT_NAME_REGEXP_STR = "[-a-z]+([\/][-a-z]+)*"
33
+
34
+ #
35
+ # Regular expression for dialect qualified names. Dialect qualified names are
36
+ # '/' seperated names, where a name is [-a-z]+.
37
+ # Examples: wlang/xhtml/uri, wlang/plain-text, ...
38
+ #
39
+ QUALIFIED_DIALECT_NAME_REGEXP = /^[-a-z]+([\/][-a-z]+)*$/
40
+
41
+ # Reusable string for building encoder name based regexps
42
+ ENCODER_NAME_REGEXP_STR = "[-a-z]+"
43
+
44
+ #
45
+ # Regular expression for encoder names.
46
+ #
47
+ ENCODER_NAME_REGEXP = /^([-a-z]+)*$/
48
+
49
+ #
50
+ # Regular expression for encoder qualified names. Encoder qualified names are
51
+ # '/' seperated names, where a name is [-a-z]+.
52
+ # Examples: xhtml/entities-encoding, sql/single-quoting, ...
53
+ #
54
+ QUALIFIED_ENCODER_NAME_REGEXP = /^([-a-z]+)([\/][-a-z]+)*$/
55
+
56
+ # Reusable string for building qualified encoder name based regexps
57
+ QUALIFIED_ENCODER_NAME_REGEXP_STR = "[-a-z]+([\/][-a-z]+)*"
58
+
59
+ #
60
+ # Provides installed {file extension => dialect} mappings. File extensions
61
+ # (keys) contain the first dot (like .wtpl, .whtml, ...). Dialects (values) are
62
+ # qualified names, not Dialect instances.
63
+ #
64
+ FILE_EXTENSIONS = {}
65
+
66
+ #
67
+ # Provides installed {file extension => data loader} mapping. File extensions
68
+ # (keys) contain the first dot (like .wtpl, .whtml, ...). Data loades are
69
+ # Proc instances that take a single |uri| argument.
70
+ #
71
+ DATA_EXTENSIONS = {}
72
+
73
+ #
74
+ # Main anonymous dialect. All installed dialects are children of this one,
75
+ # which is anonymous because it does not appear in qualified names.
76
+ #
77
+ @dialect = Dialect.new("", nil)
78
+
79
+ #
80
+ # Installs or query a dialect.
81
+ #
82
+ # <b>When called with a block</b>, this method installs a _wlang_ dialect under
83
+ # _name_ (which cannot be qualified). Extensions can be provided to let _wlang_
84
+ # automatically recognize files that are expressed in this dialect. The block
85
+ # is interpreted as code in the dialect DSL (domain specific language, see
86
+ # WLang::Dialect::DSL). Returns nil in this case.
87
+ #
88
+ # <b>When called without a block</b> this method returns a Dialect instance
89
+ # installed under name (which can be a qualified name). Extensions are ignored
90
+ # in this case. Returns nil if not found, a Dialect instance otherwise.
91
+ #
92
+ def self.dialect(name, *extensions, &block)
93
+ if block_given?
94
+ raise "Unsupported qualified names in dialect installation"\
95
+ unless name.index('/').nil?
96
+ Dialect::DSL.new(@dialect).dialect(name, *extensions, &block).build!
97
+ else
98
+ @dialect.dialect(name)
99
+ end
100
+ end
101
+
102
+ #
103
+ # Adds a data loader for file extensions.
104
+ #
105
+ def self.data_loader(*exts, &block)
106
+ raise(ArgumentError, "Block expected") unless block_given?
107
+ raise(ArgumentError, "Block of arity 1 expected") unless block.arity==1
108
+ exts.each do |ext|
109
+ DATA_EXTENSIONS[ext] = block
110
+ end
111
+ end
112
+
113
+ #
114
+ # Loads data from a given URI. If _extension_ is omitted, tries to infer it
115
+ # from the uri, otherwise use it directly. Returns loaded data.
116
+ #
117
+ def self.load_data(uri, extension=nil)
118
+ if extension.nil?
119
+ extension = File.extname(uri)
120
+ raise("Unable to infer data loader from #{uri}") if extension.nil?
121
+ end
122
+ loader = DATA_EXTENSIONS[extension]
123
+ raise("No data loader for #{extension}") if loader.nil?
124
+ loader.call(uri)
125
+ end
126
+
127
+ # Infers a dialect from a file extension
128
+ def self.infer_dialect(uri)
129
+ WLang::FILE_EXTENSIONS[File.extname(uri)]
130
+ end
131
+
132
+ #
133
+ # Returns an encoder installed under a qualified name. Returns nil if not
134
+ # found.
135
+ #
136
+ def self.encoder(name)
137
+ @dialect.encoder(name)
138
+ end
139
+
140
+ #
141
+ # Instantiates a template written in some _wlang_ dialect, using a given _context_
142
+ # (providing instantiation data). Returns instantiatiation as a String. If you want
143
+ # to instantiate the template in a specific buffer (a file or console for example),
144
+ # use Template. _template_ is expected to be a String and _context_ a Hash. To
145
+ # know available dialects, see WLang::StandardDialects. <em>block_symbols</em>
146
+ # can be <tt>:braces</tt> ('{' and '}' pairs), <tt>:brackets</tt> ('[' and ']'
147
+ # pairs) or <tt>:parentheses</tt> ('(' and ')' pairs).
148
+ #
149
+ # Examples:
150
+ # WLang.instantiate "Hello ${who} !", {"who" => "Mr. Jones"}
151
+ # WLang.instantiate "SELECT * FROM people WHERE name='{name}'", {"who" => "Mr. O'Neil"}, "wlang/sql"
152
+ # WLang.instantiate "Hello $(who) !", {"who" => "Mr. Jones"}, "wlang/active-string", :parentheses
153
+ #
154
+ def self.instantiate(template, context=nil, dialect="wlang/active-string", block_symbols = :braces)
155
+ WLang::Template.new(template, dialect, context, block_symbols).instantiate
156
+ end
157
+
158
+ #
159
+ # Instantiates a file written in some _wlang_ dialect, using a given _context_
160
+ # (providing instantiation data). Outputs instantiation in _buffer_ (any object
161
+ # responding to <tt><<</tt>, usually a IO; String is supported as well). If
162
+ # _dialect_ is nil, tries to infer it from the file extension; otherwise _dialect_
163
+ # is expected to be a qualified dialect name. See instantiate about <tt>block_symbols</tt>.
164
+ # Returns _buffer_.
165
+ #
166
+ # Examples:
167
+ # Wlang.file_instantiate "template.wtpl", {"who" => "Mr. Jones"}
168
+ # Wlang.file_instantiate "template.wtpl", {"who" => "Mr. Jones"}, STDOUT
169
+ # Wlang.file_instantiate "template.xxx", {"who" => "Mr. Jones"}, STDOUT, "wlang/xhtml"
170
+ #
171
+ def self.file_instantiate(file, context=nil, buffer=nil, dialect=nil, block_symbols = :braces)
172
+ raise "File '#{file}' does not exists or is unreadable"\
173
+ unless File.exists?(file) and File.readable?(file)
174
+ if dialect.nil?
175
+ dialect = infer_dialect(file) if dialect.nil?
176
+ raise("No known dialect for file extension '#{File.extname(file)}'\n"\
177
+ "Known extensions are: " << WLang::FILE_EXTENSIONS.keys.join(", "))\
178
+ if dialect.nil?
179
+ end
180
+ t = WLang::Template.new(File.read(file), dialect, context, block_symbols)
181
+ t.source_file = file
182
+ t.instantiate(buffer)
183
+ end
184
+
185
+ end
186
+ require 'wlang/dialects/standard_dialects'
@@ -0,0 +1,19 @@
1
+ module WLang
2
+
3
+ #
4
+ # Provides an Object with all inherited instance methods removed.
5
+ #
6
+ class BasicObject
7
+
8
+ # Methods that we keep
9
+ KEPT_METHODS = ["__send__", "__id__", "instance_eval", "initialize", "object_id",
10
+ :__send__, :__id__, :instance_eval, :initialize, :object_id]
11
+
12
+ # Removes all methods that are not needed to the class
13
+ (instance_methods + private_instance_methods).each do |m|
14
+ undef_method(m) unless KEPT_METHODS.include?(m)
15
+ end
16
+
17
+ end # class BasicObject
18
+
19
+ end
@@ -0,0 +1,230 @@
1
+ require 'wlang/encoder_set'
2
+ require 'wlang/rule_set'
3
+ module WLang
4
+
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
8
+ # and RuleSet classes). A dialect is also a node in the dialect tree and has a
9
+ # qualified name through this tree. For example <tt>wlang/xhtml</tt> is the
10
+ # qualified name of a <tt>xhtml</tt> dialect which is a child dialect of
11
+ # <tt>wlang</tt>.
12
+ #
13
+ # Users are not intended to use this class directly. Use the Domain Specific
14
+ # Language instead (see WLang::Dialect::DSL).
15
+ #
16
+ # === For developers only
17
+ # In order to avoid having users to install all required gems of all dialects
18
+ # wlang implements a lazy load design pattern on the dialect tree, through the
19
+ # WLang::Dialect::DSL and WLang::Dialect::Loader classes. The former only creates
20
+ # Dialect instances as tree nodes (by chaining dialects through @parent) and
21
+ # installs mapping with file extensions. Rules and encoders are not initially
22
+ # installed (precisely: WLang::Dialect::DSL#require_ruby is simply ignored).
23
+ # When a given dialect is needed by wlang it is first built (through the build!
24
+ # method and the WLang::Dialect::Loader class).
25
+ #
26
+ # Standard dialect obtention methods (WLang#dialect as well as WLang::Dialect#dialect)
27
+ # ensure that returned dialects are built. If you obtain dialects another way,
28
+ # be sure that they are built before using them (is_built? and build! are your
29
+ # friends to achive that goal).
30
+ #
31
+ # Moreover, child dialects may require tools of their ancestors. The following
32
+ # invariant should always be respected: if a dialect is built, all its ancestors
33
+ # are built as well. This invariant is not enforced by the build! method because
34
+ # it is trivially respected by the way WLang::Dialect#dialect is implemented.
35
+ #
36
+ class Dialect
37
+
38
+ # Underlying ruleset
39
+ attr_reader :ruleset
40
+
41
+ # Underlying encoders
42
+ attr_reader :encoders
43
+
44
+ # Dialect name
45
+ attr_reader :name
46
+
47
+ # Parent dialect
48
+ attr_reader :parent
49
+
50
+ #
51
+ # Creates a dialect instance. _builder_ block is a chunk of code of the DSL
52
+ # that will be executed twice: once at construction time to create sub dialects
53
+ # nodes and install file extensions and once at building time to install ruleset
54
+ # and encoders.
55
+ #
56
+ def initialize(name, parent, &builder)
57
+ @name, @parent = name, parent
58
+ @builder, @built = builder, builder.nil?
59
+ @dialects = nil
60
+ @encoders = nil
61
+ @ruleset = nil
62
+ DSL.new(self).instance_eval(&builder) unless builder.nil?
63
+ end
64
+
65
+ ### Lazy load mechanism ######################################################
66
+
67
+ #
68
+ # Force the dialect to be built. Has no effect if it is already built. Invokes
69
+ # the DSL chunk of code through WLang::DSL::Loader otherwise.
70
+ #
71
+ def build!
72
+ unless is_built?
73
+ WLang::Dialect::Loader.new(self).instance_eval(&@builder)
74
+ @built = true
75
+ end
76
+ self
77
+ end
78
+
79
+ # Returns true if this dialect is already built, false otherwise.
80
+ def is_built?
81
+ return @built
82
+ end
83
+
84
+
85
+ ### Installation #############################################################
86
+
87
+ #
88
+ # Adds a child dialect under _name_. _name_ cannot be qualified and must be a
89
+ # valid dialect name according to the wlang specification (see WLang::DIALECT_NAME_REGEXP).
90
+ # _child_ must be a Dialect instance.
91
+ #
92
+ def add_child_dialect(name, child)
93
+ raise(ArgumentError, "Invalid dialect name") unless WLang::DIALECT_NAME_REGEXP =~ name
94
+ raise(ArgumentError, "Dialect expected") unless Dialect===child
95
+ @dialects = {} if @dialects.nil?
96
+ @dialects[name] = child
97
+ end
98
+
99
+ # See EncoderSet#add_encoder
100
+ def add_encoder(name, &block)
101
+ @encoders = EncoderSet.new if @encoders.nil?
102
+ @encoders.add_encoder(name, &block)
103
+ end
104
+
105
+ # See EncoderSet#add_encoders
106
+ def add_encoders(mod, pairs)
107
+ @encoders = EncoderSet.new if @encoders.nil?
108
+ @encoders.add_encoders(mod, pairs)
109
+ end
110
+
111
+ # See RuleSet::add_rule
112
+ def add_rule(name, &block)
113
+ @ruleset = RuleSet.new if @ruleset.nil?
114
+ @ruleset.add_rule(name, &block)
115
+ end
116
+
117
+ # See RuleSet::add_rules
118
+ def add_rules(mod, pairs)
119
+ @ruleset = RuleSet.new if @ruleset.nil?
120
+ @ruleset.add_rules(mod, pairs)
121
+ end
122
+
123
+ ### Query API ################################################################
124
+
125
+ # Returns qualified name of this dialect
126
+ def qualified_name
127
+ parentname = @parent.nil? ? "" : @parent.to_s
128
+ return ""==parentname ? @name : parentname + '/' + @name
129
+ end
130
+
131
+ #
132
+ # Finds a child dialect by name. _name_ can be a String denoting a qualified
133
+ # name as well as an Array of strings, resulting from a qualified name split.
134
+ # This method should always be invoked on built dialects, it always returns nil
135
+ # otherwise. When found, returned dialect is automatically built as well as all
136
+ # its ancestors. When not found, the method returns nil.
137
+ #
138
+ def dialect(name)
139
+ # implement argument conventions
140
+ if String===name
141
+ raise(ArgumentError, "Invalid dialect name #{name}") unless WLang::QUALIFIED_DIALECT_NAME_REGEXP =~ name
142
+ name = name.split('/')
143
+ elsif not(Array===name)
144
+ raise(ArgumentError,"Invalid dialect name #{name}")
145
+ end
146
+
147
+ # not built or no child at all
148
+ return nil if @dialects.nil?
149
+
150
+ # get first child name and find it
151
+ child_name = name[0]
152
+ child_dialect = @dialects[child_name]
153
+
154
+ if child_dialect.nil?
155
+ # unexisting, return nil
156
+ return nil
157
+ elsif name.length==1
158
+ # found and last of qualified name -> build it
159
+ return child_dialect.build!
160
+ else
161
+ # found but not last of qualified name -> build it and delegate
162
+ child_dialect.build!
163
+ return child_dialect.dialect(name[1..-1])
164
+ end
165
+ end
166
+
167
+ #
168
+ # Finds an encoder by name.
169
+ #
170
+ def encoder(name)
171
+ # implement argument conventions
172
+ if String===name
173
+ raise(ArgumentError, "Invalid encoder name #{name}") unless WLang::QUALIFIED_ENCODER_NAME_REGEXP =~ name
174
+ name = name.split('/')
175
+ elsif not(Array===name)
176
+ raise(ArgumentError,"Invalid encoder name #{name}")
177
+ end
178
+
179
+ # last name in qualified?
180
+ if name.length==1
181
+ # delegate to encoders
182
+ return nil if @encoders.nil?
183
+ return @encoders.get_encoder(name[0])
184
+ else
185
+ # find sub dialect, and delegate
186
+ child_name = name[0]
187
+ child_dialect = dialect(child_name)
188
+ if child_dialect.nil?
189
+ return nil
190
+ else
191
+ return child_dialect.encoder(name[1..-1])
192
+ end
193
+ end
194
+ end
195
+
196
+ # Finds a encoder in dialect tree
197
+ def find_encoder(name)
198
+ raise(ArgumentError, "Invalid (relative) encoder name #{name}") unless String===name
199
+ raise(ArgumentError, "Invalid (relative) encoder name #{name}") if name.include?("/")
200
+ return nil if @encoders.nil?
201
+ if @encoders.has_encoder?(name)
202
+ @encoders.get_encoder(name)
203
+ elsif @parent
204
+ @parent.find_encoder(name)
205
+ else
206
+ nil
207
+ end
208
+ end
209
+
210
+ # See RuleSet#pattern.
211
+ def pattern(block_symbols)
212
+ return RuleSet.new.pattern(block_symbols) if @ruleset.nil?
213
+ @ruleset.pattern(block_symbols)
214
+ end
215
+
216
+ ### Other utilities ##########################################################
217
+
218
+ # Factors a spacing friendly buffer for instantiation in this dialect
219
+ def factor_buffer
220
+ IntelligentBuffer.new
221
+ end
222
+
223
+ # Returns a string representation
224
+ def to_s
225
+ qualified_name
226
+ end
227
+
228
+ end # class Dialect
229
+
230
+ end #module WLang