jade-lang 0.8.0 → 0.10.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 +4 -4
- data/CHANGELOG.md +439 -2
- data/docs/interop.md +18 -1
- data/docs/stdlib.md +3 -1
- data/docs/syntax.md +96 -0
- data/lib/jade/api.rb +9 -0
- data/lib/jade/ast.rb +37 -1
- data/lib/jade/cli/eject.rb +132 -0
- data/lib/jade/cli.rb +2 -0
- data/lib/jade/codegen/boundary/specialized/list.rb +11 -7
- data/lib/jade/codegen/boundary/specialized/maybe.rb +2 -2
- data/lib/jade/codegen/boundary/specialized/record.rb +19 -4
- data/lib/jade/codegen/boundary/specialized/scalar.rb +2 -2
- data/lib/jade/codegen/boundary/specialized.rb +13 -5
- data/lib/jade/codegen/boundary.rb +12 -2
- data/lib/jade/codegen/context.rb +17 -0
- data/lib/jade/codegen/error.rb +1 -0
- data/lib/jade/codegen/function_call.rb +23 -7
- data/lib/jade/codegen/function_declaration.rb +25 -13
- data/lib/jade/codegen/helpers.rb +27 -4
- data/lib/jade/codegen/implementation.rb +2 -2
- data/lib/jade/codegen/inline.rb +1 -1
- data/lib/jade/codegen/inlines.rb +6 -1
- data/lib/jade/codegen/names.rb +31 -0
- data/lib/jade/codegen/pattern/constructor.rb +2 -2
- data/lib/jade/codegen/transforms/fold_shape.rb +1 -1
- data/lib/jade/codegen.rb +76 -15
- data/lib/jade/compiler.rb +2 -0
- data/lib/jade/entry.rb +1 -1
- data/lib/jade/error.rb +5 -0
- data/lib/jade/extensions.rb +70 -0
- data/lib/jade/formatter/calls.rb +68 -11
- data/lib/jade/formatter/collections.rb +4 -4
- data/lib/jade/formatter/declarations.rb +13 -15
- data/lib/jade/formatter/helper.rb +20 -0
- data/lib/jade/formatter/infix_application.rb +19 -4
- data/lib/jade/formatter/lambda.rb +24 -12
- data/lib/jade/formatter/pattern.rb +3 -0
- data/lib/jade/frontend/desugaring/placeholder.rb +40 -17
- data/lib/jade/frontend/desugaring.rb +29 -9
- data/lib/jade/frontend/fixity_fixer.rb +6 -4
- data/lib/jade/frontend/forward_declaration/error/alias_expansion.rb +23 -0
- data/lib/jade/frontend/forward_declaration/error/duplicate_constructor_name.rb +45 -0
- data/lib/jade/frontend/forward_declaration/error/duplicate_type_name.rb +42 -0
- data/lib/jade/frontend/forward_declaration/error.rb +3 -0
- data/lib/jade/frontend/forward_declaration/helper.rb +58 -0
- data/lib/jade/frontend/forward_declaration/import_declaration.rb +4 -0
- data/lib/jade/frontend/forward_declaration/interface_declaration.rb +6 -0
- data/lib/jade/frontend/forward_declaration/module.rb +4 -0
- data/lib/jade/frontend/forward_declaration/struct_declaration.rb +10 -1
- data/lib/jade/frontend/forward_declaration/type_alias_declaration.rb +37 -0
- data/lib/jade/frontend/forward_declaration/type_declaration.rb +10 -1
- data/lib/jade/frontend/forward_declaration.rb +2 -0
- data/lib/jade/frontend/pattern_analysis/exhaustiveness.rb +47 -43
- data/lib/jade/frontend/pattern_analysis/matrix.rb +28 -219
- data/lib/jade/frontend/pattern_analysis/pattern.rb +132 -0
- data/lib/jade/frontend/pattern_analysis/redundancy.rb +24 -0
- data/lib/jade/frontend/pattern_analysis/signature.rb +220 -0
- data/lib/jade/frontend/pattern_analysis/usefulness.rb +72 -0
- data/lib/jade/frontend/pattern_analysis/witnesses.rb +73 -0
- data/lib/jade/frontend/pattern_analysis.rb +5 -38
- data/lib/jade/frontend/semantic_analysis/body.rb +29 -1
- data/lib/jade/frontend/semantic_analysis/error/duplicate_implementation.rb +39 -0
- data/lib/jade/frontend/semantic_analysis/error/implementation_on_alias.rb +24 -0
- data/lib/jade/frontend/semantic_analysis/error/no_base_case.rb +45 -0
- data/lib/jade/frontend/semantic_analysis/error/placeholder_not_allowed.rb +15 -3
- data/lib/jade/frontend/semantic_analysis/error/recursive_type_alias.rb +24 -0
- data/lib/jade/frontend/semantic_analysis/error.rb +4 -0
- data/lib/jade/frontend/semantic_analysis/helper.rb +41 -0
- data/lib/jade/frontend/semantic_analysis/implementation.rb +13 -10
- data/lib/jade/frontend/semantic_analysis/inhabitedness.rb +90 -0
- data/lib/jade/frontend/semantic_analysis/interop_import_declaration.rb +28 -16
- data/lib/jade/frontend/semantic_analysis/keyed_call.rb +21 -5
- data/lib/jade/frontend/semantic_analysis/pattern_range.rb +16 -0
- data/lib/jade/frontend/semantic_analysis/struct_declaration.rb +1 -0
- data/lib/jade/frontend/semantic_analysis/type_alias_declaration/cycle_detection.rb +95 -0
- data/lib/jade/frontend/semantic_analysis/type_alias_declaration.rb +48 -0
- data/lib/jade/frontend/semantic_analysis/type_declaration.rb +1 -0
- data/lib/jade/frontend/semantic_analysis.rb +5 -0
- data/lib/jade/frontend/type_checking/canonicalize.rb +1 -0
- data/lib/jade/frontend/type_checking/cascade.rb +90 -0
- data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +10 -0
- data/lib/jade/frontend/type_checking/constraints/deriving.rb +5 -4
- data/lib/jade/frontend/type_checking/constraints.rb +18 -3
- data/lib/jade/frontend/type_checking/error/display.rb +49 -0
- data/lib/jade/frontend/type_checking/error/division_by_zero.rb +31 -0
- data/lib/jade/frontend/type_checking/error/empty_range_pattern.rb +25 -0
- data/lib/jade/frontend/type_checking/error/function_body_type_mismatch.rb +4 -3
- data/lib/jade/frontend/type_checking/error/function_call_type_mismatch.rb +108 -5
- data/lib/jade/frontend/type_checking/error/if_branch_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/if_branches_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/if_condition_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/implementation_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/list_item_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/missing_patterns.rb +20 -4
- data/lib/jade/frontend/type_checking/error/pattern_type_mismatch.rb +7 -1
- data/lib/jade/frontend/type_checking/error/range_pattern_type.rb +29 -0
- data/lib/jade/frontend/type_checking/error/record_access_type_mismatch.rb +2 -2
- data/lib/jade/frontend/type_checking/error/recursive_derivation.rb +25 -0
- data/lib/jade/frontend/type_checking/error/type_mismatch.rb +10 -2
- data/lib/jade/frontend/type_checking/error/unreachable_branch.rb +22 -0
- data/lib/jade/frontend/type_checking/error.rb +6 -0
- data/lib/jade/frontend/type_checking/inference/assign.rb +1 -1
- data/lib/jade/frontend/type_checking/inference/case_of.rb +21 -4
- data/lib/jade/frontend/type_checking/inference/division.rb +50 -0
- data/lib/jade/frontend/type_checking/inference/function_call.rb +47 -2
- data/lib/jade/frontend/type_checking/inference/lambda.rb +1 -1
- data/lib/jade/frontend/type_checking/inference/pattern.rb +35 -0
- data/lib/jade/frontend/type_checking/inference/type_alias_declaration.rb +18 -0
- data/lib/jade/frontend/type_checking/inference.rb +2 -0
- data/lib/jade/frontend/type_checking/loader.rb +11 -0
- data/lib/jade/frontend/type_checking/port_resolution.rb +26 -8
- data/lib/jade/frontend/type_checking/substitution.rb +2 -2
- data/lib/jade/frontend/type_checking.rb +3 -1
- data/lib/jade/frontend/usage_analysis.rb +5 -1
- data/lib/jade/interop/boundary.rb +64 -24
- data/lib/jade/interop/error.rb +35 -7
- data/lib/jade/interop/lowering.rb +8 -2
- data/lib/jade/lsp/converters.rb +83 -2
- data/lib/jade/lsp/handlers.rb +122 -25
- data/lib/jade/lsp/snippets.rb +12 -0
- data/lib/jade/lsp/state.rb +11 -4
- data/lib/jade/module_loader/build.rb +36 -0
- data/lib/jade/module_loader.rb +1 -0
- data/lib/jade/parsing/combinators.rb +78 -2
- data/lib/jade/parsing/error.rb +43 -6
- data/lib/jade/parsing/type.rb +5 -3
- data/lib/jade/parsing.rb +140 -26
- data/lib/jade/runtime.rb +32 -13
- data/lib/jade/source.rb +6 -2
- data/lib/jade/stdlib/basics.rb +22 -3
- data/lib/jade/stdlib/char.rb +2 -0
- data/lib/jade/stdlib/clock.rb +6 -2
- data/lib/jade/stdlib/decimal.rb +51 -14
- data/lib/jade/stdlib/intrinsics.rb +59 -6
- data/lib/jade/stdlib/list.rb +37 -1
- data/lib/jade/stdlib/number.rb +47 -0
- data/lib/jade/stdlib/range.rb +211 -0
- data/lib/jade/stdlib.rb +11 -4
- data/lib/jade/symbol/alias.rb +11 -0
- data/lib/jade/symbol/parser.rb +1 -1
- data/lib/jade/symbol.rb +13 -1
- data/lib/jade/task.rb +74 -56
- data/lib/jade/type/anonymous_record.rb +11 -2
- data/lib/jade/type/application.rb +11 -2
- data/lib/jade/type/base.rb +26 -0
- data/lib/jade/type/function.rb +11 -2
- data/lib/jade/type.rb +36 -10
- data/lib/jade/version.rb +1 -1
- data/lib/jade.rb +1 -0
- metadata +35 -3
- data/lib/jade/frontend/type_checking/constraints/deriving/assignable.rb +0 -170
data/lib/jade/lsp/converters.rb
CHANGED
|
@@ -33,6 +33,7 @@ module Jade
|
|
|
33
33
|
enum_member: 22,
|
|
34
34
|
struct: 23,
|
|
35
35
|
interface: 11,
|
|
36
|
+
type_parameter: 26,
|
|
36
37
|
}.freeze
|
|
37
38
|
|
|
38
39
|
HOVERABLE_SYMBOLS = [
|
|
@@ -44,6 +45,7 @@ module Jade
|
|
|
44
45
|
Jade::Symbol::Variant,
|
|
45
46
|
Jade::Symbol::Union,
|
|
46
47
|
Jade::Symbol::Struct,
|
|
48
|
+
Jade::Symbol::Alias,
|
|
47
49
|
].freeze
|
|
48
50
|
|
|
49
51
|
# LSP CompletionItemKind values we use.
|
|
@@ -66,8 +68,8 @@ module Jade
|
|
|
66
68
|
nil
|
|
67
69
|
end
|
|
68
70
|
|
|
69
|
-
def completion_items
|
|
70
|
-
Snippets
|
|
71
|
+
def completion_items(module_name = nil)
|
|
72
|
+
Snippets.for_module(module_name).map do |snippet|
|
|
71
73
|
{
|
|
72
74
|
label: snippet.label,
|
|
73
75
|
kind: COMPLETION_KIND_SNIPPET,
|
|
@@ -104,6 +106,21 @@ module Jade
|
|
|
104
106
|
end
|
|
105
107
|
end
|
|
106
108
|
|
|
109
|
+
# Enter and `end` are the two moments an editor can indent from what
|
|
110
|
+
# is already written, without the parse it will not get mid-typing.
|
|
111
|
+
def on_type_edits(text, line, character, typed)
|
|
112
|
+
text
|
|
113
|
+
.lines
|
|
114
|
+
.map(&:chomp)
|
|
115
|
+
.then do |lines|
|
|
116
|
+
case typed
|
|
117
|
+
in "\n" then indent_edit(lines, line, indent_after(lines, line))
|
|
118
|
+
in "d" then closing_edit(lines, line, character)
|
|
119
|
+
else []
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
107
124
|
def whole_document_edit(source, new_text)
|
|
108
125
|
last_line = source.line_starts.size - 1
|
|
109
126
|
{
|
|
@@ -162,6 +179,9 @@ module Jade
|
|
|
162
179
|
in AST::StructDeclaration(name:, range:)
|
|
163
180
|
document_symbol(name, :struct, source, range)
|
|
164
181
|
|
|
182
|
+
in AST::TypeAliasDeclaration(name:, range:)
|
|
183
|
+
document_symbol(name, :type_parameter, source, range)
|
|
184
|
+
|
|
165
185
|
in AST::InterfaceDeclaration(name:, range:, functions:)
|
|
166
186
|
document_symbol(
|
|
167
187
|
name, :interface, source, range,
|
|
@@ -374,8 +394,12 @@ module Jade
|
|
|
374
394
|
|
|
375
395
|
def hover_body(symbol, registry)
|
|
376
396
|
case symbol
|
|
397
|
+
in Jade::Symbol::Alias
|
|
398
|
+
render_alias(symbol, registry)
|
|
399
|
+
|
|
377
400
|
in Jade::Symbol::Union | Jade::Symbol::Struct
|
|
378
401
|
render_type(symbol, registry)
|
|
402
|
+
|
|
379
403
|
else
|
|
380
404
|
render_value(symbol, registry)
|
|
381
405
|
end
|
|
@@ -404,6 +428,21 @@ module Jade
|
|
|
404
428
|
)
|
|
405
429
|
end
|
|
406
430
|
|
|
431
|
+
# Expansion erases the name everywhere else, so this is the only place
|
|
432
|
+
# left that says what an alias stands for.
|
|
433
|
+
def render_alias(symbol, registry)
|
|
434
|
+
Jade::Type
|
|
435
|
+
.from_symbol(symbol.body, registry, Frontend::TypeChecking::VarGen.new)
|
|
436
|
+
.first
|
|
437
|
+
.then { "type alias #{symbol.name}#{alias_params(symbol)} = #{it}" }
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def alias_params(symbol)
|
|
441
|
+
symbol.type_params.empty? ?
|
|
442
|
+
'' :
|
|
443
|
+
"(#{symbol.type_params.map(&:name).join(', ')})"
|
|
444
|
+
end
|
|
445
|
+
|
|
407
446
|
def render_type(symbol, registry)
|
|
408
447
|
impls_for(symbol, registry)
|
|
409
448
|
.then { it.empty? ? '' : "\n\nimplements #{it.join(', ')}" }
|
|
@@ -471,6 +510,7 @@ module Jade
|
|
|
471
510
|
in AST::VariableReference | AST::ConstructorReference |
|
|
472
511
|
AST::QualifiedAccess | AST::FunctionDeclaration |
|
|
473
512
|
AST::TypeDeclaration | AST::StructDeclaration |
|
|
513
|
+
AST::TypeAliasDeclaration |
|
|
474
514
|
AST::InterfaceDeclaration | AST::VariantDeclaration
|
|
475
515
|
node.symbol.then { resolve_ref(it, registry) }
|
|
476
516
|
|
|
@@ -518,6 +558,47 @@ module Jade
|
|
|
518
558
|
document_symbol(fn.name, :function, source, fn.range)
|
|
519
559
|
end
|
|
520
560
|
|
|
561
|
+
INDENT = 2
|
|
562
|
+
|
|
563
|
+
# A block opens on a header, on a keyword that ends one, or on an
|
|
564
|
+
# unclosed bracket. `in` is a branch head: it sits with its `case`.
|
|
565
|
+
OPENS = /(\A(def|case|implements|interface|uses|if)\b)|((\bthen|\bwith|\belse|[({\[])\z)/
|
|
566
|
+
|
|
567
|
+
def indent_after(lines, line)
|
|
568
|
+
previous(lines, line)
|
|
569
|
+
.then { it ? indent_of(it) + (OPENS.match?(it.strip) ? INDENT : 0) : 0 }
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
# `end` closes the block its opener began, so it lands where the
|
|
573
|
+
# opener sits: one level out from the body above it.
|
|
574
|
+
def closing_edit(lines, line, character)
|
|
575
|
+
return [] unless lines[line].to_s[0...character].strip == 'end'
|
|
576
|
+
|
|
577
|
+
previous(lines, line)
|
|
578
|
+
.then { it ? [indent_of(it) - INDENT, 0].max : 0 }
|
|
579
|
+
.then { indent_edit(lines, line, it) }
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
def previous(lines, line)
|
|
583
|
+
lines[0...line].reverse.find { !it.strip.empty? }
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
def indent_edit(lines, line, width)
|
|
587
|
+
return [] if indent_of(lines[line].to_s) == width
|
|
588
|
+
|
|
589
|
+
[{
|
|
590
|
+
range: {
|
|
591
|
+
start: { line:, character: 0 },
|
|
592
|
+
end: { line:, character: indent_of(lines[line].to_s) },
|
|
593
|
+
},
|
|
594
|
+
newText: ' ' * width,
|
|
595
|
+
}]
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
def indent_of(line)
|
|
599
|
+
line[/\A */].length
|
|
600
|
+
end
|
|
601
|
+
|
|
521
602
|
def diagnostic_message(diagnostic)
|
|
522
603
|
[
|
|
523
604
|
diagnostic.message,
|
data/lib/jade/lsp/handlers.rb
CHANGED
|
@@ -24,6 +24,7 @@ module Jade
|
|
|
24
24
|
when 'textDocument/rename' then on_rename(state, message)
|
|
25
25
|
when 'textDocument/inlayHint' then on_inlay_hint(state, message)
|
|
26
26
|
when 'textDocument/formatting' then on_formatting(state, message)
|
|
27
|
+
when 'textDocument/onTypeFormatting' then on_type_formatting(state, message)
|
|
27
28
|
else on_unknown(state, message)
|
|
28
29
|
end
|
|
29
30
|
end
|
|
@@ -39,9 +40,13 @@ module Jade
|
|
|
39
40
|
|
|
40
41
|
def on_initialize(state, message)
|
|
41
42
|
params = (message['params'] || {})
|
|
42
|
-
root
|
|
43
|
+
# The editor's root is the project root, not the source root the
|
|
44
|
+
# compiler names modules from. Reading the manifest also loads the
|
|
45
|
+
# extension gems a project imports.
|
|
46
|
+
project = params
|
|
43
47
|
.then { it['rootUri'] || it.dig('workspaceFolders', 0, 'uri') }
|
|
44
48
|
.then { it ? it.sub(%r{\Afile://}, '') : Dir.pwd }
|
|
49
|
+
.then { [Project.find(it), it] }
|
|
45
50
|
|
|
46
51
|
{
|
|
47
52
|
capabilities: {
|
|
@@ -55,10 +60,14 @@ module Jade
|
|
|
55
60
|
renameProvider: { prepareProvider: true },
|
|
56
61
|
inlayHintProvider: true,
|
|
57
62
|
documentFormattingProvider: true,
|
|
63
|
+
documentOnTypeFormattingProvider: {
|
|
64
|
+
firstTriggerCharacter: "\n",
|
|
65
|
+
moreTriggerCharacter: ['d'],
|
|
66
|
+
},
|
|
58
67
|
},
|
|
59
68
|
serverInfo: { name: 'jade-lsp', version: '0.1.0' },
|
|
60
69
|
}
|
|
61
|
-
.then { [state.
|
|
70
|
+
.then { [state.with_project(*project), [respond(message['id'], it)]] }
|
|
62
71
|
end
|
|
63
72
|
|
|
64
73
|
def negotiate_encoding(params)
|
|
@@ -70,7 +79,7 @@ module Jade
|
|
|
70
79
|
def on_did_open(state, params)
|
|
71
80
|
params['textDocument']
|
|
72
81
|
.then { state.put_buffer(it['uri'], it['text']) }
|
|
73
|
-
.then { recompile_and_publish(it) }
|
|
82
|
+
.then { recompile_and_publish(it, scope: :project) }
|
|
74
83
|
end
|
|
75
84
|
|
|
76
85
|
def on_did_change(state, params)
|
|
@@ -83,7 +92,7 @@ module Jade
|
|
|
83
92
|
|
|
84
93
|
def on_did_close(state, params)
|
|
85
94
|
params['textDocument']['uri']
|
|
86
|
-
.then { recompile_and_publish(state.close(it), extra_uris: [it]) }
|
|
95
|
+
.then { recompile_and_publish(state.close(it), extra_uris: [it], scope: :project) }
|
|
87
96
|
end
|
|
88
97
|
|
|
89
98
|
def on_document_symbol(state, message)
|
|
@@ -116,7 +125,18 @@ module Jade
|
|
|
116
125
|
end
|
|
117
126
|
|
|
118
127
|
def on_completion(state, message)
|
|
119
|
-
|
|
128
|
+
message
|
|
129
|
+
.dig('params', 'textDocument', 'uri')
|
|
130
|
+
.then { module_name_for(it, state.source_root) }
|
|
131
|
+
.then { [state, [respond(message['id'], Converters.completion_items(it))]] }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def module_name_for(uri, source_root)
|
|
135
|
+
return nil unless uri && source_root
|
|
136
|
+
|
|
137
|
+
Converters
|
|
138
|
+
.relative_path(uri, source_root)
|
|
139
|
+
.then { Source.module_name_for(it) }
|
|
120
140
|
end
|
|
121
141
|
|
|
122
142
|
def on_prepare_rename(state, message)
|
|
@@ -148,6 +168,23 @@ module Jade
|
|
|
148
168
|
[state, [respond(message['id'], edits)]]
|
|
149
169
|
end
|
|
150
170
|
|
|
171
|
+
def on_type_formatting(state, message)
|
|
172
|
+
params = message['params']
|
|
173
|
+
state
|
|
174
|
+
.buffers[params.dig('textDocument', 'uri')]
|
|
175
|
+
.then { it ? on_type_edits(it, params) : [] }
|
|
176
|
+
.then { [state, [respond(message['id'], it)]] }
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def on_type_edits(text, params)
|
|
180
|
+
Converters.on_type_edits(
|
|
181
|
+
text,
|
|
182
|
+
params.dig('position', 'line'),
|
|
183
|
+
params.dig('position', 'character'),
|
|
184
|
+
params['ch'],
|
|
185
|
+
)
|
|
186
|
+
end
|
|
187
|
+
|
|
151
188
|
def formatting_edits_for(state, uri)
|
|
152
189
|
text = state.buffers[uri]
|
|
153
190
|
return nil unless text
|
|
@@ -261,43 +298,103 @@ module Jade
|
|
|
261
298
|
# extra_uris always receive a publishDiagnostics, but compile output
|
|
262
299
|
# wins: if a real diagnostic came back for the URI, we send that, not
|
|
263
300
|
# an empty clear.
|
|
264
|
-
def recompile_and_publish(state, extra_uris: [])
|
|
265
|
-
|
|
266
|
-
return [state, extra_uris.map { publish_for(it, []) }]
|
|
267
|
-
end
|
|
301
|
+
def recompile_and_publish(state, extra_uris: [], scope: :buffers)
|
|
302
|
+
return [state, extra_uris.map { publish_for(it, []) }] if state.source_root.nil?
|
|
268
303
|
|
|
269
304
|
overlays = state.buffers
|
|
270
305
|
.to_h { |uri, text| [Converters.relative_path(uri, state.source_root), text] }
|
|
271
306
|
|
|
272
|
-
diagnostics_by_uri, registry =
|
|
307
|
+
diagnostics_by_uri, registry =
|
|
308
|
+
compile_each(state, overlays, entries(state, overlays, scope))
|
|
273
309
|
|
|
274
|
-
uris = (diagnostics_by_uri.keys + state.buffers.keys + extra_uris).uniq
|
|
310
|
+
uris = (diagnostics_by_uri.keys + state.buffers.keys + state.published + extra_uris).uniq
|
|
275
311
|
messages = uris.map { publish_for(it, diagnostics_by_uri[it] || []) }
|
|
276
312
|
|
|
277
|
-
next_state = registry ? state.set_registry(registry) : state
|
|
313
|
+
next_state = (registry ? state.set_registry(registry) : state)
|
|
314
|
+
.with_published(diagnostics_by_uri.keys)
|
|
278
315
|
[next_state, messages]
|
|
279
316
|
end
|
|
280
317
|
|
|
281
|
-
#
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
318
|
+
# Typing recompiles what is open; opening and closing sweep.
|
|
319
|
+
def entries(state, overlays, scope)
|
|
320
|
+
return overlays.keys if scope == :buffers
|
|
321
|
+
|
|
322
|
+
Dir
|
|
323
|
+
.glob('**/*.jd', base: state.source_root)
|
|
324
|
+
.sort
|
|
325
|
+
.then { imported_last(it, state.registry) }
|
|
289
326
|
end
|
|
290
327
|
|
|
291
|
-
|
|
328
|
+
# Compiling a module compiles what it imports, so the modules nothing
|
|
329
|
+
# imports cover the project in one pass. A file the last compile never
|
|
330
|
+
# saw goes first, since it may be the new root.
|
|
331
|
+
def imported_last(paths, registry)
|
|
332
|
+
return paths unless registry
|
|
333
|
+
|
|
334
|
+
[imported(registry), module_names(registry)]
|
|
335
|
+
.then { |names, by_path| paths.partition { !names.include?(by_path[it]) } }
|
|
336
|
+
.flatten
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def imported(registry)
|
|
340
|
+
registry.dependency_graph.nodes.values.flatten.to_set
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def module_names(registry)
|
|
344
|
+
registry
|
|
345
|
+
.modules
|
|
346
|
+
.each_value
|
|
347
|
+
.filter_map { [it.source.uri, it.name] if it.source }
|
|
348
|
+
.to_h
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def compile_each(state, overlays, entries)
|
|
352
|
+
entries
|
|
353
|
+
.reduce([{}, nil, []]) { |acc, entry| compile_unless_seen(acc, state, overlays, entry) }
|
|
354
|
+
.first(2)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def compile_unless_seen((diagnostics, registry, seen), state, overlays, entry)
|
|
358
|
+
return [diagnostics, registry, seen] if seen.include?(entry)
|
|
359
|
+
|
|
360
|
+
compile(state, entry, overlays)
|
|
361
|
+
.then { |(compiled, found)| [diagnostics.merge(found), compiled || registry, seen + compiled_paths(compiled)] }
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def compiled_paths(registry)
|
|
365
|
+
return [] unless registry
|
|
366
|
+
|
|
367
|
+
registry
|
|
368
|
+
.modules
|
|
369
|
+
.each_value
|
|
370
|
+
.reject { Stdlib.is_stdlib?(it) }
|
|
371
|
+
.filter_map { it.source&.uri }
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def compile(state, entry_path, overlays)
|
|
292
375
|
ModuleLoader
|
|
293
|
-
.load(source_root, entry_path, tolerant: true, overlays:)
|
|
294
|
-
.then { [it, collect_diagnostics(it, source_root)] }
|
|
376
|
+
.load(state.source_root, entry_path, cache_dir: state.cache_dir, tolerant: true, overlays:)
|
|
377
|
+
.then { [it, collect_diagnostics(it, state.source_root)] }
|
|
295
378
|
rescue Jade::CompilationError => e
|
|
296
|
-
[nil, diagnostics_by_uri(e.diagnostics, source_root)]
|
|
379
|
+
[nil, diagnostics_by_uri(e.diagnostics, state.source_root)]
|
|
297
380
|
rescue StandardError => e
|
|
298
381
|
$stderr.puts "[jade-lsp] compile crash: #{e.class}: #{e.message}"
|
|
299
382
|
$stderr.puts e.backtrace.first(20).join("\n")
|
|
300
|
-
[nil,
|
|
383
|
+
[nil, crash_diagnostic(state.source_root, entry_path, overlays, e)]
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Publishing nothing renders a crashed compile as a clean file.
|
|
387
|
+
def crash_diagnostic(source_root, entry_path, overlays, error)
|
|
388
|
+
Jade::Source
|
|
389
|
+
.new(uri: entry_path, text: overlays[entry_path] || '')
|
|
390
|
+
.then { Jade::Diagnostics::Label[it, 0...0, nil] }
|
|
391
|
+
.then do
|
|
392
|
+
Jade::Diagnostics::Diagnostic.error(
|
|
393
|
+
"jade-lsp crashed compiling this file: #{error.class}: #{error.message}",
|
|
394
|
+
primary: it,
|
|
395
|
+
)
|
|
396
|
+
end
|
|
397
|
+
.then { { Converters.lsp_uri(entry_path, source_root) => [it] } }
|
|
301
398
|
end
|
|
302
399
|
|
|
303
400
|
def collect_diagnostics(registry, source_root)
|
data/lib/jade/lsp/snippets.rb
CHANGED
|
@@ -105,6 +105,18 @@ module Jade
|
|
|
105
105
|
|
|
106
106
|
module_function
|
|
107
107
|
|
|
108
|
+
# The compiler requires the name to match the path, so a placeholder
|
|
109
|
+
# here offers a decision with one right answer.
|
|
110
|
+
def for_module(module_name)
|
|
111
|
+
return ALL unless module_name
|
|
112
|
+
|
|
113
|
+
ALL.map do
|
|
114
|
+
next it unless it.label == 'module'
|
|
115
|
+
|
|
116
|
+
it.with(body: "module #{module_name} exposing (${0})")
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
108
120
|
# Every form, as plain Jade — what `jade q syntax` serves.
|
|
109
121
|
def catalog
|
|
110
122
|
ALL.map { { form: it.label, detail: it.detail, source: it.source } }
|
data/lib/jade/lsp/state.rb
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
module Jade
|
|
2
2
|
module LSP
|
|
3
|
-
State = Data.define(:source_root, :buffers, :registry) do
|
|
3
|
+
State = Data.define(:source_root, :cache_dir, :buffers, :registry, :published) do
|
|
4
4
|
def self.empty
|
|
5
|
-
new(source_root: nil, buffers: {}, registry: nil)
|
|
5
|
+
new(source_root: nil, cache_dir: nil, buffers: {}, registry: nil, published: [])
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
# No manifest means no cache directory to share with the CLI, and
|
|
9
|
+
# every compile starts from nothing.
|
|
10
|
+
def with_project(project, root)
|
|
11
|
+
with(source_root: project&.source_root || root, cache_dir: project&.cache_path)
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
def put_buffer(uri, text)
|
|
@@ -20,6 +22,11 @@ module Jade
|
|
|
20
22
|
def set_registry(reg)
|
|
21
23
|
with(registry: reg)
|
|
22
24
|
end
|
|
25
|
+
|
|
26
|
+
# Cleared next round, whether or not the file is still open.
|
|
27
|
+
def with_published(uris)
|
|
28
|
+
with(published: uris)
|
|
29
|
+
end
|
|
23
30
|
end
|
|
24
31
|
end
|
|
25
32
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
require 'jade/module_loader/cache'
|
|
4
|
+
|
|
5
|
+
module Jade
|
|
6
|
+
module ModuleLoader
|
|
7
|
+
# The emitted Ruby, as opposed to `Cache`, which holds compiled
|
|
8
|
+
# modules. Generated code calls the runtime by name, so a build left
|
|
9
|
+
# by another version of the compiler can call a helper this one no
|
|
10
|
+
# longer has. Sources are untouched in that case, so mtimes say
|
|
11
|
+
# nothing: keep the fingerprint next to the build and drop the build
|
|
12
|
+
# when it does not match.
|
|
13
|
+
module Build
|
|
14
|
+
extend self
|
|
15
|
+
|
|
16
|
+
FINGERPRINT = '.fingerprint'
|
|
17
|
+
|
|
18
|
+
def discard_foreign(root)
|
|
19
|
+
return unless File.exist?(root)
|
|
20
|
+
return if fingerprint(root) == Cache.compiler_fingerprint
|
|
21
|
+
|
|
22
|
+
FileUtils.rm_rf(root)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def stamp(root)
|
|
26
|
+
File.write(File.join(root, FINGERPRINT), Cache.compiler_fingerprint)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def fingerprint(root)
|
|
32
|
+
File.join(root, FINGERPRINT).then { File.read(it) if File.exist?(it) }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/jade/module_loader.rb
CHANGED
|
@@ -6,6 +6,7 @@ require 'jade/ast'
|
|
|
6
6
|
require 'jade/registry'
|
|
7
7
|
require 'jade/frontend'
|
|
8
8
|
require 'jade/codegen'
|
|
9
|
+
require 'jade/module_loader/build'
|
|
9
10
|
require 'jade/module_loader/cache'
|
|
10
11
|
require 'jade/module_loader/dependency_resolver'
|
|
11
12
|
require 'jade/module_loader/dependency_graph'
|
|
@@ -166,6 +166,26 @@ module Jade
|
|
|
166
166
|
end
|
|
167
167
|
end
|
|
168
168
|
|
|
169
|
+
# A word that only reads as a keyword in one position, so it stays
|
|
170
|
+
# usable as an ordinary name everywhere else.
|
|
171
|
+
def contextual(word)
|
|
172
|
+
(@contextuals ||= {})[word] ||= P.new do |state|
|
|
173
|
+
type(:identifier).call(state).and_then do |(token, next_state)|
|
|
174
|
+
next Ok[[token, next_state]] if token.value == word
|
|
175
|
+
|
|
176
|
+
Err[[
|
|
177
|
+
Parsing::UnexpectedTokenError.new(
|
|
178
|
+
entry: state.entry,
|
|
179
|
+
span: token.range,
|
|
180
|
+
actual: token,
|
|
181
|
+
expected: word,
|
|
182
|
+
),
|
|
183
|
+
state,
|
|
184
|
+
]]
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
169
189
|
def lazy(&block)
|
|
170
190
|
P.new do |input|
|
|
171
191
|
block.call.call(input)
|
|
@@ -216,6 +236,13 @@ module Jade
|
|
|
216
236
|
with(diagnostics: diagnostics.add(diagnostic))
|
|
217
237
|
end
|
|
218
238
|
|
|
239
|
+
# Is the token here on the same line as the one before it?
|
|
240
|
+
def continues_line?
|
|
241
|
+
return true if eof? || position.zero?
|
|
242
|
+
|
|
243
|
+
same_line?(tokens[position - 1].range.begin, current.range.begin)
|
|
244
|
+
end
|
|
245
|
+
|
|
219
246
|
def skip_until(sync_types)
|
|
220
247
|
(position...tokens.length)
|
|
221
248
|
.find { sync_types.include?(tokens[it].type) }
|
|
@@ -228,6 +255,12 @@ module Jade
|
|
|
228
255
|
@fn = block
|
|
229
256
|
end
|
|
230
257
|
|
|
258
|
+
def stalled?(error, state)
|
|
259
|
+
return state.eof? if error.span.nil?
|
|
260
|
+
|
|
261
|
+
error.span.begin == state.current&.range&.begin
|
|
262
|
+
end
|
|
263
|
+
|
|
231
264
|
def call(tokens)
|
|
232
265
|
@fn.call(tokens)
|
|
233
266
|
end
|
|
@@ -282,8 +315,51 @@ module Jade
|
|
|
282
315
|
map_error(&:commit)
|
|
283
316
|
end
|
|
284
317
|
|
|
285
|
-
|
|
286
|
-
|
|
318
|
+
# A stalled alternation reports whichever branch it tried last, so
|
|
319
|
+
# say what the position wanted instead. One that got somewhere is
|
|
320
|
+
# already reporting the useful error.
|
|
321
|
+
def expecting(what)
|
|
322
|
+
P.new do |state|
|
|
323
|
+
call(state).map_error do |(err, err_state)|
|
|
324
|
+
[!err.committed? && stalled?(err, state) ? err.copy(expected: what) : err, err_state]
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# A `(` opening a new line starts a statement, it does not continue
|
|
330
|
+
# the expression above it: `(x, y) = p` is a binding, not a call on
|
|
331
|
+
# whatever the line before ended with.
|
|
332
|
+
def on_same_line
|
|
333
|
+
P.new do |state|
|
|
334
|
+
next Err[[
|
|
335
|
+
Parsing::UnexpectedTokenError.new(
|
|
336
|
+
entry: state.entry,
|
|
337
|
+
span: state.current.range,
|
|
338
|
+
actual: state.current,
|
|
339
|
+
expected: 'the same line',
|
|
340
|
+
),
|
|
341
|
+
state,
|
|
342
|
+
]] unless state.continues_line?
|
|
343
|
+
|
|
344
|
+
call(state)
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def parsing(name)
|
|
349
|
+
map_error { |err| err.copy(context: [name, *err.context]) }
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# Opens an empty body's span out to the construct it belongs to.
|
|
353
|
+
# The innermost one gets there first; past that the span is wider
|
|
354
|
+
# than the marker `body` left, so an enclosing construct skips it.
|
|
355
|
+
def spanning_empty_body
|
|
356
|
+
P.new do |state|
|
|
357
|
+
call(state).map_error do |(err, err_state)|
|
|
358
|
+
next [err, err_state] unless err.is_a?(MissingBodyError) && err.span.size == 1
|
|
359
|
+
|
|
360
|
+
[err.copy(span: state.current.range.begin...err.actual.range.end), err_state]
|
|
361
|
+
end
|
|
362
|
+
end
|
|
287
363
|
end
|
|
288
364
|
end
|
|
289
365
|
end
|
data/lib/jade/parsing/error.rb
CHANGED
|
@@ -21,14 +21,15 @@ module Jade
|
|
|
21
21
|
self
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def
|
|
24
|
+
def copy(**changes)
|
|
25
25
|
self.class.new(
|
|
26
|
-
entry:
|
|
27
|
-
span:
|
|
28
|
-
actual:
|
|
29
|
-
expected:
|
|
26
|
+
entry: @entry,
|
|
27
|
+
span: @span,
|
|
28
|
+
actual: @actual,
|
|
29
|
+
expected: @expected,
|
|
30
30
|
committed: @committed,
|
|
31
|
-
context:
|
|
31
|
+
context: @context,
|
|
32
|
+
**changes,
|
|
32
33
|
)
|
|
33
34
|
end
|
|
34
35
|
|
|
@@ -152,6 +153,42 @@ module Jade
|
|
|
152
153
|
end
|
|
153
154
|
end
|
|
154
155
|
|
|
156
|
+
# `in 0..limit`. Rejecting it is right — coverage is decided before
|
|
157
|
+
# `limit` exists — but the reader needs to be told that, not sent to
|
|
158
|
+
# look at their `then`.
|
|
159
|
+
class NonLiteralRangeBoundError < Error
|
|
160
|
+
def message
|
|
161
|
+
"#{context_prefix}A range pattern's bounds have to be literals, and " \
|
|
162
|
+
"#{@actual.value.inspect} is a value. Which integers a branch " \
|
|
163
|
+
"covers is settled when the code is compiled, so a bound the " \
|
|
164
|
+
"compiler cannot see cannot be checked."
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def label
|
|
168
|
+
'range bound is not a literal'
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def notes
|
|
172
|
+
[
|
|
173
|
+
Jade::Diagnostics::Annotation[
|
|
174
|
+
:help,
|
|
175
|
+
'match `_` and compare in the body, or use `if` — either way the ' \
|
|
176
|
+
'branch is no longer something exhaustiveness can account for',
|
|
177
|
+
],
|
|
178
|
+
]
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
class MissingBodyError < UnexpectedTokenError
|
|
183
|
+
def hint
|
|
184
|
+
'(a body has to produce a value)'
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def label
|
|
188
|
+
'missing body'
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
155
192
|
class InvalidOperatorError < Error
|
|
156
193
|
HINTS = {
|
|
157
194
|
'/=' => 'Use `!=` for inequality.',
|
data/lib/jade/parsing/type.rb
CHANGED
|
@@ -5,7 +5,9 @@ module Jade
|
|
|
5
5
|
module Type
|
|
6
6
|
extend Combinators::Dsl
|
|
7
7
|
|
|
8
|
-
parser(:type_expression) {
|
|
8
|
+
parser(:type_expression) {
|
|
9
|
+
(type_function | type_atom | type_record).expecting('a type')
|
|
10
|
+
}
|
|
9
11
|
|
|
10
12
|
parser(:type_atom) {
|
|
11
13
|
type_application | type_var | type_tuple | grouped(lazy { type_function })
|
|
@@ -65,8 +67,8 @@ module Jade
|
|
|
65
67
|
no_args = [Combinators::CommaList.empty, nil]
|
|
66
68
|
|
|
67
69
|
(
|
|
68
|
-
(type_name >> optional(type_application_args, default: no_args)) |
|
|
69
|
-
(type_var >> type_application_args)
|
|
70
|
+
(type_name >> optional(type_application_args.on_same_line, default: no_args)) |
|
|
71
|
+
(type_var >> type_application_args.on_same_line)
|
|
70
72
|
).map(&AST.type_application)
|
|
71
73
|
}
|
|
72
74
|
|