luaof 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|