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 +7 -0
- data/.rubocop.yml +9 -0
- data/.rubocop_todo.yml +7 -0
- data/CHANGELOG.md +7 -0
- data/README.md +30 -0
- data/Rakefile +16 -0
- data/exe/2latex +21 -0
- data/lib/luaof/latex_renderer.rb +438 -0
- data/lib/luaof/lexer.rb +43 -0
- data/lib/luaof/node.rb +131 -0
- data/lib/luaof/parser.rb +86 -0
- data/lib/luaof/version.rb +5 -0
- data/lib/luaof.rb +10 -0
- data/manifest.scm +1 -0
- data/sig/luaof.gen.rbs +64 -0
- data/sig/luaof.rbs +4 -0
- metadata +60 -0
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
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
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
|
data/lib/luaof/lexer.rb
ADDED
@@ -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
|
data/lib/luaof/parser.rb
ADDED
@@ -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
|
data/lib/luaof.rb
ADDED
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
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: []
|