luaof 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c6a1ca3a8d8612d70ca89b1c1810474db873a9cbaeba467874e4c9c62bca2ced
4
+ data.tar.gz: 98ee0e1f889f4bc99a30cdfd2d8f4590d80a4ab2e7fd01b6e157a17b02819355
5
+ SHA512:
6
+ metadata.gz: 796b1810e48641095effaedcb489c310fa96a2b2b2f57c01666c47bac0675873f04f5355e3aca601137a67e2bcc11529abfcc3ae76c27429a7036dd65ca8d51f
7
+ data.tar.gz: c0faed3de422a2adf1633d791cae880151f05caea064a48f8a0c412fa57ea7f26348b0fa967397647bb2183d837a0dda0193b671cf8fd8eac1ddfdc72f3385de
data/.rubocop.yml ADDED
@@ -0,0 +1,9 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ AllCops:
4
+ TargetRubyVersion: 3.1
5
+ NewCops: enable
6
+ DisabledByDefault: true
7
+
8
+ Lint/BooleanSymbol:
9
+ Enabled: false
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,7 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2024-06-01 09:14:58 UTC using RuboCop version 1.48.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # ChangeLog of luaof
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [0.1.0] - 2024-06-08
6
+
7
+ - Initial release
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Luaof
2
+
3
+ Luaof is an experimental Lua "our format" parser.
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ $ bundle add luaof
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ $ gem install luaof
14
+
15
+ ## Usage
16
+
17
+ Please see `exe/2latex` for the example.
18
+
19
+ ## Development
20
+
21
+ After checking out the repo, run `bin/setup` to install dependencies.
22
+ Then, run `rake test` to run the tests.
23
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
24
+
25
+ To install this gem onto your local machine, run `bundle exec rake install`.
26
+ To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
27
+
28
+ ## Contributing
29
+
30
+ Bug reports and pull requests are welcome.
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
data/exe/2latex ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/luaof'
5
+
6
+ lexer = Luaof::Lexer.new
7
+ parser = Luaof::Parser.new
8
+
9
+ $stdin.each_line(chomp: true) do |line|
10
+ lexer.push(line) do |token|
11
+ parser.push(token)
12
+ end
13
+ end
14
+
15
+ parser.parse_section!
16
+ parser.parse_api_indicator!
17
+
18
+ parser.register_mapping!
19
+ parser.register_refs!
20
+
21
+ Luaof::LaTeXRenderer.new($stdout, parser.labels, :ja).render(parser.document)
@@ -0,0 +1,438 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Luaof
6
+ class LaTeXRenderer
7
+ SECTION_LEVEL_TO_CONTROL_SEQUENCE = [nil, 'chapter', 'section', 'subsection', 'subsubsection'].freeze
8
+
9
+ def initialize(output, labels, lang)
10
+ @output = output
11
+ @labels = labels
12
+ @lang = lang
13
+ end
14
+
15
+ def render(document)
16
+ case document
17
+ in Array
18
+ document.each { |child| render(child) }
19
+ in Node[label: { tag: :ANSI }, children:]
20
+ case @lang
21
+ in :ja
22
+ print('ISO~Cの関数\\texttt{')
23
+ end
24
+ render(children)
25
+ print('}')
26
+ in Node[label: { tag: :manual }, children:]
27
+ children.each { |child| render(child) }
28
+
29
+ # EBNF
30
+ in Node[label: { tag: :Produc }, children:]
31
+ print("\\begin{align*}\n")
32
+ render_ebnf(children)
33
+ print('\\end{align*}')
34
+ in Node[label: { tag: :bnfNter |
35
+ :bnfrep |
36
+ :bnfopt |
37
+ :bnfter }]
38
+ print('\\(')
39
+ render_ebnf(document)
40
+ print('\\)')
41
+
42
+ in Node[label: { tag: :section, level:, title: } => label, children:]
43
+ control_sequence = SECTION_LEVEL_TO_CONTROL_SEQUENCE[level] or raise
44
+ print("\\#{control_sequence}{")
45
+ render(title)
46
+ print('}')
47
+ id = label&.dig(:id) and print("\\label{#{id}}")
48
+ print("\n\n")
49
+ render(children)
50
+ in Node[label: { tag: :simplesect }, children:]
51
+ render(children)
52
+ in Node[label: { tag: :verbatim }, children:]
53
+ print("\n\n\\vskip.5\\baselineskip\\noindent\\fcolorbox{gray!30}{gray!10}{\\parbox{.95\\textwidth}{\\texttt{")
54
+ render_verbatim_content(children)
55
+ print("}}}\\hfill\\vskip.5\\baselineskip\n\n")
56
+ in Node[label: { tag: :emphx | :emph | :def | :rep }, children:]
57
+ print('\\emph{')
58
+ render(children)
59
+ print('}')
60
+ in Node[label: { tag: :id | :T | :idx }, children:]
61
+ print('\\texttt{')
62
+ render(children)
63
+ print('}')
64
+ in Node[label: { tag: :Q }, children:]
65
+ print('「')
66
+ render(children)
67
+ print('」')
68
+
69
+ in Node[label: { tag: :x }, children: [String => index]]
70
+ render(index)
71
+ print('\\index{')
72
+ render(index)
73
+ print('}')
74
+ in Node[label: { tag: :x }, children: [String => index, :linebreak, String => index2] => content]
75
+ render(content)
76
+ print('\\index{')
77
+ render(index)
78
+ print(' ')
79
+ render(index2)
80
+ print('}')
81
+
82
+ in Node[label: { tag: :M | :num }, children:]
83
+ print('\\(')
84
+ render(children)
85
+ print('\\)')
86
+ in Node[label: { tag: :sp }, children:]
87
+ print('^{')
88
+ render(children)
89
+ print('}')
90
+ in Node[label: { tag: :description }, children:]
91
+ print("\\begin{description}\n")
92
+ render(children)
93
+ print("\\end{description}\n")
94
+ in Node[label: { tag: :itemize }, children:]
95
+ print("\\begin{itemize}\n")
96
+ render(children)
97
+ print("\\end{itemize}\n")
98
+ in Node[label: { tag: :item, param: }, children:]
99
+ print('\\item[')
100
+ render(param)
101
+ print(']')
102
+ render(children)
103
+ in Node[label: { tag: :item }, children:]
104
+ print('\\item{}')
105
+ render(children)
106
+ in Node[label: { tag: :defid }, children: [String => id]]
107
+ print('\\texttt{')
108
+ render(id)
109
+ print("}\\label{#{id}}")
110
+ in Node[label: { tag: :see, param: { label: { id: String => id } } => param }]
111
+ id or raise
112
+ print("(「#{linktext(param)}」\\pageref{#{id}}ページ)")
113
+ in Node[label: { tag: :seeF }, children: [String => id]]
114
+ case @lang
115
+ in :ja
116
+ print('(関数\\texttt{')
117
+ render(id)
118
+ print("}は\\pageref{#{id}}ページ)")
119
+ end
120
+ in Node[label: { tag: :seeC }, children: [String => id]]
121
+ case @lang
122
+ in :ja
123
+ print('(C関数\\texttt{')
124
+ render(id)
125
+ print("}は\\pageref{#{id}}ページ)")
126
+ end
127
+ in Node[label: { tag: :refsec }, children: [String => id]]
128
+ id = "sec:#{id}"
129
+ node = @labels[id] or raise "section reference #{id} not found"
130
+ case @lang
131
+ in :ja
132
+ print("\\ref{#{id}}節「#{linktext(node)}」(\\pageref{#{id}}ページ)")
133
+ end
134
+ in Node[label: { tag: :See, param: { label: { id: } } => param }]
135
+ id or raise
136
+ case @lang
137
+ in :ja
138
+ print("#{linktext(param)}(\\pageref{#{id}}ページ)")
139
+ end
140
+ in Node[label: { tag: :link, param: [String => id] }, children:]
141
+ @labels["sec:#{id}"] and id = "sec:#{id}"
142
+ render(children)
143
+ case @lang
144
+ in :ja
145
+ print("(\\pageref{#{id}}ページ)")
146
+ end
147
+ in Node[label: { tag: :Lid }, children: [String => id]]
148
+ print('\\texttt{')
149
+ render(id)
150
+ case @lang
151
+ in :ja
152
+ print("}(\\pageref{#{id}}ページ)")
153
+ end
154
+
155
+ in Node[label: { tag: :index }, children: [String => index]]
156
+ print('\\index{')
157
+ render(index)
158
+ print('}')
159
+ in Node[label: { tag: :index }, children: [String => index, :linebreak, String => index2]]
160
+ print('\\index{')
161
+ render(index)
162
+ print(' ')
163
+ render(index2)
164
+ print('}')
165
+
166
+ in Node[label: { tag: :Char }, children:]
167
+ print('`\\texttt{')
168
+ render(children)
169
+ print("}'")
170
+ in Node[label: { tag: :St }, children:]
171
+ print('``\\texttt{')
172
+ render(children)
173
+ print("}''")
174
+ in Node[label: { tag: :rw | :Rw }, children:]
175
+ print('\\textbf{')
176
+ render(children)
177
+ print('}')
178
+
179
+ in Node[label: { tag: :apii, pop: String => pop, push: String => push, error: String => error }]
180
+ print('\\dotfill\\(\\begin{smallmatrix}\\texttt{')
181
+ render(pop)
182
+ print('}\\\\\\texttt{')
183
+ render(push)
184
+ print('}\\\\\\texttt{')
185
+ render(error)
186
+ print("}\\end{smallmatrix}\\)\n\n")
187
+
188
+ in Node[label: { tag: :APIEntry, param:, indicator: { pop:, push:, error: } }, children:]
189
+ signature = signaturetext(param)
190
+ signature.match(/ (lua L? [\w\\{}]+) [)]? [ ]+ [(] /x) or
191
+ signature.match(/ (lua L? [\w\\{}]+) /x)
192
+ name = ::Regexp.last_match(1) or raise signature.inspect[..500]
193
+ id = name.gsub('\\_{}', '_')
194
+ print("\n\n\\vskip.5\\baselineskip\\noindent\\fcolorbox{gray!30}{gray!10}{\\parbox{.9\\textwidth}{\\texttt{")
195
+ render_verbatim_content(param)
196
+ print("}}}\\label{#{id}}\\index{\\texttt{#{name}}}\\dotfill\\(\\begin{smallmatrix}\\texttt{")
197
+ render(pop)
198
+ print('}\\\\\\texttt{')
199
+ render(push)
200
+ print('}\\\\\\texttt{')
201
+ render(error)
202
+ print("}\\end{smallmatrix}\\)\\vskip.5\\baselineskip\n\n")
203
+ render(children)
204
+ in Node[label: { tag: :APIEntry, param: }, children:]
205
+ signature = signaturetext(param)
206
+ signature.match(/ (lua L? [\w\\{}]+) [)]? [ ]+ [(] /x) or
207
+ signature.match(/ (lua L? [\w\\{}]+) /x)
208
+ name = ::Regexp.last_match(1) or raise signature.inspect[..500]
209
+ id = name.gsub('\\_{}', '_')
210
+ print("\n\n\\vskip.5\\baselineskip\\noindent\\fcolorbox{gray!30}{gray!10}{\\parbox{.9\\textwidth}{\\texttt{")
211
+ render_verbatim_content(param)
212
+ print("}}}\\label{#{id}}\\index{\\texttt{#{name}}}\\hfill\\vskip.5\\baselineskip\n\n")
213
+ render(children)
214
+ in Node[label: { tag: :LibEntry, param: }, children:]
215
+ signature = signaturetext(param)
216
+ name = signature.sub(/ [ ] [(] .* /x, '')
217
+ id = name.gsub('\\_{}', '_')
218
+ print("\n\n\\vskip.5\\baselineskip\\noindent\\hfill\\fcolorbox{gray!30}{gray!10}{\\parbox{.95\\textwidth}{\\texttt{")
219
+ render_verbatim_content(param)
220
+ print("}}}\\label{#{id}}\\index{\\texttt{#{name}}}\\hfill\\vskip.5\\baselineskip\n\n")
221
+ render(children)
222
+
223
+ in Node[label: { tag: :N }, children:]
224
+ render(children)
225
+ in Node[label: { tag: :At }, children: []]
226
+ print('@')
227
+ in Node[label: { tag: :En }, children: []]
228
+ print('--')
229
+ in Node[label: { tag: :CId }, children: ]
230
+ case @lang
231
+ in :ja
232
+ print('Cの関数')
233
+ end
234
+ render(children)
235
+ in { pipe: }
236
+ render(pipe)
237
+ in { label: :leftbrace, children: }
238
+ print('{')
239
+ render(children)
240
+ print('}')
241
+ in String
242
+ print(escape(document))
243
+ in :linebreak
244
+ print("\n")
245
+ in :emptyline
246
+ print("\n\n")
247
+ in keyword: :Cdots
248
+ print('\\dots{}')
249
+ in keyword: :En
250
+ print('--')
251
+ in keyword: :VerBar
252
+ print('\\textbar{}')
253
+ in keyword: :leq
254
+ print('\\(\\leq\\)')
255
+ in keyword: :At
256
+ print('@')
257
+ in keyword: :pi
258
+ print('\\pi{}')
259
+ in keyword: :log
260
+ print('\\log{}')
261
+ in keyword: :cpp
262
+ print('\\cpluspluslogo{}')
263
+ in keyword: :nil | :false | :true | :fail => name
264
+ print('\\textbf{')
265
+ render(name)
266
+ print('}')
267
+ in Symbol => name
268
+ print(name)
269
+ else
270
+ raise document.inspect[..500]
271
+ end
272
+ end
273
+
274
+ def render_ebnf(target)
275
+ case target
276
+ in Array
277
+ target.each do |child|
278
+ render_ebnf(child)
279
+ end
280
+ in Node[label: { tag: :producname }, children:]
281
+ render_ebnf(children)
282
+ in keyword: :Or
283
+ print(' \\quad\\text{\\textbar{}} ')
284
+ in keyword: :OrNL
285
+ print("\\\\\n\\text{\\textbar{}} &")
286
+ in Node[label: { tag: :producbody }, children:]
287
+ print(' \\rightarrow{} &')
288
+ render_ebnf(children)
289
+ print("\\\\\n")
290
+
291
+ in keyword: :Open
292
+ print('\\text{\\{}')
293
+ in keyword: :Close
294
+ print('\\text{\\}}')
295
+ in :emptyline
296
+ # nop
297
+ in { pipe: }
298
+ print(pipe.gsub('|', '\\text{\\textbar{}}'))
299
+ in String
300
+ print("\\text{#{escape(target)}}")
301
+ in Node[label: { tag: :bnfNter }, children:]
302
+ render_ebnf(children)
303
+ in Node[label: { tag: :bnfter }, children:]
304
+ print('\\text{\\textquoteleft{}\\texttt{')
305
+ render_ebnf(children)
306
+ print('}\\textquoteright{}}')
307
+ in Node[label: { tag: :bnfrep }, children:]
308
+ print('\\left\\{')
309
+ render_ebnf(children)
310
+ print('\\right\\}')
311
+ in Node[label: { tag: :bnfopt }, children:]
312
+ print('\\left[')
313
+ render_ebnf(children)
314
+ print('\\right]')
315
+
316
+ in Node[label: { tag: :rep }, children:]
317
+ print(' \\text{\\emph{')
318
+ render_ebnf(children)
319
+ print('}} ')
320
+ in Node[label: { tag: :rw | :Rw }, children:]
321
+ print(' \\text{\\textbf{')
322
+ render_ebnf(children)
323
+ print('}} ')
324
+
325
+ in Node[label: :leftbrace, children:]
326
+ print('\\text{\\{}')
327
+ render_ebnf(children)
328
+ print('\\text{\\}}')
329
+ in :linebreak
330
+ # nop
331
+ else
332
+ raise target.inspect[..500]
333
+ end
334
+ end
335
+
336
+ def escape(target)
337
+ escaped = +''
338
+ target.each_char do |char|
339
+ escaped << case char
340
+ when '_'
341
+ '\\_{}'
342
+ when '^'
343
+ '\\textasciicircum{}'
344
+ when '&'
345
+ '\\&'
346
+ when '#'
347
+ '\\#'
348
+ when '%'
349
+ '\\%'
350
+ when '$'
351
+ '\\${}'
352
+ when '\\'
353
+ '\\textbackslash{}'
354
+ when '~'
355
+ '\\textasciitilde{}'
356
+ else
357
+ char
358
+ end
359
+ end
360
+ escaped
361
+ end
362
+
363
+ def render_verbatim_content(target)
364
+ case target
365
+ in Array
366
+ target.each { |child| render_verbatim_content(child) }
367
+ in String
368
+ escaped = +''
369
+ target.each_char { |char| escaped << (char == ' ' ? '\\ ' : escape(char)) }
370
+ print(escaped)
371
+ in keyword: :Cdots
372
+ print('\\dots{}')
373
+ in Node[label: :leftbrace, children:]
374
+ print('\\{')
375
+ render_verbatim_content(children)
376
+ print('\\}')
377
+ in Node[label: { tag: :rep }, children:]
378
+ print('\\emph{')
379
+ render_verbatim_content(children)
380
+ print('}')
381
+ in :linebreak
382
+ print("\\\\\n")
383
+ in :emptyline
384
+ print("\\\\\n\\\\\n")
385
+ in keyword: :ldots
386
+ print('\\dots{}')
387
+ else
388
+ raise target.inspect[..500]
389
+ end
390
+ end
391
+
392
+ def signaturetext(node)
393
+ case node
394
+ in Array
395
+ node.map { |n| signaturetext(n) }.join
396
+ in String
397
+ escape(node)
398
+ in :linebreak
399
+ "\n"
400
+ in { keyword: :ldots }
401
+ '\\ldots{}'
402
+ in { keyword: :Cdots }
403
+ '\\dots{}'
404
+ in Node[label: :leftbrace, children:]
405
+ content = children.map { |c| signaturetext(c) }.join
406
+ "{#{content}}"
407
+ in Node[label: { tag: :rep }, children:]
408
+ "\\emph{#{signaturetext(children)}}"
409
+ else
410
+ raise node.inspect[..500]
411
+ end
412
+ end
413
+
414
+ def linktext(node)
415
+ case node
416
+ in label: { tag: :section, title: [String => title] }
417
+ title
418
+ in label: { tag: :APIEntry, param: }
419
+ name = signaturetext(param)
420
+ name.match(/ (lua L? [\w\\{}]+) [)]? [ ]+ [(] /x) or
421
+ name.match(/ (lua L? [\w\\{}]+) /x)
422
+ name = ::Regexp.last_match(1) or raise [name, node].inspect[..500]
423
+ "\\texttt{#{name}}"
424
+ else
425
+ raise node.inspect[..500]
426
+ end
427
+ end
428
+
429
+ def print(target)
430
+ target.is_a?(Symbol) and target = target.to_s
431
+ case target
432
+ in String
433
+ target.scrub!
434
+ @output.print(target)
435
+ end
436
+ end
437
+ end
438
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+ require "forwardable"
5
+
6
+ module Luaof
7
+ class Lexer
8
+ def initialize
9
+ @scanner = StringScanner.new(+'')
10
+ end
11
+
12
+ def push(line)
13
+ if line.empty?
14
+ yield :emptyline
15
+ return
16
+ end
17
+ concat(line)
18
+ until eos?
19
+ if skip(/ @ (\w+) ({)? /x)
20
+ if @scanner[2]
21
+ yield({ tag: @scanner[1].intern })
22
+ else
23
+ yield({ keyword: @scanner[1].intern })
24
+ end
25
+ elsif skip('{')
26
+ yield :leftbrace
27
+ elsif skip('}')
28
+ yield :closer
29
+ elsif (text = scan(/ [|] \s* /x))
30
+ yield({ pipe: text })
31
+ elsif (text = scan(/ [^@{}|]+ /x))
32
+ yield text
33
+ else
34
+ raise ">>>#{rest}"
35
+ end
36
+ end
37
+ yield :linebreak
38
+ end
39
+
40
+ extend Forwardable
41
+ def_delegators :@scanner, :eos?, :concat, :string, :skip, :rest, :scan, :pos
42
+ end
43
+ end
data/lib/luaof/node.rb ADDED
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luaof
4
+ class Node
5
+ attr :label
6
+ attr_accessor :children
7
+
8
+ def initialize(label, mapping)
9
+ @label = label
10
+ @children = []
11
+ @mapping = mapping
12
+ end
13
+
14
+ def parse_api_indicator!
15
+ case self
16
+ in label: { tag: :APIEntry }, children: [{ label: { tag: :apii }, children: indicator }, *_]
17
+ pop, push, error, nope = expand_indicator(indicator).split(',')
18
+ nope and raise indicator
19
+ @label.update(indicator: { pop:, push:, error: })
20
+ @children.shift
21
+ in label: { tag: :apii }, children: indicator
22
+ pop, push, error, nope = expand_indicator(indicator).split(',')
23
+ nope and raise indicator
24
+ @label.update(pop:, push:, error:)
25
+ @children = []
26
+ else
27
+ @children.select { _1.is_a?(Node) }.each(&:parse_api_indicator!)
28
+ end
29
+ end
30
+
31
+ def expand_indicator(directives)
32
+ result = +''
33
+ directives.each do |directive|
34
+ directive => String
35
+ result.concat(directive)
36
+ end
37
+ result
38
+ end
39
+
40
+ def parse_section!
41
+ case self
42
+ in label: { tag: :section } => label,
43
+ children: [Node[label: { tag: :title }, children: title], *_] => children
44
+ children.shift
45
+ @label = { **label, title: }
46
+ else
47
+ # nop
48
+ end
49
+ @children.select { _1.is_a?(Node) }.each(&:parse_section!)
50
+ end
51
+
52
+ def register_mapping!
53
+ case self
54
+ in label: { tag: :section, param: [String => id] }
55
+ id = "sec:#{id}"
56
+ @mapping[id] and raise "already registered: #{id}"
57
+ @label[:id] = id
58
+ @mapping[id] = self
59
+ in label: { tag: :APIEntry, param: }
60
+ text = signaturetext(param)
61
+ text.match(/ (lua L? _ \w+) [)]? [ ]+ [(] /x) or
62
+ text.match(/ (lua L? _ \w+) /x)
63
+ id = ::Regexp.last_match(1) or raise "empty: #{text}"
64
+ id = id.to_s
65
+ @mapping[id] and raise "already registered: #{id}"
66
+ @label[:id] = id
67
+ @mapping[id] = self
68
+ in label: { tag: :defid }, children: String => id
69
+ @mapping[id] and raise "already registered: #{id}"
70
+ @label[:id] = id
71
+ @mapping[id] = self
72
+ else
73
+ # nop
74
+ end
75
+ @children.select { _1.is_a?(Node) }.each(&:register_mapping!)
76
+ end
77
+
78
+ def register_refs!
79
+ case self
80
+ in label: { tag: :see }, children: [String => id]
81
+ label = @mapping[id] || @mapping["sec:#{id}"] or
82
+ raise "not found: #{id}"
83
+ @label[:param] = label
84
+ in label: { tag: :See }, children: [String => id]
85
+ label = @mapping[id] || @mapping["sec:#{id}"] or
86
+ raise "not found: #{id}"
87
+ @label[:param] = label
88
+ else
89
+ # nop
90
+ end
91
+ @children.select { _1.is_a?(Node) }.each(&:register_refs!)
92
+ end
93
+
94
+ def signaturetext(target)
95
+ case target
96
+ in Array
97
+ target.map { |t| signaturetext(t) }.join
98
+ in String
99
+ target
100
+ in :linebreak
101
+ "\n"
102
+ in keyword: :ldots
103
+ '\\ldots{}'
104
+ in Node[label: { tag: :rep }, children:]
105
+ content = children.map { |c| signaturetext(c) }.join
106
+ "\\emph{#{content}}"
107
+ in Node[label: :leftbrace, children:]
108
+ content = children.map { |c| signaturetext(c) }.join
109
+ "{#{content}}"
110
+ else
111
+ raise target.inspect[..500]
112
+ end
113
+ end
114
+
115
+ def deconstruct_keys(_keys)
116
+ { label: @label, children: @children }
117
+ end
118
+
119
+ def pretty_print(q)
120
+ q.object_group(self) do
121
+ q.breakable
122
+ q.text 'label='
123
+ q.pp(@label)
124
+ q.text ','
125
+ q.breakable
126
+ q.text 'children='
127
+ q.pp(@children)
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "node"
4
+
5
+ module Luaof
6
+ class Parser
7
+ attr :document, :labels
8
+
9
+ def initialize
10
+ @document = []
11
+ @stack = []
12
+ @labels = {}
13
+ end
14
+
15
+ def push(token)
16
+ case token
17
+ in { tag: :sect1 | :sect2 | :sect3 | :sect4 => tag }
18
+ @stack.unshift(Node.new({ **token, tag: :section,
19
+ level: Integer(tag.to_s.match(/ sect (\d) /x)[1]) }, @labels))
20
+ in { tag: _ } | :leftbrace
21
+ @stack.unshift(Node.new(token, @labels))
22
+ in { pipe: }
23
+ case @stack
24
+ in { label: { tag: (:item | :link | :APIEntry | :LibEntry | :section) } => label,
25
+ children: } => function, *_
26
+ label[:param] and raise
27
+ label[:param] = children
28
+ function.children = []
29
+ else
30
+ @stack.first.children << pipe
31
+ end
32
+ in String | { keyword: _ }
33
+ case @stack
34
+ in Node => node, *_
35
+ node.children << token
36
+ end
37
+ in :closer
38
+ case @stack
39
+ in Node[label: { tag: :C | :Ci }], *_
40
+ @stack.shift
41
+ in Node[label: :leftbrace] => child, Node => parent, *_
42
+ parent.children << @stack.shift
43
+ in [Node => child, Node => parent, *_]
44
+ child.children.pop while child.children.last in :emptyline | :linebreak
45
+ parent.children << @stack.shift
46
+ in [Node[label: { tag: :manual }], *_]
47
+ @document << @stack.shift
48
+ end
49
+ in :emptyline
50
+ case @stack
51
+ in Node[children: [*_, :linebreak | :emptyline]] => node, *_
52
+ node.children[-1] = token
53
+ in Node[children: [] | [*_, Node[label: { tag: :title }]]], *_
54
+ # nop
55
+ in Node => node, *_
56
+ node.children << token
57
+ end
58
+ in :linebreak
59
+ case @stack
60
+ in [Node[label: :leftbrace] => node, *_]
61
+ node.children << token
62
+ in [] | [Node[children: [] | [*_, Node[label: { tag: :title }] | :emptyline]], *_]
63
+ # nop
64
+ in Node => node, *_
65
+ node.children << token
66
+ end
67
+ end
68
+ end
69
+
70
+ def parse_section!
71
+ @document.each(&:parse_section!)
72
+ end
73
+
74
+ def register_mapping!
75
+ @document.each(&:register_mapping!)
76
+ end
77
+
78
+ def register_refs!
79
+ @document.each(&:register_refs!)
80
+ end
81
+
82
+ def parse_api_indicator!
83
+ @document.each(&:parse_api_indicator!)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luaof
4
+ VERSION = "0.1.0"
5
+ end
data/lib/luaof.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "luaof/version"
4
+ require_relative "luaof/lexer"
5
+ require_relative "luaof/parser"
6
+ require_relative "luaof/latex_renderer"
7
+
8
+ module Luaof
9
+ Error = Class.new(StandardError)
10
+ end
data/manifest.scm ADDED
@@ -0,0 +1 @@
1
+ (specifications->manifest (list "ruby@3.1" "ruby-rubocop"))
data/sig/luaof.gen.rbs ADDED
@@ -0,0 +1,64 @@
1
+ # TypeProf 0.21.3
2
+
3
+ # Classes
4
+ module Luaof
5
+ VERSION: String
6
+ Error: Class
7
+
8
+ class Lexer
9
+ extend Forwardable
10
+ @scanner: StringScanner
11
+
12
+ def initialize: -> void
13
+ def push: (untyped line) -> nil
14
+ end
15
+
16
+ class Node
17
+ @label: untyped
18
+ @labels: Hash[untyped, untyped]
19
+
20
+ attr_accessor children: Array[untyped]
21
+ def initialize: (untyped label, Hash[untyped, untyped] labels) -> void
22
+ def parse_param!: -> Array[untyped]
23
+ def parse_api_indicator!: -> Array[untyped]
24
+ def expand_indicator: (Array[untyped] directives) -> String
25
+ def parse_section!: -> Array[untyped]
26
+ def register_labels!: -> Array[untyped]
27
+ def register_refs!: -> Array[untyped]
28
+ def signaturetext: (untyped target) -> String
29
+ def expand_pipe!: -> Array[untyped]
30
+ def surely_expand_pipe!: (Array[untyped] target, ?parent: ^(untyped) -> untyped?) -> Array[untyped]?
31
+ def deconstruct_keys: (Array[:children | :label] _keys) -> {label: untyped, children: Array[untyped]}
32
+ def pretty_print: (untyped q) -> untyped
33
+ end
34
+
35
+ class Parser
36
+ @document: Array[Node?]
37
+ @stack: Array[Node]
38
+ @labels: Hash[untyped, untyped]
39
+
40
+ def initialize: -> void
41
+ def push: (untyped token) -> ((Array[Node?] | Node)?)
42
+ def parse_param!: -> Array[Node?]
43
+ def parse_section!: -> Array[Node?]
44
+ def register_labels!: -> Array[Node?]
45
+ def register_refs!: -> Array[Node?]
46
+ def parse_api_indicator!: -> Array[Node?]
47
+ def expand_pipe!: -> Array[Node?]
48
+ end
49
+
50
+ class Renderer
51
+ SECTION_LEVEL_TO_CONTROL_SEQUENCE: [nil, String, String, String, String]
52
+ @output: untyped
53
+ @labels: untyped
54
+
55
+ def initialize: (untyped output, untyped labels) -> void
56
+ def render: (untyped document) -> untyped
57
+ def render_ebnf: (untyped target) -> nil
58
+ def escape: (untyped target) -> String
59
+ def render_verbatim_content: (untyped target) -> untyped
60
+ def signaturetext: (untyped node) -> String
61
+ def linktext: (untyped node) -> String
62
+ def print: (String target) -> untyped
63
+ end
64
+ end
data/sig/luaof.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Luaof
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: luaof
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - gemmaro
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-06-08 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: luaof is a Lua "our format" parser library. The "our format" is used
14
+ for the Lua reference manual. This gem has highly experimental LaTeX renderer for
15
+ Japanese, which can be referred as an example renderer.
16
+ email:
17
+ - gemmaro.dev@gmail.com
18
+ executables:
19
+ - 2latex
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - ".rubocop.yml"
24
+ - ".rubocop_todo.yml"
25
+ - CHANGELOG.md
26
+ - README.md
27
+ - Rakefile
28
+ - exe/2latex
29
+ - lib/luaof.rb
30
+ - lib/luaof/latex_renderer.rb
31
+ - lib/luaof/lexer.rb
32
+ - lib/luaof/node.rb
33
+ - lib/luaof/parser.rb
34
+ - lib/luaof/version.rb
35
+ - manifest.scm
36
+ - sig/luaof.gen.rbs
37
+ - sig/luaof.rbs
38
+ homepage:
39
+ licenses: []
40
+ metadata: {}
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 3.1.0
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubygems_version: 3.3.26
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: Lua our format parser
60
+ test_files: []