parsanol 1.3.56-arm-linux → 1.3.57-arm-linux

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/exe/parsanol +7 -0
  3. data/lib/parsanol/3.2/parsanol_native.so +0 -0
  4. data/lib/parsanol/3.3/parsanol_native.so +0 -0
  5. data/lib/parsanol/3.4/parsanol_native.so +0 -0
  6. data/lib/parsanol/4.0/parsanol_native.so +0 -0
  7. data/lib/parsanol/native/libparsanol.so +0 -0
  8. data/lib/parsanol/native/serializer.rb +5 -2
  9. data/lib/parsanol/parg/artifact.rb +213 -0
  10. data/lib/parsanol/parg/authoring.rb +110 -0
  11. data/lib/parsanol/parg/bindings.rb +129 -0
  12. data/lib/parsanol/parg/cli.rb +251 -0
  13. data/lib/parsanol/parg/compiler.rb +465 -0
  14. data/lib/parsanol/parg/derive.rb +28 -0
  15. data/lib/parsanol/parg/document.rb +69 -0
  16. data/lib/parsanol/parg/error.rb +18 -0
  17. data/lib/parsanol/parg/frontend.rb +173 -0
  18. data/lib/parsanol/parg/import.rb +34 -0
  19. data/lib/parsanol/parg/importers/abnf.rb +299 -0
  20. data/lib/parsanol/parg/importers/ebnf.rb +201 -0
  21. data/lib/parsanol/parg/importers/pest.rb +314 -0
  22. data/lib/parsanol/parg/imports.rb +87 -0
  23. data/lib/parsanol/parg/lexer.rb +72 -0
  24. data/lib/parsanol/parg/lints.rb +171 -0
  25. data/lib/parsanol/parg/lsp.rb +208 -0
  26. data/lib/parsanol/parg/lutaml.rb +67 -0
  27. data/lib/parsanol/parg/node.rb +20 -0
  28. data/lib/parsanol/parg/parser.rb +458 -0
  29. data/lib/parsanol/parg/preprocess.rb +36 -0
  30. data/lib/parsanol/parg/render.rb +44 -0
  31. data/lib/parsanol/parg/selfhost.rb +49 -0
  32. data/lib/parsanol/parg/visitor.rb +64 -0
  33. data/lib/parsanol/parg.rb +38 -0
  34. data/lib/parsanol/version.rb +1 -1
  35. data/lib/parsanol/vm.rb +7 -5
  36. data/lib/parsanol.rb +3 -0
  37. data/parsanol.gemspec +3 -1
  38. metadata +31 -4
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "rbconfig"
5
+
6
+ module Parsanol
7
+ module PARG
8
+ # F11: minimal PARG LSP over stdio (JSON-RPC 2.0, no framework).
9
+ #
10
+ # initialize / shutdown / exit
11
+ # textDocument/didOpen|didChange -> publishDiagnostics
12
+ # (parse errors, lint errors, failing inline tests)
13
+ # textDocument/hover -> the rule's ## doc comment
14
+ #
15
+ # Diagnostics and hover run entirely on the compiler API — one
16
+ # language implementation, every editor.
17
+ class Lsp
18
+ CONTENT_LENGTH = "Content-Length: "
19
+
20
+ def initialize(input = $stdin, output = $stdout)
21
+ @input = input
22
+ @output = output
23
+ @documents = {}
24
+ end
25
+
26
+ def run
27
+ until @input.eof?
28
+ header = @input.gets("\r\n\r\n")
29
+ break if header.nil?
30
+
31
+ length = header[/Content-Length: (\d+)/i, 1].to_i
32
+ break if length.zero?
33
+
34
+ message = JSON.parse(@input.read(length))
35
+ handle(message)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def handle(message)
42
+ case message["method"]
43
+ when "initialize"
44
+ respond(message, {
45
+ "capabilities" => {
46
+ "textDocumentSync" => 1,
47
+ "hoverProvider" => true,
48
+ },
49
+ "serverInfo" => { "name" => "parsanol-parg-lsp" },
50
+ })
51
+ when "shutdown"
52
+ respond(message, nil)
53
+ when "exit"
54
+ exit 0
55
+ when "textDocument/didOpen", "textDocument/didChange"
56
+ doc = message["params"]["textDocument"]
57
+ @documents[doc["uri"]] =
58
+ doc["text"] || message.dig("params", "contentChanges", -1, "text")
59
+ publish_diagnostics(doc["uri"])
60
+ when "textDocument/codeAction"
61
+ respond(message, code_action(message["params"]))
62
+ when "textDocument/hover"
63
+ respond(message, hover(message.dig("params", "textDocument", "uri"),
64
+ message.dig("params", "position")))
65
+ end
66
+ end
67
+
68
+ def respond(message, result)
69
+ send_message("id" => message["id"], "result" => result)
70
+ end
71
+
72
+ def notify(method, params)
73
+ send_message("jsonrpc" => "2.0", "method" => method, "params" => params)
74
+ end
75
+
76
+ def send_message(payload)
77
+ body = JSON.generate(payload)
78
+ @output.write("Content-Length: #{body.bytesize}\r\n\r\n#{body}")
79
+ @output.flush
80
+ end
81
+
82
+ def publish_diagnostics(uri)
83
+ source = @documents[uri]
84
+ diagnostics = []
85
+ document = nil
86
+ begin
87
+ document = Parser.new(source).parse
88
+ rescue ParseError => e
89
+ line = 0
90
+ # The self-hosting artifact yields a precise offset (F7 wire);
91
+ # prefer it when the artifact is available.
92
+ if SelfHost.available?
93
+ begin
94
+ SelfHost.validate(source)
95
+ rescue Parsanol::ParseFailed
96
+ # fall through to line 0
97
+ end
98
+ end
99
+ diagnostics << diagnostic(line, e.message)
100
+ end
101
+ if document
102
+ begin
103
+ tables_dir = ENV.fetch("PG_TABLES_DIR", nil)
104
+ compiler = Compiler.new(document, tables_dir)
105
+ document.rules.each_key { |rule| compiler.atom_for(rule) }
106
+ Lints.errors(document, compiler).each do |error|
107
+ diagnostics << diagnostic(rule_line(source, error[/\Arule (\w+):/, 1]), error)
108
+ end
109
+ compiler.run_tests.each do |failure|
110
+ diagnostics << diagnostic(0, failure)
111
+ end
112
+ rescue ParseError, PARG::Error => e
113
+ diagnostics << diagnostic(0, e.message)
114
+ end
115
+ end
116
+ notify("textDocument/publishDiagnostics",
117
+ "uri" => uri, "diagnostics" => diagnostics)
118
+ end
119
+
120
+ # Rule-granular position: the line the named rule is defined on.
121
+ def rule_line(source, rule_name)
122
+ return 0 unless rule_name
123
+
124
+ source.lines.index { |l| l.match?(/^\s*#{rule_name}\s*=/) } || 0
125
+ end
126
+
127
+ def diagnostic(line, message)
128
+ {
129
+ "range" => { "start" => { "line" => line, "character" => 0 },
130
+ "end" => { "line" => line, "character" => 0 } },
131
+ "severity" => 1,
132
+ "source" => "parsanol-parg",
133
+ "message" => message,
134
+ }
135
+ end
136
+
137
+ # Reorder code action: only when EVERY top-level alternative of the
138
+ # rule is a quoted literal is the reorder mechanically safe.
139
+ def code_action(params)
140
+ uri = params.dig("textDocument", "uri")
141
+ source = @documents[uri]
142
+ return [] unless source
143
+
144
+ line_no = params.dig("range", "start", "line").to_i
145
+ text = source.lines[line_no].to_s
146
+ return [] unless text.match?(/^\s*[a-z_]+\s*=\s*"/)
147
+
148
+ alternatives = top_level_literals(text)
149
+ return [] if alternatives.nil? || alternatives.size < 2
150
+
151
+ sorted = alternatives.sort_by { |a| -a.length }
152
+ return [] if sorted == alternatives
153
+
154
+ [{
155
+ "title" => "Reorder alternatives longest-first",
156
+ "kind" => "refactor",
157
+ "edit" => {
158
+ "changes" => {
159
+ uri => [{ "range" => whole_line_range(line_no),
160
+ "newText" => "#{text[/^\s*[a-z_]+\s*=\s*/]}#{sorted.join(' / ').inspect}" }],
161
+ },
162
+ },
163
+ }]
164
+ end
165
+
166
+ def top_level_literals(line)
167
+ rhs = line.sub(/^\s*[a-z_]+\s*=\s*/, "")
168
+ parts = rhs.split(/ (?=")/).flat_map { |p| p.split(%r{ / (?=")}) }
169
+ literals = parts.map { |p| p.match(/^"((?:[^"\\]|\\.)*)"$/) }
170
+ return nil if literals.any?(&:nil?)
171
+
172
+ literals.map { |m| m[1] }
173
+ end
174
+
175
+ def whole_line_range(line_no)
176
+ { "start" => { "line" => line_no, "character" => 0 },
177
+ "end" => { "line" => line_no, "character" => 1000 } }
178
+ end
179
+
180
+ def hover(uri, position)
181
+ source = @documents[uri]
182
+ return nil unless source && position
183
+
184
+ line_no = position.dig("position", "line") || position["line"]
185
+ text = source.lines[line_no.to_i].to_s
186
+ rule = text[/^\s*([a-z_]+)\s*=/, 1]
187
+ return nil unless rule
188
+
189
+ lines = source.lines
190
+ rule_line = lines.index { |l| l.match?(/^\s*#{rule}\s*=/) }
191
+ return nil unless rule_line
192
+
193
+ doc_lines = []
194
+ cursor = rule_line - 1
195
+ while cursor >= 0 && lines[cursor].strip.start_with?("##")
196
+ doc_lines.unshift(lines[cursor])
197
+ cursor -= 1
198
+ end
199
+ return nil if doc_lines.empty?
200
+
201
+ doc = doc_lines.map(&:strip).join("\n")
202
+
203
+ { "contents" => { "kind" => "markdown",
204
+ "value" => doc.gsub(/^##\s?/, "").strip } }
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Parsanol
4
+ module PARG
5
+ # lutaml-model integration: register a PARG artifact as a lutaml-model
6
+ # string format, giving every Serializable class from_<format> —
7
+ # artifact parse -> bindings -> model instance.
8
+ #
9
+ # Parsanol::PARG::Lutaml.register(
10
+ # IsoIdentifier,
11
+ # format_name: :pubid_iso,
12
+ # artifact: "artifacts/iso.json",
13
+ # entry: "identifier",
14
+ # )
15
+ # IsoIdentifier.from_pubid_iso("ISO/IEC 12345-1:2020")
16
+ #
17
+ # Registration goes through ::Lutaml::Model::FormatRegistry (the same
18
+ # extension point as xml/json/yaml), so the format participates in the
19
+ # framework's format metadata and error typing. to_<format> (render)
20
+ # arrives with artifact render specs.
21
+ module Lutaml
22
+ module_function
23
+
24
+ def register(model_class, format_name:, artifact:, entry:, tables_dir: nil)
25
+ unless defined?(::Lutaml::Model)
26
+ raise Error, "lutaml-model is not available; add it to your bundle"
27
+ end
28
+
29
+ art = Artifact.load(artifact, tables_dir: tables_dir)
30
+ entry_name = entry
31
+
32
+ adapter = Class.new do
33
+ define_singleton_method(:parse) do |data, _options = {}|
34
+ art.parse_and_bind(entry_name, data)
35
+ end
36
+ end
37
+
38
+ ::Lutaml::Model::FormatRegistry.register(
39
+ format_name,
40
+ mapping_class: ::Lutaml::Model::Mapping,
41
+ adapter_class: adapter,
42
+ transformer: artifact_transformer(model_class, art, entry_name),
43
+ error_types: [Parsanol::ParseFailed],
44
+ )
45
+
46
+ model_class.define_singleton_method(:"from_#{format_name}") do |input, _options = {}|
47
+ new(art.parse_and_bind(entry_name, input))
48
+ end
49
+ format_name
50
+ end
51
+
52
+ def artifact_transformer(model_class, artifact, entry_name)
53
+ Class.new do
54
+ define_singleton_method(:name) { "#{model_class.name}PGTransform" }
55
+
56
+ define_method(:data_to_model) do |data, _format, _options = {}|
57
+ model_class.new(data)
58
+ end
59
+
60
+ define_singleton_method(:from_pg) do |input|
61
+ model_class.new(artifact.parse_and_bind(entry_name, input))
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Parsanol
4
+ module PARG
5
+ # PARG intermediate representation node.
6
+ #
7
+ # kind + payload slots:
8
+ # [:lit, string, fold] literal; fold = case-insensitive
9
+ # [:class, ranges] byte ranges [[lo, hi], ...]
10
+ # [:seq, items]
11
+ # [:alt, branches] ordered choice
12
+ # [:rep, child, min, max] max nil = unbounded
13
+ # [:opt, child] maybe (0..1)
14
+ # [:pred, positive, child] &x / !x
15
+ # [:cap, name, child] x as name
16
+ # [:ref, name] rule reference
17
+ # [:table, table, column] alternatives expanded from data
18
+ Node = Struct.new(:kind, :a, :b, :c)
19
+ end
20
+ end