expressir 2.4.35-arm-linux → 2.4.37-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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/conformance.yml +34 -0
  3. data/.gitignore +3 -0
  4. data/Gemfile +1 -1
  5. data/TODO.sota/00-overview.md +57 -0
  6. data/TODO.sota/01-type-checking.md +115 -0
  7. data/TODO.sota/02-evaluator.md +86 -0
  8. data/TODO.sota/03-conformance.md +53 -0
  9. data/TODO.sota/04-lsp.md +70 -0
  10. data/TODO.sota/05-interop.md +55 -0
  11. data/TODO.sota/06-dialects.md +57 -0
  12. data/TODO.sota/07-reference-artifacts.md +55 -0
  13. data/docs/_guides/interop/index.adoc +25 -0
  14. data/docs/_guides/interop/json-schema.adoc +88 -0
  15. data/docs/_guides/interop/owl-rdf.adoc +87 -0
  16. data/docs/_guides/reference/checker-notes.adoc +55 -0
  17. data/docs/_guides/reference/checker.adoc +36 -0
  18. data/docs/_guides/reference/evaluator.adoc +40 -0
  19. data/docs/_guides/reference/grammar-crosswalk.adoc +299 -0
  20. data/docs/_guides/reference/index.adoc +22 -0
  21. data/docs/_guides/reference/lsp.adoc +41 -0
  22. data/docs/_guides/reference/p21.adoc +30 -0
  23. data/docs/_guides/reference/shtolo.adoc +36 -0
  24. data/exe/expressir-lsp +6 -0
  25. data/expressir.gemspec +2 -2
  26. data/lib/expressir/3.2/expressir_core.so +0 -0
  27. data/lib/expressir/3.3/expressir_core.so +0 -0
  28. data/lib/expressir/3.4/expressir_core.so +0 -0
  29. data/lib/expressir/4.0/expressir_core.so +0 -0
  30. data/lib/expressir/cli.rb +18 -0
  31. data/lib/expressir/commands/check.rb +3 -1
  32. data/lib/expressir/commands/export_json_schema.rb +29 -0
  33. data/lib/expressir/commands/export_owl.rb +26 -0
  34. data/lib/expressir/commands/validate.rb +3 -0
  35. data/lib/expressir/commands.rb +2 -0
  36. data/lib/expressir/conformance/trend.rb +100 -0
  37. data/lib/expressir/conformance.rb +8 -0
  38. data/lib/expressir/eval/environment.rb +83 -0
  39. data/lib/expressir/eval/expression.rb +610 -0
  40. data/lib/expressir/eval/interpreter.rb +316 -0
  41. data/lib/expressir/eval/rules.rb +88 -0
  42. data/lib/expressir/eval/values.rb +74 -0
  43. data/lib/expressir/eval.rb +17 -0
  44. data/lib/expressir/express/builders/expression_builder.rb +9 -1
  45. data/lib/expressir/express/checker.rb +779 -2
  46. data/lib/expressir/express/dialect.rb +44 -0
  47. data/lib/expressir/express/formatters/expressions_formatter.rb +4 -1
  48. data/lib/expressir/express/parser.rb +9 -4
  49. data/lib/expressir/express.rb +1 -0
  50. data/lib/expressir/interop/json_schema.rb +195 -0
  51. data/lib/expressir/interop/owl.rb +216 -0
  52. data/lib/expressir/interop.rb +13 -0
  53. data/lib/expressir/lsp/document.rb +226 -0
  54. data/lib/expressir/lsp/server.rb +184 -0
  55. data/lib/expressir/lsp/workspace.rb +402 -0
  56. data/lib/expressir/lsp.rb +11 -0
  57. data/lib/expressir/mapping/refpath.rb +65 -4
  58. data/lib/expressir/reference/grammar_crosswalk.rb +122 -0
  59. data/lib/expressir/reference/grammar_crosswalk.yml +800 -0
  60. data/lib/expressir/reference/note_catalog.rb +54 -0
  61. data/lib/expressir/reference.rb +10 -0
  62. data/lib/expressir/version.rb +1 -1
  63. data/lib/expressir.rb +4 -0
  64. data/lib/tasks/conformance.rake +27 -0
  65. data/lib/tasks/reference.rake +25 -0
  66. metadata +50 -4
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module Lsp
5
+ # An open document: source text, last good AST, and the LSP position
6
+ # translations that the protocol needs. Offsets crossing the boundary
7
+ # are byte offsets into the UTF-8 text; LSP positions are 0-based
8
+ # UTF-16 line/character pairs.
9
+ class Document
10
+ IDENTIFIER = /[A-Za-z][A-Za-z0-9_]*/
11
+ NAME_WINDOW = 1024
12
+
13
+ Token = Struct.new(:text, :start_byte, :byte_length, keyword_init: true) do
14
+ def end_byte
15
+ start_byte + byte_length
16
+ end
17
+ end
18
+
19
+ SYMBOL_KINDS = {
20
+ Model::Declarations::Schema => 3, # Namespace
21
+ Model::Declarations::Entity => 5, # Class
22
+ Model::Declarations::Type => 26, # TypeParameter
23
+ Model::Declarations::Function => 12, # Function
24
+ Model::Declarations::Procedure => 6, # Method
25
+ Model::Declarations::Rule => 11, # Interface
26
+ Model::Declarations::Constant => 14, # Constant
27
+ Model::Declarations::Attribute => 8, # Field
28
+ Model::Declarations::Variable => 13, # Variable
29
+ }.freeze
30
+
31
+ attr_reader :uri, :path, :text, :file, :parse_error
32
+
33
+ def initialize(uri:, text:, path: nil)
34
+ @uri = uri
35
+ @path = path || uri_path(uri)
36
+ @text = text.to_s
37
+ @parse_error = nil
38
+ parse!
39
+ end
40
+
41
+ def update(text)
42
+ text = text.to_s
43
+ return if text == @text
44
+
45
+ @text = text
46
+ parse!
47
+ end
48
+
49
+ def schemas
50
+ Array(@file&.schemas)
51
+ end
52
+
53
+ def schema_containing(byte)
54
+ schemas.select { |schema| schema.source_offset.to_i <= byte }
55
+ .max_by(&:source_offset)
56
+ end
57
+
58
+ def symbols
59
+ return [] unless @file
60
+
61
+ position_index.nodes.filter_map do |entry|
62
+ node = entry[:node]
63
+ kind = SYMBOL_KINDS[node.class]
64
+ next unless kind && node.respond_to?(:id) && node.id && entry[:line]
65
+
66
+ {
67
+ "name" => node.id.to_s,
68
+ "kind" => kind,
69
+ "range" => line_range(entry[:line], entry[:end_line] || entry[:line]),
70
+ "selectionRange" => range_for(node),
71
+ }
72
+ end
73
+ end
74
+
75
+ def reference_nodes
76
+ nodes_of_type(Model::References::SimpleReference)
77
+ end
78
+
79
+ def declaration_nodes
80
+ nodes_of_type(Model::ModelElement).select do |node|
81
+ SYMBOL_KINDS.key?(node.class) && node.respond_to?(:id) && node.id
82
+ end
83
+ end
84
+
85
+ def token_at(position)
86
+ byte = byte_offset(position["line"], position["character"])
87
+ return nil unless byte
88
+
89
+ line_start = line_starts[position["line"]]
90
+ line_text = text.byteslice(line_start,
91
+ line_end_byte(position["line"]) - line_start)
92
+ column = byte - line_start
93
+ matches = line_text.to_s.b.to_enum(:scan, IDENTIFIER)
94
+ matches.each do
95
+ match = Regexp.last_match
96
+ next unless column.between?(match.begin(0), match.end(0))
97
+
98
+ return Token.new(text: match[0],
99
+ start_byte: line_start + match.begin(0),
100
+ byte_length: match[0].bytesize)
101
+ end
102
+ nil
103
+ end
104
+
105
+ def position(byte)
106
+ byte = 0 if byte.nil? || byte.negative?
107
+ byte = text.bytesize if byte > text.bytesize
108
+ line = line_starts.bsearch_index { |start| start > byte }
109
+ line = line ? line - 1 : line_starts.length - 1
110
+ prefix = text.byteslice(line_starts[line], byte - line_starts[line]).to_s
111
+ { "line" => line, "character" => utf16_length(prefix) }
112
+ end
113
+
114
+ def range(start_byte, length)
115
+ { "start" => position(start_byte),
116
+ "end" => position(start_byte + length) }
117
+ end
118
+
119
+ def range_for(node)
120
+ length = node.respond_to?(:id) && node.id ? node.id.to_s.bytesize : 1
121
+ range(name_offset(node), length)
122
+ end
123
+
124
+ def name_offset(node)
125
+ offset = node.source_offset.to_i
126
+ return offset if node.is_a?(Model::Declarations::Attribute)
127
+
128
+ if node.respond_to?(:id) && node.id
129
+ window = text.byteslice(offset, NAME_WINDOW).to_s
130
+ match = /\b#{Regexp.escape(node.id.to_s)}\b/i.match(window)
131
+ return offset + match.begin(0) if match
132
+ end
133
+ offset
134
+ end
135
+
136
+ def line_range(start_line, end_line)
137
+ start_byte = line_starts[start_line - 1] || text.bytesize
138
+ end_byte = line_end_byte(end_line - 1)
139
+ { "start" => position(start_byte), "end" => position(end_byte) }
140
+ end
141
+
142
+ def byte_offset(line, character)
143
+ start = line_starts[line]
144
+ return nil unless start
145
+
146
+ byte = start
147
+ units = 0
148
+ text.byteslice(start, line_end_byte(line) - start).to_s.each_char do |char|
149
+ break if units >= character
150
+
151
+ units += utf16_code_units(char)
152
+ byte += char.bytesize
153
+ end
154
+ byte
155
+ end
156
+
157
+ private
158
+
159
+ def parse!
160
+ @file = Expressir::Express::Parser.from_exp(@text,
161
+ include_source: true)
162
+ @collector = nil
163
+ @line_starts = nil
164
+ @parse_error = nil
165
+ rescue StandardError => e
166
+ @parse_error = e
167
+ end
168
+
169
+ def position_index
170
+ Express::NodePositionIndex.new(@file, Express::LineMap.new(@text.b))
171
+ end
172
+
173
+ def nodes_of_type(type)
174
+ collector.nodes.grep(type)
175
+ end
176
+
177
+ def collector
178
+ @collector ||= Collector.new.tap { |instance| instance.visit(@file) }
179
+ end
180
+
181
+ class Collector < Express::ModelVisitor
182
+ attr_reader :nodes
183
+
184
+ def initialize
185
+ super
186
+ @nodes = []
187
+ end
188
+
189
+ def visit(node)
190
+ @nodes << node
191
+ super
192
+ end
193
+ end
194
+
195
+ def line_starts
196
+ @line_starts ||= begin
197
+ bytes = @text.b
198
+ starts = [0]
199
+ index = 0
200
+ while (index = bytes.index("\n", index))
201
+ starts << (index + 1)
202
+ index += 1
203
+ end
204
+ starts
205
+ end
206
+ end
207
+
208
+ def line_end_byte(line)
209
+ following = line_starts[line + 1]
210
+ following ? following - 1 : @text.bytesize
211
+ end
212
+
213
+ def utf16_length(string)
214
+ string.each_char.sum { |char| utf16_code_units(char) }
215
+ end
216
+
217
+ def utf16_code_units(char)
218
+ char.ord > 0xFFFF ? 2 : 1
219
+ end
220
+
221
+ def uri_path(uri)
222
+ uri.to_s.delete_prefix("file://")
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Expressir
6
+ module Lsp
7
+ # JSON-RPC 2.0 transport over stdio (or any IO pair) implementing the
8
+ # LSP 3.17 subset: lifecycle, text sync, diagnostics, symbols,
9
+ # definition, references, hover and rename.
10
+ class Server
11
+ SUPPORTED_CHARACTERS = "utf-16"
12
+
13
+ def initialize(input: $stdin, output: $stdout)
14
+ @input = input
15
+ @output = output
16
+ @workspace = Workspace.new
17
+ @shutdown = false
18
+ @exit = false
19
+ end
20
+
21
+ def run
22
+ while (message = read_message)
23
+ dispatch(message)
24
+ break if @exit
25
+ end
26
+ @shutdown ? 0 : 1
27
+ end
28
+
29
+ private
30
+
31
+ def dispatch(message)
32
+ method = message["method"]
33
+ return respond_error(message, -32601, "unknown method #{method}") if method.nil?
34
+
35
+ case method
36
+ when "initialize" then respond(message, initialize_result)
37
+ when "initialized", "$/cancelRequest" then nil
38
+ when "shutdown"
39
+ @shutdown = true
40
+ respond(message, nil)
41
+ when "exit" then @exit = true
42
+ when "textDocument/didOpen" then did_open(message["params"])
43
+ when "textDocument/didChange" then did_change(message["params"])
44
+ when "textDocument/didSave" then did_save(message["params"])
45
+ when "textDocument/didClose" then did_close(message["params"])
46
+ when "textDocument/documentSymbol"
47
+ respond(message, @workspace.symbols(uri_of(message)))
48
+ when "textDocument/definition"
49
+ respond(message, @workspace.definition(uri_of(message), position_of(message)))
50
+ when "textDocument/references"
51
+ respond(message, @workspace.references(
52
+ uri_of(message), position_of(message),
53
+ include_declaration: include_declaration?(message)
54
+ ))
55
+ when "textDocument/hover"
56
+ respond(message, @workspace.hover(uri_of(message), position_of(message)))
57
+ when "textDocument/prepareRename"
58
+ respond(message, @workspace.prepare_rename(uri_of(message), position_of(message)))
59
+ when "textDocument/rename"
60
+ respond(message, @workspace.rename(
61
+ uri_of(message), position_of(message),
62
+ message.dig("params", "newName").to_s
63
+ ))
64
+ else
65
+ respond_error(message, -32601, "unknown method #{method}") if message["id"]
66
+ end
67
+ rescue StandardError => e
68
+ respond_error(message, -32603, e.message) if message["id"]
69
+ end
70
+
71
+ def initialize_result
72
+ {
73
+ "capabilities" => {
74
+ "positionEncoding" => SUPPORTED_CHARACTERS,
75
+ "textDocumentSync" => {
76
+ "openClose" => true,
77
+ "change" => 1,
78
+ "save" => { "includeText" => false },
79
+ },
80
+ "documentSymbolProvider" => true,
81
+ "definitionProvider" => true,
82
+ "referencesProvider" => true,
83
+ "hoverProvider" => true,
84
+ "renameProvider" => { "prepareProvider" => true },
85
+ },
86
+ "serverInfo" => {
87
+ "name" => "expressir-lsp",
88
+ "version" => Expressir::VERSION,
89
+ },
90
+ }
91
+ end
92
+
93
+ def did_open(params)
94
+ document = params.fetch("textDocument")
95
+ @workspace.open(uri: document.fetch("uri"),
96
+ text: document.fetch("text", ""))
97
+ publish_diagnostics(document.fetch("uri"))
98
+ end
99
+
100
+ def did_change(params)
101
+ uri = params.dig("textDocument", "uri")
102
+ change = Array(params["contentChanges"]).last || {}
103
+ @workspace.change(uri: uri, text: change.fetch("text", ""))
104
+ publish_diagnostics(uri)
105
+ end
106
+
107
+ def did_save(params)
108
+ publish_diagnostics(params.dig("textDocument", "uri"))
109
+ end
110
+
111
+ def did_close(params)
112
+ uri = params.dig("textDocument", "uri")
113
+ @workspace.close(uri)
114
+ notify("textDocument/publishDiagnostics",
115
+ "uri" => uri, "diagnostics" => [])
116
+ end
117
+
118
+ def publish_diagnostics(uri)
119
+ return unless uri
120
+
121
+ notify("textDocument/publishDiagnostics",
122
+ "uri" => uri,
123
+ "diagnostics" => @workspace.diagnostics(uri))
124
+ end
125
+
126
+ def uri_of(message)
127
+ message.dig("params", "textDocument", "uri")
128
+ end
129
+
130
+ def position_of(message)
131
+ message.dig("params", "position") || { "line" => 0, "character" => 0 }
132
+ end
133
+
134
+ def include_declaration?(message)
135
+ message.dig("params", "context", "includeDeclaration") != false
136
+ end
137
+
138
+ def respond(message, result)
139
+ write("jsonrpc" => "2.0", "id" => message["id"], "result" => result)
140
+ end
141
+
142
+ def respond_error(message, code, text)
143
+ write("jsonrpc" => "2.0", "id" => message["id"],
144
+ "error" => { "code" => code, "message" => text.to_s })
145
+ end
146
+
147
+ def notify(method, params)
148
+ write("jsonrpc" => "2.0", "method" => method, "params" => params)
149
+ end
150
+
151
+ def read_message
152
+ headers = read_headers
153
+ return nil unless headers
154
+
155
+ length = headers["content-length"].to_i
156
+ body = @input.read(length)
157
+ return nil if body.nil? || body.empty?
158
+
159
+ JSON.parse(body)
160
+ end
161
+
162
+ def read_headers
163
+ headers = {}
164
+ loop do
165
+ line = @input.gets
166
+ return nil unless line
167
+
168
+ line = line.sub(/\r?\n\z/, "")
169
+ break if line.empty?
170
+
171
+ name, value = line.split(":", 2)
172
+ headers[name.downcase] = value.to_s.strip
173
+ end
174
+ headers
175
+ end
176
+
177
+ def write(payload)
178
+ json = JSON.generate(payload)
179
+ @output.write("Content-Length: #{json.bytesize}\r\n\r\n#{json}")
180
+ @output.flush
181
+ end
182
+ end
183
+ end
184
+ end