jade-lang 0.9.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 +400 -12
- 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/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/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 +3 -0
- data/lib/jade/frontend/semantic_analysis/helper.rb +31 -0
- data/lib/jade/frontend/semantic_analysis/implementation.rb +13 -0
- 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.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 +26 -1
- 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 +0 -2
- 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
- metadata +33 -2
|
@@ -83,7 +83,7 @@ module Jade
|
|
|
83
83
|
impl_sym
|
|
84
84
|
.functions[fn_name]
|
|
85
85
|
.then { it.is_a?(Symbol::ValueRef) ? it : nil }
|
|
86
|
-
&.then { "
|
|
86
|
+
&.then { "#{internal(it.module_name)}.#{it.name}" }
|
|
87
87
|
&.then { Pretty.block("def #{ruby_method}(other)", "#{it}(self, other)") }
|
|
88
88
|
end
|
|
89
89
|
end
|
|
@@ -143,7 +143,7 @@ module Jade
|
|
|
143
143
|
fn_map = symbol.functions.filter_map { |fn_name, ref|
|
|
144
144
|
next unless ref.is_a?(Symbol::ValueRef)
|
|
145
145
|
|
|
146
|
-
[fn_name, "->(*args) {
|
|
146
|
+
[fn_name, "->(*args) { #{internal(ref.module_name)}.#{ref.name}(*args) }"]
|
|
147
147
|
}.to_h
|
|
148
148
|
|
|
149
149
|
return "" if fn_map.empty?
|
data/lib/jade/codegen/inline.rb
CHANGED
data/lib/jade/codegen/inlines.rb
CHANGED
|
@@ -5,7 +5,9 @@ module Jade
|
|
|
5
5
|
|
|
6
6
|
# Ruby's native ordering on these types matches LT/EQ/GT semantics, so
|
|
7
7
|
# the derived comparison ops can collapse to plain operators.
|
|
8
|
-
RUBY_NATIVE_COMPARES = %w[
|
|
8
|
+
RUBY_NATIVE_COMPARES = %w[
|
|
9
|
+
Basics.int_compare Basics.float_compare String.str_compare Char.char_compare
|
|
10
|
+
].to_set.freeze
|
|
9
11
|
RUBY_NATIVE_EQS = %w[Basics.int_eq Basics.float_eq Basics.bool_eq String.str_eq].to_set.freeze
|
|
10
12
|
|
|
11
13
|
DERIVED_COMPARISONS = {
|
|
@@ -32,6 +34,7 @@ module Jade
|
|
|
32
34
|
'Basics.int_mul' => ->(a, b) { "(#{a} * #{b})" },
|
|
33
35
|
'Basics.int_div' => ->(a, b) { "(#{a} / #{b})" },
|
|
34
36
|
'Basics.mod' => ->(a, b) { "(#{a} % #{b})" },
|
|
37
|
+
'Number.unwrap' => ->(n) { n },
|
|
35
38
|
'Basics.float_add' => ->(a, b) { "(#{a} + #{b})" },
|
|
36
39
|
'Basics.float_sub' => ->(a, b) { "(#{a} - #{b})" },
|
|
37
40
|
'Basics.float_mul' => ->(a, b) { "(#{a} * #{b})" },
|
|
@@ -113,6 +116,7 @@ module Jade
|
|
|
113
116
|
'Char.upper?' => ->(c) { "#{c}.match?(/[A-Z]/)" },
|
|
114
117
|
'Char.lower?' => ->(c) { "#{c}.match?(/[a-z]/)" },
|
|
115
118
|
'Char.char_eq' => ->(a, b) { "(#{a} == #{b})" },
|
|
119
|
+
'Char.char_compare' => ->(a, b) { "#{a}.compare(#{b})" },
|
|
116
120
|
|
|
117
121
|
'Tuple.pair' => ->(a, b) { "Jade::Tuple::Tuple2[#{a}, #{b}]" },
|
|
118
122
|
'Tuple.first' => ->(t) { "#{t}._1" },
|
|
@@ -173,6 +177,7 @@ module Jade
|
|
|
173
177
|
|
|
174
178
|
# Bodies that don't fit a single Ruby expression.
|
|
175
179
|
NO_INLINE = %w[
|
|
180
|
+
Number.checked
|
|
176
181
|
List.sort_with
|
|
177
182
|
List.sort_by_with
|
|
178
183
|
List.filter_map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Codegen
|
|
3
|
+
# Ruby rejects its keywords as local variables and parameters, but accepts
|
|
4
|
+
# them as method names and Data members — so only bindings are rewritten
|
|
5
|
+
# here, and nothing a Ruby caller sees changes.
|
|
6
|
+
module Names
|
|
7
|
+
extend self
|
|
8
|
+
|
|
9
|
+
RESERVED = ::Set[
|
|
10
|
+
'BEGIN', 'END', '__ENCODING__', '__FILE__', '__LINE__',
|
|
11
|
+
'alias', 'and', 'begin', 'break', 'case', 'class', 'def',
|
|
12
|
+
'defined?', 'do', 'else', 'elsif', 'end', 'ensure', 'false',
|
|
13
|
+
'for', 'if', 'in', 'module', 'next', 'nil', 'not', 'or', 'redo',
|
|
14
|
+
'rescue', 'retry', 'return', 'self', 'super', 'then', 'true',
|
|
15
|
+
'undef', 'unless', 'until', 'when', 'while', 'yield',
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
# Injective: a name that is a reserved word under trailing underscores
|
|
19
|
+
# gains one more, so `begin` and `begin_` stay distinct.
|
|
20
|
+
def local(name)
|
|
21
|
+
reserved_stem?(name) ? "#{name}_" : name
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def reserved_stem?(name)
|
|
27
|
+
RESERVED.include?(name.sub(/_+\z/, ''))
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -8,7 +8,7 @@ module Jade
|
|
|
8
8
|
def generate(node, registry)
|
|
9
9
|
node => AST::Pattern::Constructor(symbol:, patterns:)
|
|
10
10
|
constructor = registry.lookup(symbol)
|
|
11
|
-
qualified =
|
|
11
|
+
qualified = rooted(constructor.qualified_name)
|
|
12
12
|
|
|
13
13
|
if keyed_variant?(constructor)
|
|
14
14
|
generate_keyed(qualified, patterns.first, registry)
|
|
@@ -39,7 +39,7 @@ module Jade
|
|
|
39
39
|
def generate_keyed(qualified, pattern, registry)
|
|
40
40
|
case pattern
|
|
41
41
|
in AST::Pattern::Binding(name:)
|
|
42
|
-
"#{qualified} => #{name}"
|
|
42
|
+
"#{qualified} => #{Names.local(name)}"
|
|
43
43
|
|
|
44
44
|
in AST::Pattern::Wildcard
|
|
45
45
|
qualified
|
data/lib/jade/codegen.rb
CHANGED
|
@@ -3,6 +3,7 @@ require 'jade/codegen/context'
|
|
|
3
3
|
require 'jade/codegen/helpers'
|
|
4
4
|
require 'jade/codegen/pretty'
|
|
5
5
|
require 'jade/codegen/method_names'
|
|
6
|
+
require 'jade/codegen/names'
|
|
6
7
|
require 'jade/codegen/inlines'
|
|
7
8
|
require 'jade/codegen/inline'
|
|
8
9
|
require 'jade/codegen/boundary'
|
|
@@ -83,10 +84,13 @@ module Jade
|
|
|
83
84
|
def generate(node, registry, depth: 0)
|
|
84
85
|
case node
|
|
85
86
|
in AST::Module(name:, body:)
|
|
87
|
+
reopened = reopened_type(name, registry)
|
|
88
|
+
|
|
86
89
|
preamble = [
|
|
87
90
|
"require 'jade/runtime'",
|
|
88
91
|
*Stdlib.requires(name),
|
|
89
92
|
*namespace_setup_lines(name),
|
|
93
|
+
*reopening_require(reopened, name),
|
|
90
94
|
].reject(&:empty?).join(Pretty.newline)
|
|
91
95
|
|
|
92
96
|
shape_consts = collect_record_shapes(body)
|
|
@@ -100,11 +104,15 @@ module Jade
|
|
|
100
104
|
boundary_helpers = Boundary::Specialized.collect_helpers(body, registry)
|
|
101
105
|
.then { Boundary::Specialized.emit_helpers(it, registry) }
|
|
102
106
|
|
|
107
|
+
dict_consts = {}
|
|
108
|
+
|
|
103
109
|
outer, inner, wrappers =
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
with_dict_consts(dict_consts) do
|
|
111
|
+
with_boundary_cache(boundary_cache) do
|
|
112
|
+
with_dispatched_methods(collect_dispatched_methods(body, registry)) do
|
|
113
|
+
with_hoisted_records do
|
|
114
|
+
partition_module_body(body.expressions, registry, name.count('.'))
|
|
115
|
+
end
|
|
108
116
|
end
|
|
109
117
|
end
|
|
110
118
|
end
|
|
@@ -114,17 +122,18 @@ module Jade
|
|
|
114
122
|
.then { Pretty.block("module Internal", it) }
|
|
115
123
|
|
|
116
124
|
[
|
|
117
|
-
"extend self",
|
|
125
|
+
(reopened ? "" : "extend self"),
|
|
118
126
|
*shape_consts,
|
|
119
127
|
*outer,
|
|
120
128
|
inner_module,
|
|
129
|
+
*referenced_dicts(dict_consts, [*outer, *inner, *wrappers]),
|
|
121
130
|
*boundary_consts,
|
|
122
131
|
*boundary_helpers,
|
|
123
132
|
*wrappers,
|
|
124
133
|
]
|
|
125
134
|
.reject(&:empty?)
|
|
126
135
|
.join(Pretty.newline(2))
|
|
127
|
-
.then { Pretty.block("module #{to_qualified(name)}", it) }
|
|
136
|
+
.then { Pretty.block("#{reopened ? 'class' : 'module'} #{to_qualified(name)}", it) }
|
|
128
137
|
.then { [preamble, it].reject(&:empty?).join(Pretty.newline(2)) }
|
|
129
138
|
|
|
130
139
|
in AST::ImportDeclaration(module_name:)
|
|
@@ -138,7 +147,11 @@ module Jade
|
|
|
138
147
|
end
|
|
139
148
|
|
|
140
149
|
in AST::InteropImportDeclaration(module: mod)
|
|
141
|
-
Pretty.block(
|
|
150
|
+
Pretty.block(
|
|
151
|
+
"begin",
|
|
152
|
+
"require '#{mod.name.gsub('::', '/').downcase}'",
|
|
153
|
+
"rescue LoadError\nend",
|
|
154
|
+
)
|
|
142
155
|
|
|
143
156
|
in AST::Implementation
|
|
144
157
|
Implementation.generate(node, registry)
|
|
@@ -166,17 +179,17 @@ module Jade
|
|
|
166
179
|
codegen
|
|
167
180
|
|
|
168
181
|
in Symbol::Function => fn
|
|
169
|
-
"#{
|
|
182
|
+
"#{internal(fn.module_name)}.method(:#{fn.name})"
|
|
170
183
|
|
|
171
184
|
else
|
|
172
|
-
name
|
|
185
|
+
Names.local(name)
|
|
173
186
|
end
|
|
174
187
|
end
|
|
175
188
|
|
|
176
189
|
in AST::Assign(pattern:, expression:)
|
|
177
190
|
case pattern
|
|
178
191
|
in AST::Pattern::Binding(name:)
|
|
179
|
-
"#{name} = #{generate(expression, registry)}"
|
|
192
|
+
"#{Names.local(name)} = #{generate(expression, registry)}"
|
|
180
193
|
in AST::Pattern::Wildcard
|
|
181
194
|
generate(expression, registry)
|
|
182
195
|
else
|
|
@@ -193,7 +206,7 @@ module Jade
|
|
|
193
206
|
FunctionDeclaration.generate(node, registry)
|
|
194
207
|
|
|
195
208
|
in AST::FunctionDeclarationParam(name:)
|
|
196
|
-
name
|
|
209
|
+
Names.local(name)
|
|
197
210
|
|
|
198
211
|
in AST::FunctionCall
|
|
199
212
|
FunctionCall.generate(node, registry)
|
|
@@ -209,6 +222,9 @@ module Jade
|
|
|
209
222
|
in AST::StructDeclaration(name:, record_type:, symbol:)
|
|
210
223
|
data_define_with_methods(name, record_type.fields.keys, "::#{to_qualified(symbol.qualified_name)}")
|
|
211
224
|
|
|
225
|
+
in AST::TypeAliasDeclaration
|
|
226
|
+
""
|
|
227
|
+
|
|
212
228
|
in AST::InterfaceDeclaration
|
|
213
229
|
""
|
|
214
230
|
|
|
@@ -221,7 +237,7 @@ module Jade
|
|
|
221
237
|
codegen
|
|
222
238
|
|
|
223
239
|
in Symbol::Function => fn
|
|
224
|
-
"#{
|
|
240
|
+
"#{internal(fn.module_name)}.method(:#{fn.name})"
|
|
225
241
|
|
|
226
242
|
in Symbol::Constructor => sym
|
|
227
243
|
ConstructorReference.from_symbol(sym)
|
|
@@ -254,11 +270,14 @@ module Jade
|
|
|
254
270
|
in AST::Pattern::Literal(literal:)
|
|
255
271
|
generate(literal, registry)
|
|
256
272
|
|
|
273
|
+
in AST::Pattern::Range(from:, to:)
|
|
274
|
+
"#{from&.value}..#{to&.value}"
|
|
275
|
+
|
|
257
276
|
in AST::Pattern::Wildcard
|
|
258
277
|
"_"
|
|
259
278
|
|
|
260
279
|
in AST::Pattern::Binding(name:)
|
|
261
|
-
name
|
|
280
|
+
Names.local(name)
|
|
262
281
|
|
|
263
282
|
in AST::Pattern::Record(fields:)
|
|
264
283
|
generate_many(fields, registry)
|
|
@@ -270,7 +289,7 @@ module Jade
|
|
|
270
289
|
in AST::Pattern::List(patterns:, rest:)
|
|
271
290
|
rest_part =
|
|
272
291
|
case rest
|
|
273
|
-
in AST::Pattern::Binding(name:) then ["*#{name}"]
|
|
292
|
+
in AST::Pattern::Binding(name:) then ["*#{Names.local(name)}"]
|
|
274
293
|
in AST::Pattern::Wildcard then ["*"]
|
|
275
294
|
in nil then []
|
|
276
295
|
end
|
|
@@ -325,7 +344,7 @@ module Jade
|
|
|
325
344
|
|
|
326
345
|
def param_name(pattern, index = 0)
|
|
327
346
|
case pattern
|
|
328
|
-
in AST::Pattern::Binding(name:) then name
|
|
347
|
+
in AST::Pattern::Binding(name:) then Names.local(name)
|
|
329
348
|
in AST::Pattern::Wildcard then '_'
|
|
330
349
|
else
|
|
331
350
|
param_synthetic_name(index)
|
|
@@ -340,6 +359,48 @@ module Jade
|
|
|
340
359
|
"#{'../' * current_depth}#{import_path}"
|
|
341
360
|
end
|
|
342
361
|
|
|
362
|
+
# Emission generates some expressions only to discard them (a decoder
|
|
363
|
+
# weighed against its specialized form, say), and a discarded one can
|
|
364
|
+
# still have claimed a constant. Keep the ones that survived, and the
|
|
365
|
+
# ones those refer to.
|
|
366
|
+
def referenced_dicts(table, body)
|
|
367
|
+
reachable_dicts(table, body.join(Pretty.newline))
|
|
368
|
+
.map { |source, const| "#{const} = #{source}.freeze" }
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# A kept dictionary can name another one, so widen the search text
|
|
372
|
+
# with what was kept until nothing new turns up.
|
|
373
|
+
def reachable_dicts(table, code, kept = {})
|
|
374
|
+
table
|
|
375
|
+
.select { |_, const| code.match?(/\b#{const}\b/) }
|
|
376
|
+
.then do |found|
|
|
377
|
+
next found if found.size == kept.size
|
|
378
|
+
|
|
379
|
+
reachable_dicts(table, [code, *found.keys].join(Pretty.newline), found)
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# A module whose name is already a type in its parent shares one Ruby
|
|
384
|
+
# constant with it, so it reopens that class rather than defining a
|
|
385
|
+
# module beside it and clobbering whichever loads second.
|
|
386
|
+
def reopened_type(name, registry)
|
|
387
|
+
*parent, last = name.split('.')
|
|
388
|
+
return nil if parent.empty?
|
|
389
|
+
|
|
390
|
+
registry
|
|
391
|
+
.get(parent.join('.'))
|
|
392
|
+
&.then { it.defined_types.key?(last) ? it : nil }
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Before the class body, not inside it: an import writes its require
|
|
396
|
+
# within the body, by which point `class` has already made a plain
|
|
397
|
+
# class for the constant and the type will clobber it on load.
|
|
398
|
+
def reopening_require(entry, name)
|
|
399
|
+
return [] if entry.nil? || Stdlib.is_stdlib?(entry)
|
|
400
|
+
|
|
401
|
+
["require_relative '#{relative_require(entry.path, name.count('.'))}'"]
|
|
402
|
+
end
|
|
403
|
+
|
|
343
404
|
def namespace_setup_lines(name)
|
|
344
405
|
parts = name.split('.')
|
|
345
406
|
(0...parts.length - 1)
|
data/lib/jade/compiler.rb
CHANGED
|
@@ -7,6 +7,7 @@ module Jade
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def require(path)
|
|
10
|
+
ModuleLoader::Build.discard_foreign(build_root)
|
|
10
11
|
target = File.expand_path("#{build_root}/#{path}.rb", config.project_root)
|
|
11
12
|
|
|
12
13
|
if needs_rebuild?(target)
|
|
@@ -14,6 +15,7 @@ module Jade
|
|
|
14
15
|
.load(config.source_root, path + '.jd', cache_dir: cache_root)
|
|
15
16
|
.tap { render_diagnostics(it) }
|
|
16
17
|
.then { ModuleLoader.emit(it, path: build_root) }
|
|
18
|
+
.then { ModuleLoader::Build.stamp(build_root) }
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
Kernel.require(File.realpath(target))
|
data/lib/jade/entry.rb
CHANGED
data/lib/jade/formatter/calls.rb
CHANGED
|
@@ -12,7 +12,10 @@ module Jade
|
|
|
12
12
|
|
|
13
13
|
callee_str = format_node(callee, source:)
|
|
14
14
|
args_strs = args.map { format_node(it, source:) }
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
Calls::Call[callee_str, args_strs, indent].then do |call|
|
|
17
|
+
Calls.render(call, trailing_comma, opened_last: Calls.render_open(args.last, source))
|
|
18
|
+
end
|
|
16
19
|
end
|
|
17
20
|
end
|
|
18
21
|
|
|
@@ -25,7 +28,10 @@ module Jade
|
|
|
25
28
|
|
|
26
29
|
callee_str = format_node(callee, source:)
|
|
27
30
|
field_strs = fields.map { "#{it.key}: #{format_node(it.value, source:)}" }
|
|
28
|
-
|
|
31
|
+
|
|
32
|
+
Calls::Call[callee_str, field_strs, indent].then do |call|
|
|
33
|
+
Calls.render(call, trailing_comma, opened_last: Calls.render_open_field(fields.last, source))
|
|
34
|
+
end
|
|
29
35
|
end
|
|
30
36
|
end
|
|
31
37
|
|
|
@@ -33,19 +39,70 @@ module Jade
|
|
|
33
39
|
extend self
|
|
34
40
|
extend Helper
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
# A trailing block-shaped argument reads as the call's body, the way a
|
|
43
|
+
# Ruby block does. Only these three grow a body worth hugging.
|
|
44
|
+
HUGGABLE = [AST::Lambda, AST::List, AST::RecordLiteral].freeze
|
|
45
|
+
|
|
46
|
+
# A rendered call, before the decision of how to lay it out: the callee,
|
|
47
|
+
# its already-formatted arguments, and the column they sit at.
|
|
48
|
+
Call = Data.define(:callee, :items, :indent)
|
|
49
|
+
|
|
50
|
+
def huggable?(node)
|
|
51
|
+
HUGGABLE.any? { node.is_a?(it) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def render_open(node, source)
|
|
55
|
+
return nil unless huggable?(node)
|
|
56
|
+
|
|
57
|
+
dispatch_for(node).format(node, indent: 0, source:, open: true)
|
|
58
|
+
end
|
|
38
59
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.join("\n")
|
|
60
|
+
def render_open_field(field, source)
|
|
61
|
+
render_open(field&.value, source)&.then { "#{field.key}: #{it}" }
|
|
62
|
+
end
|
|
43
63
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
64
|
+
def render(call, trailing_comma, opened_last: nil)
|
|
65
|
+
case [trailing_comma, inline(call), opened_last]
|
|
66
|
+
in [false, String => fits, _] then fits
|
|
67
|
+
in [false, nil, String => last] then hug_last(call, last) || explode(call)
|
|
68
|
+
else explode(call)
|
|
47
69
|
end
|
|
48
70
|
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def inline(call)
|
|
75
|
+
"#{call.callee}(#{call.items.join(', ')})"
|
|
76
|
+
.then { too_long?(it, call.indent) ? nil : it.then(&and_indent(call.indent)) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# An earlier multi-line argument would leave the head straddling lines.
|
|
80
|
+
def hug_last(call, last)
|
|
81
|
+
case [call.items[..-2], last.split("\n")]
|
|
82
|
+
in [[*head], [_]] then nil
|
|
83
|
+
in [[*head], _] if head.any? { it.include?("\n") } then nil
|
|
84
|
+
in [[*head], [opener, *rest]]
|
|
85
|
+
"#{call.callee}(#{(head + [opener]).join(', ')}"
|
|
86
|
+
.then { too_long?(it, call.indent) ? nil : hug_lines(it, rest, call.indent) }
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def hug_lines(opening, rest, indent)
|
|
91
|
+
[opening, *rest[..-2], "#{rest.last})"]
|
|
92
|
+
.join("\n")
|
|
93
|
+
.then(&and_indent(indent))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def explode(call)
|
|
97
|
+
call.items
|
|
98
|
+
.map { "#{it.then(&and_indent(call.indent + 1))}," }
|
|
99
|
+
.join("\n")
|
|
100
|
+
.then { "#{indent_callee(call)}(\n#{it}\n#{INDENT * call.indent})" }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def indent_callee(call)
|
|
104
|
+
call.callee.then(&and_indent(call.indent))
|
|
105
|
+
end
|
|
49
106
|
end
|
|
50
107
|
end
|
|
51
108
|
end
|
|
@@ -22,12 +22,12 @@ module Jade
|
|
|
22
22
|
extend self
|
|
23
23
|
extend Helper
|
|
24
24
|
|
|
25
|
-
def format(node, indent:, source:)
|
|
25
|
+
def format(node, indent:, source:, open: false)
|
|
26
26
|
node => AST::List(items:, trailing_comma:)
|
|
27
27
|
|
|
28
28
|
format_delimited(
|
|
29
29
|
items.map { format_node(it, source:) },
|
|
30
|
-
'[', ']', trailing_comma, indent,
|
|
30
|
+
'[', ']', trailing_comma || open, indent,
|
|
31
31
|
)
|
|
32
32
|
end
|
|
33
33
|
end
|
|
@@ -36,13 +36,13 @@ module Jade
|
|
|
36
36
|
extend self
|
|
37
37
|
extend Helper
|
|
38
38
|
|
|
39
|
-
def format(node, indent:, source:)
|
|
39
|
+
def format(node, indent:, source:, open: false)
|
|
40
40
|
node => AST::RecordLiteral(fields:, trailing_comma:)
|
|
41
41
|
|
|
42
42
|
field_strs = fields.map { "#{it.key}: #{format_node(it.value, source:)}" }
|
|
43
43
|
inline = "{ #{field_strs.join(', ')} }"
|
|
44
44
|
|
|
45
|
-
if trailing_comma || too_long?(inline, indent)
|
|
45
|
+
if trailing_comma || open || too_long?(inline, indent)
|
|
46
46
|
inner = field_strs
|
|
47
47
|
.map { "#{it.then(&and_indent(indent + 1))}," }
|
|
48
48
|
.join("\n")
|
|
@@ -68,24 +68,22 @@ module Jade
|
|
|
68
68
|
"(#{type_params.map(&:name).join(', ')})"
|
|
69
69
|
header = "struct #{name}#{params_str} ="
|
|
70
70
|
|
|
71
|
-
record_type
|
|
72
|
-
|
|
73
|
-
if fields.size > 1
|
|
74
|
-
format_multiline(header, fields, row_var, indent)
|
|
75
|
-
else
|
|
76
|
-
"#{header} #{format_type(record_type)}".then(&and_indent(indent))
|
|
77
|
-
end
|
|
71
|
+
format_record_declaration(header, record_type, indent)
|
|
78
72
|
end
|
|
73
|
+
end
|
|
79
74
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
module TypeAliasDeclaration
|
|
76
|
+
extend self
|
|
77
|
+
extend Helper
|
|
78
|
+
|
|
79
|
+
def format(node, indent:, source:)
|
|
80
|
+
node => AST::TypeAliasDeclaration(name:, type_params:, body_type:)
|
|
81
|
+
|
|
82
|
+
params_str = type_params.empty? ?
|
|
83
|
+
"" :
|
|
84
|
+
"(#{type_params.map(&:name).join(', ')})"
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
.call("#{header} #{open_brace}")
|
|
88
|
-
.then { "#{it}\n#{fields_str}\n#{INDENT * indent}}" }
|
|
86
|
+
format_record_declaration("type alias #{name}#{params_str} =", body_type, indent)
|
|
89
87
|
end
|
|
90
88
|
end
|
|
91
89
|
|
|
@@ -30,6 +30,7 @@ module Jade
|
|
|
30
30
|
in AST::TypeDeclaration then TypeDeclaration
|
|
31
31
|
in AST::VariantDeclaration then VariantDeclaration
|
|
32
32
|
in AST::StructDeclaration then StructDeclaration
|
|
33
|
+
in AST::TypeAliasDeclaration then TypeAliasDeclaration
|
|
33
34
|
in AST::ImportDeclaration then ImportDeclaration
|
|
34
35
|
in AST::InteropImportDeclaration then InteropImportDeclaration
|
|
35
36
|
in AST::InteropFunction then InteropFunction
|
|
@@ -113,6 +114,25 @@ module Jade
|
|
|
113
114
|
Pattern.format(node, source:)
|
|
114
115
|
end
|
|
115
116
|
|
|
117
|
+
# A record body past one field goes multi-line, so a `type alias` over a
|
|
118
|
+
# record breaks the same way as the `struct` beside it.
|
|
119
|
+
def format_record_declaration(header, body, indent)
|
|
120
|
+
case body
|
|
121
|
+
in AST::TypeRecord(fields:, row_var:) if fields.size > 1
|
|
122
|
+
open_brace = row_var ? "{ #{row_var.name} |" : "{"
|
|
123
|
+
fields_str = fields
|
|
124
|
+
.map { |k, v| "#{k}: #{format_type(v)}".then(&and_indent(indent + 1)) }
|
|
125
|
+
.join(",\n")
|
|
126
|
+
|
|
127
|
+
and_indent(indent)
|
|
128
|
+
.call("#{header} #{open_brace}")
|
|
129
|
+
.then { "#{it}\n#{fields_str}\n#{INDENT * indent}}" }
|
|
130
|
+
|
|
131
|
+
else
|
|
132
|
+
"#{header} #{format_type(body)}".then(&and_indent(indent))
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
116
136
|
def format_type(node)
|
|
117
137
|
Type.format(node)
|
|
118
138
|
end
|
|
@@ -12,6 +12,11 @@ module Jade
|
|
|
12
12
|
format_pipe_chain(node, indent, source:)
|
|
13
13
|
when '++'
|
|
14
14
|
format_concat_chain(node, indent, source:)
|
|
15
|
+
# Spelled tight, the way it is in a pattern. `0 .. n` is nobody's
|
|
16
|
+
# idea of a range.
|
|
17
|
+
when '..'
|
|
18
|
+
"#{format_node(left, source:)}..#{format_node(right, source:)}"
|
|
19
|
+
.then(&and_indent(indent))
|
|
15
20
|
else
|
|
16
21
|
"#{format_node(left, source:)} #{operator.value} #{format_node(right, source:)}"
|
|
17
22
|
.then(&and_indent(indent))
|
|
@@ -58,11 +63,21 @@ module Jade
|
|
|
58
63
|
# Emit a chain ladder: head on its own line, each subsequent
|
|
59
64
|
# operand prefixed by `op` indented one level deeper.
|
|
60
65
|
def format_ladder(chain, op, indent, source:)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
chain[1..]
|
|
67
|
+
.map { format_node(it, source:) }
|
|
68
|
+
.map { continued(it, indent + 1) }
|
|
69
|
+
.map { "#{INDENT * (indent + 1)}#{op} #{it}" }
|
|
70
|
+
.then { [format_node(chain.first, indent:, source:), *it] }
|
|
71
|
+
.join("\n")
|
|
72
|
+
end
|
|
64
73
|
|
|
65
|
-
|
|
74
|
+
# What follows an operand's first line belongs under the operator that
|
|
75
|
+
# introduced it, not at column zero.
|
|
76
|
+
def continued(operand, indent)
|
|
77
|
+
operand
|
|
78
|
+
.split("\n")
|
|
79
|
+
.then { |(first, *rest)| [first, *rest.map { "#{INDENT * indent}#{it}" }] }
|
|
80
|
+
.join("\n")
|
|
66
81
|
end
|
|
67
82
|
end
|
|
68
83
|
end
|
|
@@ -15,23 +15,35 @@ module Jade
|
|
|
15
15
|
AST::RecordUpdateSugar, AST::RecordAccessSugar,
|
|
16
16
|
].freeze
|
|
17
17
|
|
|
18
|
-
def format(node, indent:, source:)
|
|
18
|
+
def format(node, indent:, source:, open: false)
|
|
19
19
|
node => AST::Lambda(params:, body:)
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if inline_body?(body)
|
|
24
|
-
"#{head} { #{format_node(body.expressions.first, source:)} }"
|
|
25
|
-
.then(&and_indent(indent))
|
|
26
|
-
else
|
|
27
|
-
[
|
|
28
|
-
"#{head} {".then(&and_indent(indent)),
|
|
29
|
-
format_node(body, indent: indent + 1, source:),
|
|
30
|
-
"}".then(&and_indent(indent)),
|
|
31
|
-
].join("\n")
|
|
21
|
+
format_head(params).then do |head|
|
|
22
|
+
(inline(head, body, indent, source) unless open) || block(head, body, indent, source)
|
|
32
23
|
end
|
|
33
24
|
end
|
|
34
25
|
|
|
26
|
+
# An inline body that broke anyway leaves `-> { Expect.equal(` hanging.
|
|
27
|
+
def inline(head, body, indent, source)
|
|
28
|
+
return nil unless inline_body?(body)
|
|
29
|
+
|
|
30
|
+
body.expressions.first
|
|
31
|
+
.then { "#{head} { #{format_node(it, source:)} }" }
|
|
32
|
+
.then { fits?(it, indent) ? it.then(&and_indent(indent)) : nil }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def fits?(line, indent)
|
|
36
|
+
!line.include?("\n") && !too_long?(line, indent)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def block(head, body, indent, source)
|
|
40
|
+
[
|
|
41
|
+
"#{head} {".then(&and_indent(indent)),
|
|
42
|
+
format_node(body, indent: indent + 1, source:),
|
|
43
|
+
"}".then(&and_indent(indent)),
|
|
44
|
+
].join("\n")
|
|
45
|
+
end
|
|
46
|
+
|
|
35
47
|
def format_head(params)
|
|
36
48
|
return "->" if params.empty?
|
|
37
49
|
|