luaof 0.1.0

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.
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: []