jsduck 0.5 → 0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +30 -2
- data/bin/jsduck +24 -2
- data/jsduck.gemspec +2 -2
- data/lib/jsduck/aggregator.rb +53 -32
- data/lib/jsduck/app.rb +71 -22
- data/lib/jsduck/cfg_table.rb +2 -2
- data/lib/jsduck/class.rb +5 -0
- data/lib/jsduck/css_parser.rb +75 -0
- data/lib/jsduck/doc_formatter.rb +111 -20
- data/lib/jsduck/doc_parser.rb +64 -4
- data/lib/jsduck/event_table.rb +3 -3
- data/lib/jsduck/exporter.rb +11 -9
- data/lib/jsduck/inheritance_tree.rb +2 -7
- data/lib/jsduck/{parser.rb → js_parser.rb} +20 -4
- data/lib/jsduck/lexer.rb +1 -2
- data/lib/jsduck/long_params.rb +4 -8
- data/lib/jsduck/members.rb +15 -1
- data/lib/jsduck/merger.rb +50 -5
- data/lib/jsduck/method_table.rb +4 -8
- data/lib/jsduck/page.rb +20 -9
- data/lib/jsduck/property_table.rb +2 -2
- data/lib/jsduck/relations.rb +4 -0
- data/lib/jsduck/source_file.rb +101 -0
- data/lib/jsduck/{source_formatter.rb → source_writer.rb} +14 -27
- data/lib/jsduck/table.rb +12 -11
- data/template/resources/docs.js +43 -51
- metadata +8 -6
data/README.md
CHANGED
@@ -54,7 +54,7 @@ it would like that you wrote comments like that instead:
|
|
54
54
|
/**
|
55
55
|
* Basic text field. Can be used as a direct replacement for traditional
|
56
56
|
* text inputs, or as the base class for more sophisticated input controls
|
57
|
-
* (like
|
57
|
+
* (like Ext.form.TextArea and Ext.form.ComboBox).
|
58
58
|
*
|
59
59
|
* Validation
|
60
60
|
* ----------
|
@@ -197,12 +197,40 @@ Copying
|
|
197
197
|
JsDuck is distributed under the terms of the GNU General Public License version 3.
|
198
198
|
|
199
199
|
JsDuck was developed by [Rene Saarsoo](http://triin.net),
|
200
|
-
with contributions from [Ondřej Jirman](https://github.com/megous)
|
200
|
+
with contributions from [Ondřej Jirman](https://github.com/megous)
|
201
|
+
and [Nick Poulden](https://github.com/nick).
|
201
202
|
|
202
203
|
|
203
204
|
Changelog
|
204
205
|
---------
|
205
206
|
|
207
|
+
* 0.6 - JsDuck is now used for creating the official ExtJS4 documentation.
|
208
|
+
* Automatic linking of class names found in comments. Instead of writing
|
209
|
+
`{@link Ext.Panel}` one can simply write `Ext.Panel` and link will be
|
210
|
+
automatically created.
|
211
|
+
* In generated docs, method return types and parameter types are also
|
212
|
+
automatically linked to classes if such class is included to docs.
|
213
|
+
* Support for `{@img}` tag for including images to documentation.
|
214
|
+
The markup created by `{@link}` and `{@img}` tags can now be customized using
|
215
|
+
the --img and --link command line options to supply HTML templates.
|
216
|
+
* Links to source code are no more simply links to line numbers.
|
217
|
+
Instead the source code files will contain ID-s like `MyClass-cfg-style`.
|
218
|
+
* New tags: `@docauthor`, `@alternateClassName`, `@mixins`.
|
219
|
+
The latter two Ext4 class properties are both detected from code and
|
220
|
+
can also be defined (or overriden) in doc-comments.
|
221
|
+
* Global methods are now placed to separate "global" class.
|
222
|
+
Creation of this can be turned off using `--ignore-global`.
|
223
|
+
* Much improved search feature.
|
224
|
+
Search results are now ordered so that best matches are at the top.
|
225
|
+
No more is there a select-box to match at beginning/middle/end -
|
226
|
+
we automatically search first by exact match, then beginning and
|
227
|
+
finally by middle. Additionally the search no more lists a lot of
|
228
|
+
duplicates - only the class that defines a method is listed, ignoring
|
229
|
+
all the classes that inherit it.
|
230
|
+
* Support for doc-comments in [SASS](http://sass-lang.com/) .scss files:
|
231
|
+
For now, it's possible to document SASS variables and mixins.
|
232
|
+
* Several bug fixes.
|
233
|
+
|
206
234
|
* 0.5 - Search and export
|
207
235
|
* Search from the actually generated docs (not through sencha.com)
|
208
236
|
* JSON export with --json switch.
|
data/bin/jsduck
CHANGED
@@ -34,10 +34,28 @@ opts = OptionParser.new do | opts |
|
|
34
34
|
app.template_dir = path
|
35
35
|
end
|
36
36
|
|
37
|
-
opts.on('--json', "Produces JSON export instead of HTML documentation.") do
|
37
|
+
opts.on('--json', "Produces JSON export instead of HTML documentation.") do
|
38
38
|
app.export = :json
|
39
39
|
end
|
40
40
|
|
41
|
+
opts.on('--link=TPL', "HTML template for replacing {@link}.",
|
42
|
+
"Possible placeholders:",
|
43
|
+
"%c - full class name (e.g. 'Ext.Panel')",
|
44
|
+
"%m - class member name (e.g. 'urlEncode')",
|
45
|
+
"%M - class member name, prefixed with hash (e.g. '#urlEncode')",
|
46
|
+
"%a - anchor text for link",
|
47
|
+
"Default value in export: '<a href=\"%c%M\">%a</a>'") do |tpl|
|
48
|
+
app.link_tpl = tpl
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on('--img=TPL', "HTML template for replacing {@img}.",
|
52
|
+
"Possible placeholders:",
|
53
|
+
"%u - URL from @img tag (e.g. 'some/path.png')",
|
54
|
+
"%a - alt text for image",
|
55
|
+
"Default value in export: '<img src=\"%u\" alt=\"%a\"/>'") do |tpl|
|
56
|
+
app.img_tpl = tpl
|
57
|
+
end
|
58
|
+
|
41
59
|
# For debugging it's often useful to set --processes=0 to get deterministic results.
|
42
60
|
opts.on('-p', '--processes=COUNT', "The number of parallel processes to use.",
|
43
61
|
"Defaults to the number of processors/cores.",
|
@@ -45,6 +63,10 @@ opts = OptionParser.new do | opts |
|
|
45
63
|
app.processes = count.to_i
|
46
64
|
end
|
47
65
|
|
66
|
+
opts.on('--ignore-global', "Turns off the creation of global class.") do
|
67
|
+
app.ignore_global = true
|
68
|
+
end
|
69
|
+
|
48
70
|
opts.on('-v', '--verbose', "This will fill up your console.") do
|
49
71
|
app.verbose = true
|
50
72
|
end
|
@@ -60,7 +82,7 @@ js_files = []
|
|
60
82
|
opts.parse!(ARGV).each do |fname|
|
61
83
|
if File.exists?(fname)
|
62
84
|
if File.directory?(fname)
|
63
|
-
Dir[fname+"/**/*.js"].each {|f| js_files << f }
|
85
|
+
Dir[fname+"/**/*.{js,css,scss}"].each {|f| js_files << f }
|
64
86
|
else
|
65
87
|
js_files << fname
|
66
88
|
end
|
data/jsduck.gemspec
CHANGED
@@ -2,8 +2,8 @@ Gem::Specification.new do |s|
|
|
2
2
|
s.required_rubygems_version = ">= 1.3.7"
|
3
3
|
|
4
4
|
s.name = 'jsduck'
|
5
|
-
s.version = '0.
|
6
|
-
s.date = '2011-
|
5
|
+
s.version = '0.6'
|
6
|
+
s.date = '2011-04-27'
|
7
7
|
s.summary = "Simple JavaScript Duckumentation generator"
|
8
8
|
s.description = "Better ext-doc like JavaScript documentation generator for ExtJS"
|
9
9
|
s.homepage = "https://github.com/nene/jsduck"
|
data/lib/jsduck/aggregator.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'jsduck/merger'
|
2
|
-
|
3
1
|
module JsDuck
|
4
2
|
|
5
3
|
# Combines JavaScript Parser, DocParser and Merger.
|
@@ -10,44 +8,17 @@ module JsDuck
|
|
10
8
|
@classes = {}
|
11
9
|
@orphans = []
|
12
10
|
@current_class = nil
|
13
|
-
@merger = Merger.new
|
14
11
|
end
|
15
12
|
|
16
13
|
# Combines chunk of parsed JavaScript together with previously
|
17
14
|
# added chunks. The resulting documentation is accumulated inside
|
18
15
|
# this class and can be later accessed through #result method.
|
19
16
|
#
|
20
|
-
# -
|
21
|
-
# - filename name of the JS file where it came from
|
22
|
-
# - html_filename name of the HTML file where the source was saved.
|
17
|
+
# - file SoureFile class instance
|
23
18
|
#
|
24
|
-
def aggregate(
|
19
|
+
def aggregate(file)
|
25
20
|
@current_class = nil
|
26
|
-
|
27
|
-
doc = @merger.merge(docset[:comment], docset[:code])
|
28
|
-
|
29
|
-
add_source_data(doc, {
|
30
|
-
:filename => filename,
|
31
|
-
:html_filename => html_filename,
|
32
|
-
:linenr => docset[:linenr],
|
33
|
-
})
|
34
|
-
|
35
|
-
register(doc)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# Links doc-object to source code where it came from.
|
40
|
-
def add_source_data(doc, src)
|
41
|
-
doc[:href] = src[:html_filename] + "#line-" + src[:linenr].to_s
|
42
|
-
doc[:filename] = src[:filename]
|
43
|
-
doc[:linenr] = src[:linenr]
|
44
|
-
# class-level doc-comment can contain constructor and config
|
45
|
-
# options, link those to the same location in source.
|
46
|
-
if doc[:tagname] == :class
|
47
|
-
doc[:cfg].each {|cfg| add_source_data(cfg, src) }
|
48
|
-
doc[:method].each {|method| add_source_data(method, src) }
|
49
|
-
end
|
50
|
-
doc
|
21
|
+
file.each {|doc| register(doc) }
|
51
22
|
end
|
52
23
|
|
53
24
|
# Registers documentation node either as class or as member of
|
@@ -80,6 +51,9 @@ module JsDuck
|
|
80
51
|
[:extends, :xtype, :singleton, :private].each do |tag|
|
81
52
|
old[tag] = old[tag] || new[tag]
|
82
53
|
end
|
54
|
+
[:mixins, :alternateClassNames].each do |tag|
|
55
|
+
old[tag] = old[tag] + new[tag]
|
56
|
+
end
|
83
57
|
old[:doc] = old[:doc].length > 0 ? old[:doc] : new[:doc]
|
84
58
|
old[:cfg] = old[:cfg] + new[:cfg]
|
85
59
|
end
|
@@ -121,6 +95,53 @@ module JsDuck
|
|
121
95
|
end
|
122
96
|
end
|
123
97
|
|
98
|
+
# Creates classes for orphans that have :member property defined,
|
99
|
+
# and then inserts orphans to these classes.
|
100
|
+
def classify_orphans
|
101
|
+
@orphans.each do |orph|
|
102
|
+
if orph[:member]
|
103
|
+
class_name = orph[:member]
|
104
|
+
if !@classes[class_name]
|
105
|
+
add_empty_class(class_name)
|
106
|
+
end
|
107
|
+
add_member(orph)
|
108
|
+
@orphans.delete(orph)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Creates class with name "global" and inserts all the remaining
|
114
|
+
# orphans into it (but only if there are any orphans).
|
115
|
+
def create_global_class
|
116
|
+
return if @orphans.length == 0
|
117
|
+
|
118
|
+
add_empty_class("global", "Global variables and functions.")
|
119
|
+
@orphans.each do |orph|
|
120
|
+
orph[:member] = "global"
|
121
|
+
add_member(orph)
|
122
|
+
end
|
123
|
+
@orphans = []
|
124
|
+
end
|
125
|
+
|
126
|
+
def add_empty_class(name, doc = "")
|
127
|
+
add_class({
|
128
|
+
:tagname => :class,
|
129
|
+
:name => name,
|
130
|
+
:doc => doc,
|
131
|
+
:mixins => [],
|
132
|
+
:alternateClassNames => [],
|
133
|
+
:cfg => [],
|
134
|
+
:property => [],
|
135
|
+
:method => [],
|
136
|
+
:event => [],
|
137
|
+
:css_var => [],
|
138
|
+
:css_mixin => [],
|
139
|
+
:filename => "",
|
140
|
+
:html_filename => "",
|
141
|
+
:linenr => 0,
|
142
|
+
})
|
143
|
+
end
|
144
|
+
|
124
145
|
def result
|
125
146
|
@documentation + @orphans
|
126
147
|
end
|
data/lib/jsduck/app.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'jsduck/parser'
|
3
2
|
require 'jsduck/aggregator'
|
4
|
-
require 'jsduck/
|
3
|
+
require 'jsduck/source_file'
|
4
|
+
require 'jsduck/source_writer'
|
5
5
|
require 'jsduck/class'
|
6
6
|
require 'jsduck/tree'
|
7
7
|
require 'jsduck/tree_icons'
|
@@ -24,6 +24,9 @@ module JsDuck
|
|
24
24
|
attr_accessor :input_files
|
25
25
|
attr_accessor :verbose
|
26
26
|
attr_accessor :export
|
27
|
+
attr_accessor :link_tpl
|
28
|
+
attr_accessor :img_tpl
|
29
|
+
attr_accessor :ignore_global
|
27
30
|
|
28
31
|
def initialize
|
29
32
|
@output_dir = nil
|
@@ -31,6 +34,9 @@ module JsDuck
|
|
31
34
|
@input_files = []
|
32
35
|
@verbose = false
|
33
36
|
@export = nil
|
37
|
+
@link_tpl = nil
|
38
|
+
@img_tpl = nil
|
39
|
+
@ignore_global = false
|
34
40
|
@timer = Timer.new
|
35
41
|
@parallel = ParallelWrap.new
|
36
42
|
end
|
@@ -43,21 +49,21 @@ module JsDuck
|
|
43
49
|
|
44
50
|
# Call this after input parameters set
|
45
51
|
def run
|
46
|
-
clear_dir(@output_dir)
|
47
|
-
if @export
|
48
|
-
FileUtils.mkdir(@output_dir)
|
49
|
-
init_output_dirs(@output_dir)
|
50
|
-
else
|
51
|
-
copy_template(@template_dir, @output_dir)
|
52
|
-
end
|
53
|
-
|
54
52
|
parsed_files = @timer.time(:parsing) { parallel_parse(@input_files) }
|
55
53
|
result = @timer.time(:aggregating) { aggregate(parsed_files) }
|
56
54
|
relations = @timer.time(:aggregating) { filter_classes(result) }
|
55
|
+
warn_globals(relations)
|
56
|
+
warn_unnamed(relations)
|
57
57
|
|
58
|
+
clear_dir(@output_dir)
|
58
59
|
if @export == :json
|
60
|
+
FileUtils.mkdir(@output_dir)
|
61
|
+
init_output_dirs(@output_dir)
|
62
|
+
@timer.time(:generating) { write_src(@output_dir+"/source", parsed_files) }
|
59
63
|
@timer.time(:generating) { write_json(@output_dir+"/output", relations) }
|
60
64
|
else
|
65
|
+
copy_template(@template_dir, @output_dir)
|
66
|
+
@timer.time(:generating) { write_src(@output_dir+"/source", parsed_files) }
|
61
67
|
@timer.time(:generating) { write_tree(@output_dir+"/output/tree.js", relations) }
|
62
68
|
@timer.time(:generating) { write_members(@output_dir+"/output/members.js", relations) }
|
63
69
|
@timer.time(:generating) { write_pages(@output_dir+"/output", relations) }
|
@@ -68,15 +74,9 @@ module JsDuck
|
|
68
74
|
|
69
75
|
# Parses the files in parallel using as many processes as available CPU-s
|
70
76
|
def parallel_parse(filenames)
|
71
|
-
src = SourceFormatter.new(@output_dir + "/source", @export ? :format_pre : :format_page)
|
72
77
|
@parallel.map(filenames) do |fname|
|
73
78
|
puts "Parsing #{fname} ..." if @verbose
|
74
|
-
|
75
|
-
{
|
76
|
-
:filename => fname,
|
77
|
-
:html_filename => File.basename(src.write(code, fname)),
|
78
|
-
:data => Parser.new(code).parse,
|
79
|
-
}
|
79
|
+
SourceFile.new(IO.read(fname), fname)
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
@@ -84,9 +84,11 @@ module JsDuck
|
|
84
84
|
def aggregate(parsed_files)
|
85
85
|
agr = Aggregator.new
|
86
86
|
parsed_files.each do |file|
|
87
|
-
puts "Aggregating #{file
|
88
|
-
agr.aggregate(file
|
87
|
+
puts "Aggregating #{file.filename} ..." if @verbose
|
88
|
+
agr.aggregate(file)
|
89
89
|
end
|
90
|
+
agr.classify_orphans
|
91
|
+
agr.create_global_class unless @ignore_global
|
90
92
|
agr.result
|
91
93
|
end
|
92
94
|
|
@@ -108,6 +110,35 @@ module JsDuck
|
|
108
110
|
Relations.new(classes)
|
109
111
|
end
|
110
112
|
|
113
|
+
# print warning for each global member
|
114
|
+
def warn_globals(relations)
|
115
|
+
global = relations["global"]
|
116
|
+
return unless global
|
117
|
+
[:cfg, :property, :method, :event].each do |type|
|
118
|
+
global.members(type).each do |member|
|
119
|
+
name = member[:name]
|
120
|
+
file = member[:filename]
|
121
|
+
line = member[:linenr]
|
122
|
+
puts "Warning: Global #{type}: #{name} in #{file} line #{line}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# print warning for each member with no name
|
128
|
+
def warn_unnamed(relations)
|
129
|
+
relations.each do |cls|
|
130
|
+
[:cfg, :property, :method, :event].each do |type|
|
131
|
+
cls[type].each do |member|
|
132
|
+
if !member[:name] || member[:name] == ""
|
133
|
+
file = member[:filename]
|
134
|
+
line = member[:linenr]
|
135
|
+
puts "Warning: Unnamed #{type} in #{file} line #{line}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
111
142
|
# Given all classes, generates namespace tree and writes it
|
112
143
|
# in JSON form into a file.
|
113
144
|
def write_tree(filename, relations)
|
@@ -133,14 +164,20 @@ module JsDuck
|
|
133
164
|
@parallel.each(relations.classes) do |cls|
|
134
165
|
filename = path + "/" + cls[:name] + ".html"
|
135
166
|
puts "Writing to #{filename} ..." if @verbose
|
136
|
-
|
137
|
-
|
167
|
+
page = Page.new(cls, relations, cache)
|
168
|
+
page.link_tpl = @link_tpl if @link_tpl
|
169
|
+
page.img_tpl = @img_tpl if @img_tpl
|
170
|
+
File.open(filename, 'w') {|f| f.write(page.to_html) }
|
138
171
|
end
|
139
172
|
end
|
140
173
|
|
141
174
|
# Writes JSON export file for each class
|
142
175
|
def write_json(path, relations)
|
143
|
-
|
176
|
+
formatter = DocFormatter.new
|
177
|
+
formatter.link_tpl = @link_tpl if @link_tpl
|
178
|
+
formatter.img_tpl = @img_tpl if @img_tpl
|
179
|
+
formatter.relations = relations
|
180
|
+
exporter = Exporter.new(relations, formatter)
|
144
181
|
@parallel.each(relations.classes) do |cls|
|
145
182
|
filename = path + "/" + cls[:name] + ".json"
|
146
183
|
puts "Writing to #{filename} ..." if @verbose
|
@@ -150,6 +187,18 @@ module JsDuck
|
|
150
187
|
end
|
151
188
|
end
|
152
189
|
|
190
|
+
# Writes formatted HTML source code for each input file
|
191
|
+
def write_src(path, parsed_files)
|
192
|
+
src = SourceWriter.new(path, @export ? nil : :page)
|
193
|
+
# Can't be done in parallel, because file.html_filename= method
|
194
|
+
# updates all the doc-objects related to the file
|
195
|
+
parsed_files.each do |file|
|
196
|
+
html_filename = src.write(file.to_html, file.filename)
|
197
|
+
puts "Writing to #{html_filename} ..." if @verbose
|
198
|
+
file.html_filename = File.basename(html_filename)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
153
202
|
def copy_template(template_dir, dir)
|
154
203
|
puts "Copying template files to #{dir}..." if @verbose
|
155
204
|
FileUtils.cp_r(template_dir, dir)
|
data/lib/jsduck/cfg_table.rb
CHANGED
@@ -3,8 +3,8 @@ require 'jsduck/table'
|
|
3
3
|
module JsDuck
|
4
4
|
|
5
5
|
class CfgTable < Table
|
6
|
-
def initialize(cls, cache={})
|
7
|
-
super(cls, cache)
|
6
|
+
def initialize(cls, formatter, cache={})
|
7
|
+
super(cls, formatter, cache)
|
8
8
|
@type = :cfg
|
9
9
|
@id = @cls.full_name + "-configs"
|
10
10
|
@title = "Config Options"
|
data/lib/jsduck/class.rb
CHANGED
@@ -36,6 +36,11 @@ module JsDuck
|
|
36
36
|
@doc[:mixins] ? @doc[:mixins].collect {|classname| lookup(classname) }.compact : []
|
37
37
|
end
|
38
38
|
|
39
|
+
# Returns all mixins this class and its parent classes
|
40
|
+
def all_mixins
|
41
|
+
mixins + (parent ? parent.all_mixins : [])
|
42
|
+
end
|
43
|
+
|
39
44
|
# Looks up class object by name
|
40
45
|
# When not found, prints warning message.
|
41
46
|
def lookup(classname)
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'jsduck/lexer'
|
2
|
+
require 'jsduck/doc_parser'
|
3
|
+
|
4
|
+
module JsDuck
|
5
|
+
|
6
|
+
class CssParser
|
7
|
+
def initialize(input)
|
8
|
+
@lex = Lexer.new(input)
|
9
|
+
@doc_parser = DocParser.new(:css)
|
10
|
+
@docs = []
|
11
|
+
end
|
12
|
+
|
13
|
+
# Parses the whole CSS block and returns same kind of structure
|
14
|
+
# that JavaScript parser does.
|
15
|
+
def parse
|
16
|
+
while !@lex.empty? do
|
17
|
+
if look(:doc_comment)
|
18
|
+
comment = @lex.next(true)
|
19
|
+
@docs << {
|
20
|
+
:comment => @doc_parser.parse(comment[:value]),
|
21
|
+
:linenr => comment[:linenr],
|
22
|
+
:code => code_block
|
23
|
+
}
|
24
|
+
else
|
25
|
+
@lex.next
|
26
|
+
end
|
27
|
+
end
|
28
|
+
@docs
|
29
|
+
end
|
30
|
+
|
31
|
+
# <code-block> := <mixin> | <nop>
|
32
|
+
def code_block
|
33
|
+
if look("@", "mixin")
|
34
|
+
mixin
|
35
|
+
else
|
36
|
+
{:type => :nop}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# <mixin> := "@mixin" <css-ident>
|
41
|
+
def mixin
|
42
|
+
match("@", "mixin")
|
43
|
+
return {
|
44
|
+
:type => :css_mixin,
|
45
|
+
:name => look(:ident) ? css_ident : nil,
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
# <css-ident> := <ident> [ "-" <ident> ]*
|
50
|
+
def css_ident
|
51
|
+
chain = [match(:ident)]
|
52
|
+
while look("-", :ident) do
|
53
|
+
chain << match("-", :ident)
|
54
|
+
end
|
55
|
+
return chain.join("-")
|
56
|
+
end
|
57
|
+
|
58
|
+
# Matches all arguments, returns the value of last match
|
59
|
+
# When the whole sequence doesn't match, throws exception
|
60
|
+
def match(*args)
|
61
|
+
if look(*args)
|
62
|
+
last = nil
|
63
|
+
args.length.times { last = @lex.next }
|
64
|
+
last
|
65
|
+
else
|
66
|
+
throw "Expected: " + args.join(", ")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def look(*args)
|
71
|
+
@lex.look(*args)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|