semantifold 0.1.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.
- package/LICENSE +15 -0
- package/README.md +71 -0
- package/build/index.d.ts +54 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +45 -0
- package/build/src/backends/identifiers.d.ts +19 -0
- package/build/src/backends/identifiers.d.ts.map +1 -0
- package/build/src/backends/identifiers.js +93 -0
- package/build/src/backends/index.d.ts +35 -0
- package/build/src/backends/index.d.ts.map +1 -0
- package/build/src/backends/index.js +149 -0
- package/build/src/backends/java.d.ts +8 -0
- package/build/src/backends/java.d.ts.map +1 -0
- package/build/src/backends/java.js +172 -0
- package/build/src/backends/javascript.d.ts +8 -0
- package/build/src/backends/javascript.d.ts.map +1 -0
- package/build/src/backends/javascript.js +173 -0
- package/build/src/backends/php.d.ts +8 -0
- package/build/src/backends/php.d.ts.map +1 -0
- package/build/src/backends/php.js +175 -0
- package/build/src/backends/ruby.d.ts +8 -0
- package/build/src/backends/ruby.d.ts.map +1 -0
- package/build/src/backends/ruby.js +155 -0
- package/build/src/backends/scalars.d.ts +15 -0
- package/build/src/backends/scalars.d.ts.map +1 -0
- package/build/src/backends/scalars.js +138 -0
- package/build/src/backends/shared.d.ts +26 -0
- package/build/src/backends/shared.d.ts.map +1 -0
- package/build/src/backends/shared.js +424 -0
- package/build/src/backends/typescript.d.ts +8 -0
- package/build/src/backends/typescript.d.ts.map +1 -0
- package/build/src/backends/typescript.js +160 -0
- package/build/src/backends/writer.d.ts +87 -0
- package/build/src/backends/writer.d.ts.map +1 -0
- package/build/src/backends/writer.js +207 -0
- package/build/src/diagnostic.d.ts +67 -0
- package/build/src/diagnostic.d.ts.map +1 -0
- package/build/src/diagnostic.js +96 -0
- package/build/src/frontends/index.d.ts +14 -0
- package/build/src/frontends/index.d.ts.map +1 -0
- package/build/src/frontends/index.js +36 -0
- package/build/src/frontends/java.d.ts +12 -0
- package/build/src/frontends/java.d.ts.map +1 -0
- package/build/src/frontends/java.js +630 -0
- package/build/src/frontends/javascript-typescript.d.ts +19 -0
- package/build/src/frontends/javascript-typescript.d.ts.map +1 -0
- package/build/src/frontends/javascript-typescript.js +633 -0
- package/build/src/frontends/php.d.ts +12 -0
- package/build/src/frontends/php.d.ts.map +1 -0
- package/build/src/frontends/php.js +523 -0
- package/build/src/frontends/ruby.d.ts +12 -0
- package/build/src/frontends/ruby.d.ts.map +1 -0
- package/build/src/frontends/ruby.js +515 -0
- package/build/src/frontends/scalars.d.ts +19 -0
- package/build/src/frontends/scalars.d.ts.map +1 -0
- package/build/src/frontends/scalars.js +71 -0
- package/build/src/mapping.d.ts +160 -0
- package/build/src/mapping.d.ts.map +1 -0
- package/build/src/mapping.js +1120 -0
- package/build/src/semantic/location.d.ts +61 -0
- package/build/src/semantic/location.d.ts.map +1 -0
- package/build/src/semantic/location.js +142 -0
- package/build/src/semantic/operators.d.ts +16 -0
- package/build/src/semantic/operators.d.ts.map +1 -0
- package/build/src/semantic/operators.js +24 -0
- package/build/src/semantic/provenance.d.ts +74 -0
- package/build/src/semantic/provenance.d.ts.map +1 -0
- package/build/src/semantic/provenance.js +577 -0
- package/build/src/semantic/scalars.d.ts +19 -0
- package/build/src/semantic/scalars.d.ts.map +1 -0
- package/build/src/semantic/scalars.js +40 -0
- package/build/src/semantic/types.d.ts +1003 -0
- package/build/src/semantic/types.d.ts.map +1 -0
- package/build/src/semantic/types.js +279 -0
- package/build/src/semantic/validate.d.ts +60 -0
- package/build/src/semantic/validate.d.ts.map +1 -0
- package/build/src/semantic/validate.js +472 -0
- package/docs/architecture.md +39 -0
- package/docs/coding-standards.md +7 -0
- package/docs/goals.md +7 -0
- package/docs/language-support.md +69 -0
- package/docs/plans/2026-09-02-initial-toolchain.md +54 -0
- package/docs/source-maps.md +134 -0
- package/docs/testing.md +25 -0
- package/index.js +63 -0
- package/package.json +69 -0
- package/src/backends/identifiers.js +101 -0
- package/src/backends/index.js +159 -0
- package/src/backends/java.js +182 -0
- package/src/backends/javascript.js +184 -0
- package/src/backends/php.js +184 -0
- package/src/backends/ruby.js +163 -0
- package/src/backends/scalars.js +120 -0
- package/src/backends/shared.js +456 -0
- package/src/backends/typescript.js +168 -0
- package/src/backends/writer.js +224 -0
- package/src/diagnostic.js +103 -0
- package/src/frontends/index.js +36 -0
- package/src/frontends/java.js +735 -0
- package/src/frontends/javascript-typescript.js +718 -0
- package/src/frontends/php.js +593 -0
- package/src/frontends/ruby.js +622 -0
- package/src/frontends/scalars.js +77 -0
- package/src/mapping.js +1260 -0
- package/src/semantic/location.js +154 -0
- package/src/semantic/operators.js +28 -0
- package/src/semantic/provenance.js +629 -0
- package/src/semantic/scalars.js +44 -0
- package/src/semantic/types.js +319 -0
- package/src/semantic/validate.js +529 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {parser} from "@lezer/java"
|
|
4
|
+
import {SemantifoldDiagnostic, missingType, unsupportedSyntax} from "../diagnostic.js"
|
|
5
|
+
import {locationFromOffsets, moduleLocation} from "../semantic/location.js"
|
|
6
|
+
import {withAdaptedOperation} from "../semantic/operators.js"
|
|
7
|
+
import {withParserRanges} from "../semantic/provenance.js"
|
|
8
|
+
import {hasOnlyUnicodeScalars} from "../semantic/scalars.js"
|
|
9
|
+
import {sourceScalarType} from "./scalars.js"
|
|
10
|
+
|
|
11
|
+
/** @type {Readonly<Record<string, string>>} */
|
|
12
|
+
const simpleStringEscapes = Object.freeze({
|
|
13
|
+
"\"": "\"",
|
|
14
|
+
"'": "'",
|
|
15
|
+
"\\": "\\",
|
|
16
|
+
b: "\b",
|
|
17
|
+
f: "\f",
|
|
18
|
+
n: "\n",
|
|
19
|
+
r: "\r",
|
|
20
|
+
s: " ",
|
|
21
|
+
t: "\t"
|
|
22
|
+
})
|
|
23
|
+
const javaBinaryOperations = new Map([
|
|
24
|
+
["+", "Add"],
|
|
25
|
+
["-", "Subtract"],
|
|
26
|
+
["*", "Multiply"],
|
|
27
|
+
["&&", "And"],
|
|
28
|
+
["||", "Or"],
|
|
29
|
+
["==", "JavaEqual"],
|
|
30
|
+
["!=", "JavaNotEqual"],
|
|
31
|
+
["<", "LessThan"],
|
|
32
|
+
["<=", "LessThanOrEqual"],
|
|
33
|
+
[">", "GreaterThan"],
|
|
34
|
+
[">=", "GreaterThanOrEqual"]
|
|
35
|
+
])
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns all direct child syntax nodes.
|
|
39
|
+
* @param {import("@lezer/common").SyntaxNode} node - Parent syntax node.
|
|
40
|
+
* @returns {import("@lezer/common").SyntaxNode[]} Child syntax nodes.
|
|
41
|
+
*/
|
|
42
|
+
function directChildren(node) {
|
|
43
|
+
const children = []
|
|
44
|
+
|
|
45
|
+
for (let child = node.firstChild; child; child = child.nextSibling) children.push(child)
|
|
46
|
+
|
|
47
|
+
return children
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Returns structural direct children, excluding known grammar delimiters.
|
|
52
|
+
* @param {import("@lezer/common").SyntaxNode} node - Parent syntax node.
|
|
53
|
+
* @returns {import("@lezer/common").SyntaxNode[]} Named children.
|
|
54
|
+
*/
|
|
55
|
+
function structuralChildren(node) {
|
|
56
|
+
return directChildren(node).filter((child) => !["{", "}", "(", ")", ",", ";", "."].includes(child.name))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Finds all descendant syntax nodes with a given name.
|
|
61
|
+
* @param {import("@lezer/common").SyntaxNode} node - Root syntax node.
|
|
62
|
+
* @param {string} name - Node name.
|
|
63
|
+
* @returns {import("@lezer/common").SyntaxNode[]} Matching descendants.
|
|
64
|
+
*/
|
|
65
|
+
function descendants(node, name) {
|
|
66
|
+
const matches = []
|
|
67
|
+
|
|
68
|
+
for (const child of directChildren(node)) {
|
|
69
|
+
if (child.name == name) matches.push(child)
|
|
70
|
+
matches.push(...descendants(child, name))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return matches
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Requires one direct child node.
|
|
78
|
+
* @param {import("@lezer/common").SyntaxNode} node - Parent node.
|
|
79
|
+
* @param {string} name - Child name.
|
|
80
|
+
* @param {string} filename - Source filename.
|
|
81
|
+
* @param {string} source - Complete source.
|
|
82
|
+
* @returns {import("@lezer/common").SyntaxNode} Child node.
|
|
83
|
+
*/
|
|
84
|
+
function requiredChild(node, name, filename, source) {
|
|
85
|
+
const child = node.getChild(name)
|
|
86
|
+
|
|
87
|
+
if (!child) return unsupportedSyntax("java", `${node.name} without ${name}`, nodeLocation(node, filename, source))
|
|
88
|
+
|
|
89
|
+
return child
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Returns a normalized Lezer node location.
|
|
94
|
+
* @param {import("@lezer/common").SyntaxNode} node - Lezer node.
|
|
95
|
+
* @param {string} filename - Source filename.
|
|
96
|
+
* @param {string} source - Complete source.
|
|
97
|
+
* @returns {import("../semantic/types.js").SourceLocation} Source location.
|
|
98
|
+
*/
|
|
99
|
+
function nodeLocation(node, filename, source) {
|
|
100
|
+
return locationFromOffsets(filename, source, node.from, node.to)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Returns source text belonging to a syntax node.
|
|
105
|
+
* @param {import("@lezer/common").SyntaxNode} node - Lezer node.
|
|
106
|
+
* @param {string} source - Complete source.
|
|
107
|
+
* @returns {string} Node text.
|
|
108
|
+
*/
|
|
109
|
+
function nodeText(node, source) {
|
|
110
|
+
return source.slice(node.from, node.to)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Converts a supported Java expression node.
|
|
115
|
+
* @param {import("@lezer/common").SyntaxNode} node - Lezer expression.
|
|
116
|
+
* @param {string} filename - Source filename.
|
|
117
|
+
* @param {string} source - Complete source.
|
|
118
|
+
* @returns {import("../semantic/types.js").Expression} Semantic expression.
|
|
119
|
+
*/
|
|
120
|
+
function convertExpression(node, filename, source) {
|
|
121
|
+
const location = nodeLocation(node, filename, source)
|
|
122
|
+
|
|
123
|
+
if (node.name == "ParenthesizedExpression") {
|
|
124
|
+
const children = structuralChildren(node)
|
|
125
|
+
|
|
126
|
+
if (children.length != 1) {
|
|
127
|
+
return unsupportedSyntax("java", "unsupported parenthesized expression", location)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return convertExpression(children[0], filename, source)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (node.name == "Identifier") {
|
|
134
|
+
return withParserRanges({kind: /** @type {const} */ ("IdentifierExpression"), location, name: nodeText(node, source)}, {name: location})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (node.name == "IntegerLiteral") {
|
|
138
|
+
const value = Number(nodeText(node, source))
|
|
139
|
+
|
|
140
|
+
if (!Number.isSafeInteger(value)) return unsupportedSyntax("java", "non-safe integer literal", location)
|
|
141
|
+
|
|
142
|
+
return withParserRanges({kind: /** @type {const} */ ("IntegerLiteral"), location, value}, {literal: location})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (node.name == "BooleanLiteral") {
|
|
146
|
+
return withParserRanges({kind: /** @type {const} */ ("BooleanLiteral"), location, value: nodeText(node, source) == "true"}, {literal: location})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (node.name == "StringLiteral") {
|
|
150
|
+
return withParserRanges({kind: /** @type {const} */ ("StringLiteral"), location, value: decodeStringLiteral(node, filename, source)}, {literal: location})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (node.name == "UnaryExpression") {
|
|
154
|
+
const children = structuralChildren(node)
|
|
155
|
+
const operatorNode = node.getChild("LogicOp") ?? node.getChild("ArithOp")
|
|
156
|
+
const operand = children.find((child) => child.name != "LogicOp" && child.name != "ArithOp")
|
|
157
|
+
const operator = operatorNode ? nodeText(operatorNode, source) : undefined
|
|
158
|
+
|
|
159
|
+
if (!operatorNode || !operand || children.length != 2 || (operator != "!" && operator != "-")) {
|
|
160
|
+
return unsupportedSyntax("java", "unary expression", location)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (operator == "!" && operand.name == "MethodInvocation" && isEqualsInvocation(operand, source)) {
|
|
164
|
+
return convertStringEquality(operand, true, location, nodeLocation(operatorNode, filename, source), filename, source)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const semantic = withAdaptedOperation(withParserRanges({
|
|
168
|
+
kind: "UnaryExpression",
|
|
169
|
+
location,
|
|
170
|
+
operand: convertExpression(operand, filename, source)
|
|
171
|
+
}, {operator: nodeLocation(operatorNode, filename, source)}), operator == "!" ? "Not" : "Negate")
|
|
172
|
+
|
|
173
|
+
return /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (semantic))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (node.name == "BinaryExpression") {
|
|
177
|
+
const named = structuralChildren(node)
|
|
178
|
+
const operatorNode = node.getChild("CompareOp") ?? node.getChild("ArithOp") ?? node.getChild("LogicOp") ?? node.getChild("BitOp")
|
|
179
|
+
|
|
180
|
+
if (!operatorNode || named.length != 3) return unsupportedSyntax("java", "binary expression", location)
|
|
181
|
+
|
|
182
|
+
const operands = [named[0], named[2]]
|
|
183
|
+
const unsupportedOperand = operands.find((operand) => !isSupportedExpressionNode(operand))
|
|
184
|
+
|
|
185
|
+
if (unsupportedOperand) return unsupportedSyntax("java", unsupportedOperand.name, nodeLocation(unsupportedOperand, filename, source))
|
|
186
|
+
|
|
187
|
+
const operator = nodeText(operatorNode, source)
|
|
188
|
+
|
|
189
|
+
if (!javaBinaryOperations.has(operator)) return unsupportedSyntax("java", `binary ${operator}`, location)
|
|
190
|
+
|
|
191
|
+
const semantic = withAdaptedOperation(withParserRanges({
|
|
192
|
+
kind: "BinaryExpression",
|
|
193
|
+
left: convertExpression(operands[0], filename, source),
|
|
194
|
+
location,
|
|
195
|
+
right: convertExpression(operands[1], filename, source)
|
|
196
|
+
}, {operator: nodeLocation(operatorNode, filename, source)}), /** @type {import("../semantic/operators.js").AdaptedOperation} */ (javaBinaryOperations.get(operator)))
|
|
197
|
+
|
|
198
|
+
return /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (semantic))
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (node.name == "MethodInvocation") {
|
|
202
|
+
if (isEqualsInvocation(node, source)) {
|
|
203
|
+
const methodName = requiredChild(node, "MethodName", filename, source)
|
|
204
|
+
|
|
205
|
+
return convertStringEquality(node, false, location, nodeLocation(methodName, filename, source), filename, source)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const unsupportedReceiver = structuralChildren(node).find((child) => child.name != "MethodName" && child.name != "ArgumentList")
|
|
209
|
+
|
|
210
|
+
if (unsupportedReceiver) return unsupportedSyntax("java", "method invocation receiver", nodeLocation(unsupportedReceiver, filename, source))
|
|
211
|
+
|
|
212
|
+
const methodName = requiredChild(node, "MethodName", filename, source)
|
|
213
|
+
const argumentList = requiredChild(node, "ArgumentList", filename, source)
|
|
214
|
+
const argumentNodes = structuralChildren(argumentList)
|
|
215
|
+
const unsupportedArgument = argumentNodes.find((child) => !isSupportedExpressionNode(child))
|
|
216
|
+
|
|
217
|
+
if (unsupportedArgument) return unsupportedSyntax("java", `method argument ${unsupportedArgument.name}`, nodeLocation(unsupportedArgument, filename, source))
|
|
218
|
+
|
|
219
|
+
const arguments_ = argumentNodes.map((child) => convertExpression(child, filename, source))
|
|
220
|
+
|
|
221
|
+
return withParserRanges({arguments: arguments_, callee: nodeText(methodName, source), kind: /** @type {const} */ ("CallExpression"), location}, {
|
|
222
|
+
callee: nodeLocation(methodName, filename, source)
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return unsupportedSyntax("java", node.name, location)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Checks the one parser-native Java string equality invocation form.
|
|
231
|
+
* @param {import("@lezer/common").SyntaxNode} node - Method invocation node.
|
|
232
|
+
* @param {string} source - Complete source.
|
|
233
|
+
* @returns {boolean} Whether the invocation spells `.equals` with a receiver.
|
|
234
|
+
*/
|
|
235
|
+
function isEqualsInvocation(node, source) {
|
|
236
|
+
const methodName = node.getChild("MethodName")
|
|
237
|
+
const receivers = structuralChildren(node).filter((child) => child.name != "MethodName" && child.name != "ArgumentList")
|
|
238
|
+
|
|
239
|
+
return methodName != null && nodeText(methodName, source) == "equals" && receivers.length == 1
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Converts Java `.equals` or its directly negated form without source-text inference.
|
|
244
|
+
* @param {import("@lezer/common").SyntaxNode} node - Parser method invocation.
|
|
245
|
+
* @param {boolean} negated - Whether a parser-owned unary not wraps the invocation.
|
|
246
|
+
* @param {import("../semantic/types.js").SourceLocation} location - Whole equality expression location.
|
|
247
|
+
* @param {import("../semantic/types.js").SourceLocation} operatorLocation - Parser-owned operator/method location.
|
|
248
|
+
* @param {string} filename - Source filename.
|
|
249
|
+
* @param {string} source - Complete source.
|
|
250
|
+
* @returns {import("../semantic/types.js").Expression} Adapted equality expression.
|
|
251
|
+
*/
|
|
252
|
+
function convertStringEquality(node, negated, location, operatorLocation, filename, source) {
|
|
253
|
+
const argumentList = requiredChild(node, "ArgumentList", filename, source)
|
|
254
|
+
const arguments_ = structuralChildren(argumentList)
|
|
255
|
+
const receivers = structuralChildren(node).filter((child) => child.name != "MethodName" && child.name != "ArgumentList")
|
|
256
|
+
const unsupportedArgument = arguments_.find((child) => !isSupportedExpressionNode(child))
|
|
257
|
+
|
|
258
|
+
if (receivers.length != 1 || arguments_.length != 1 || unsupportedArgument) {
|
|
259
|
+
return unsupportedSyntax("java", "string equals invocation", nodeLocation(node, filename, source))
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** @type {Record<string, import("../semantic/types.js").SourceLocation>} */
|
|
263
|
+
const ranges = {operator: operatorLocation}
|
|
264
|
+
|
|
265
|
+
if (negated) {
|
|
266
|
+
ranges.equalityOperator = nodeLocation(requiredChild(node, "MethodName", filename, source), filename, source)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const semantic = withAdaptedOperation(withParserRanges({
|
|
270
|
+
kind: "BinaryExpression",
|
|
271
|
+
left: convertExpression(receivers[0], filename, source),
|
|
272
|
+
location,
|
|
273
|
+
right: convertExpression(arguments_[0], filename, source)
|
|
274
|
+
}, ranges), negated ? "StringNotEqual" : "StringEqual")
|
|
275
|
+
|
|
276
|
+
return /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (semantic))
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Checks whether a Lezer node is in the expression subset.
|
|
281
|
+
* @param {import("@lezer/common").SyntaxNode} node - Java syntax node.
|
|
282
|
+
* @returns {boolean} Whether the node is supported.
|
|
283
|
+
*/
|
|
284
|
+
function isSupportedExpressionNode(node) {
|
|
285
|
+
return [
|
|
286
|
+
"Identifier",
|
|
287
|
+
"IntegerLiteral",
|
|
288
|
+
"BooleanLiteral",
|
|
289
|
+
"StringLiteral",
|
|
290
|
+
"UnaryExpression",
|
|
291
|
+
"BinaryExpression",
|
|
292
|
+
"MethodInvocation",
|
|
293
|
+
"ParenthesizedExpression"
|
|
294
|
+
].includes(node.name)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Applies Java Unicode translation before token-level string escape decoding.
|
|
299
|
+
* @param {string} literal - Parser-confirmed Java string literal.
|
|
300
|
+
* @param {import("../semantic/types.js").SourceLocation} location - Literal source location.
|
|
301
|
+
* @returns {string} Unicode-translated literal.
|
|
302
|
+
*/
|
|
303
|
+
function translateUnicodeEscapes(literal, location) {
|
|
304
|
+
let translated = ""
|
|
305
|
+
let consecutiveBackslashes = 0
|
|
306
|
+
let previousWasUnicodeEscape = false
|
|
307
|
+
|
|
308
|
+
for (let index = 0; index < literal.length; index++) {
|
|
309
|
+
const character = literal[index]
|
|
310
|
+
const eligible = character == "\\" && (previousWasUnicodeEscape || consecutiveBackslashes % 2 == 0)
|
|
311
|
+
|
|
312
|
+
if (eligible && literal[index + 1] == "u") {
|
|
313
|
+
let digitsStart = index + 2
|
|
314
|
+
|
|
315
|
+
while (literal[digitsStart] == "u") digitsStart++
|
|
316
|
+
|
|
317
|
+
const hexadecimal = literal.slice(digitsStart, digitsStart + 4)
|
|
318
|
+
const isHexadecimal = hexadecimal.length == 4 && [...hexadecimal].every((digit) =>
|
|
319
|
+
(digit >= "0" && digit <= "9") || (digit >= "A" && digit <= "F") || (digit >= "a" && digit <= "f")
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
if (!isHexadecimal) return unsupportedSyntax("java", "unsupported string escape", location)
|
|
323
|
+
|
|
324
|
+
const translatedCharacter = String.fromCharCode(Number.parseInt(hexadecimal, 16))
|
|
325
|
+
|
|
326
|
+
translated += translatedCharacter
|
|
327
|
+
consecutiveBackslashes = translatedCharacter == "\\" ? consecutiveBackslashes + 1 : 0
|
|
328
|
+
previousWasUnicodeEscape = true
|
|
329
|
+
index = digitsStart + 3
|
|
330
|
+
continue
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
translated += character
|
|
334
|
+
consecutiveBackslashes = character == "\\" ? consecutiveBackslashes + 1 : 0
|
|
335
|
+
previousWasUnicodeEscape = false
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return translated
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Decodes the accepted escapes from one parser-confirmed Java string literal.
|
|
343
|
+
* @param {import("@lezer/common").SyntaxNode} node - Java StringLiteral node.
|
|
344
|
+
* @param {string} filename - Source filename.
|
|
345
|
+
* @param {string} source - Complete source.
|
|
346
|
+
* @returns {string} Decoded Unicode string.
|
|
347
|
+
*/
|
|
348
|
+
function decodeStringLiteral(node, filename, source) {
|
|
349
|
+
const location = nodeLocation(node, filename, source)
|
|
350
|
+
const literal = translateUnicodeEscapes(nodeText(node, source), location)
|
|
351
|
+
let value = ""
|
|
352
|
+
|
|
353
|
+
for (let index = 1; index < literal.length - 1; index++) {
|
|
354
|
+
const character = literal[index]
|
|
355
|
+
|
|
356
|
+
if (character == "\"" || character == "\n" || character == "\r") {
|
|
357
|
+
return unsupportedSyntax("java", "invalid string literal", location)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (character != "\\") {
|
|
361
|
+
value += character
|
|
362
|
+
continue
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (index + 1 >= literal.length - 1) return unsupportedSyntax("java", "invalid string literal", location)
|
|
366
|
+
|
|
367
|
+
const escaped = literal[++index]
|
|
368
|
+
const simple = simpleStringEscapes[escaped]
|
|
369
|
+
|
|
370
|
+
if (simple !== undefined) {
|
|
371
|
+
value += simple
|
|
372
|
+
continue
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (escaped >= "0" && escaped <= "7") {
|
|
376
|
+
let octal = escaped
|
|
377
|
+
const maximumLength = escaped <= "3" ? 3 : 2
|
|
378
|
+
|
|
379
|
+
while (octal.length < maximumLength && literal[index + 1] >= "0" && literal[index + 1] <= "7") {
|
|
380
|
+
octal += literal[++index]
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
value += String.fromCharCode(Number.parseInt(octal, 8))
|
|
384
|
+
continue
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return unsupportedSyntax("java", "unsupported string escape", location)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (!hasOnlyUnicodeScalars(value)) return unsupportedSyntax("java", "invalid Unicode string literal", location)
|
|
391
|
+
|
|
392
|
+
return value
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Converts one explicit Java return statement.
|
|
397
|
+
* @param {import("@lezer/common").SyntaxNode} statement - Java statement.
|
|
398
|
+
* @param {string} filename - Source filename.
|
|
399
|
+
* @param {string} source - Complete source.
|
|
400
|
+
* @returns {import("../semantic/types.js").ReturnStatement} Semantic return.
|
|
401
|
+
*/
|
|
402
|
+
function convertReturn(statement, filename, source) {
|
|
403
|
+
const location = nodeLocation(statement, filename, source)
|
|
404
|
+
|
|
405
|
+
if (statement.name != "ReturnStatement") return unsupportedSyntax("java", statement.name, location)
|
|
406
|
+
|
|
407
|
+
const expressionNodes = directChildren(statement).filter((child) => child.name != "return" && child.name != ";")
|
|
408
|
+
const expression = expressionNodes.length == 1 ? expressionNodes[0] : undefined
|
|
409
|
+
|
|
410
|
+
if (!expression) {
|
|
411
|
+
const unsupported = expressionNodes[0]
|
|
412
|
+
|
|
413
|
+
return unsupportedSyntax(
|
|
414
|
+
"java",
|
|
415
|
+
unsupported?.name ?? "empty return",
|
|
416
|
+
unsupported ? nodeLocation(unsupported, filename, source) : location
|
|
417
|
+
)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return {expression: convertExpression(expression, filename, source), kind: "ReturnStatement", location}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Converts one Java local declaration or assignment.
|
|
425
|
+
* @param {import("@lezer/common").SyntaxNode} statement - Java statement.
|
|
426
|
+
* @param {string} filename - Source filename.
|
|
427
|
+
* @param {string} source - Complete source.
|
|
428
|
+
* @returns {import("../semantic/types.js").LocalStatement} Semantic local statement.
|
|
429
|
+
*/
|
|
430
|
+
function convertLocalStatement(statement, filename, source) {
|
|
431
|
+
const location = nodeLocation(statement, filename, source)
|
|
432
|
+
|
|
433
|
+
if (statement.name == "LocalVariableDeclaration") {
|
|
434
|
+
const declarators = statement.getChildren("VariableDeclarator")
|
|
435
|
+
const typeNode = declarationType(statement)
|
|
436
|
+
const varNode = statement.getChild("var")
|
|
437
|
+
const modifiers = statement.getChild("Modifiers")
|
|
438
|
+
const modifierChildren = modifiers ? structuralChildren(modifiers) : []
|
|
439
|
+
|
|
440
|
+
if (varNode) return unsupportedSyntax("java", "var local declaration", nodeLocation(varNode, filename, source))
|
|
441
|
+
if (declarators.length != 1) return unsupportedSyntax("java", "multiple local declarators", location)
|
|
442
|
+
if (!typeNode || typeNode.name == "ArrayType") return unsupportedSyntax("java", "unsupported local type", location)
|
|
443
|
+
if (modifierChildren.some((child) => child.name != "final") || modifierChildren.length > 1) {
|
|
444
|
+
return unsupportedSyntax("java", "unsupported local modifiers", modifiers ? nodeLocation(modifiers, filename, source) : location)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const declarator = declarators[0]
|
|
448
|
+
const definition = requiredChild(declarator, "Definition", filename, source)
|
|
449
|
+
const assignment = declarator.getChild("AssignOp")
|
|
450
|
+
const dimensions = declarator.getChildren("Dimension")
|
|
451
|
+
const initializerNodes = directChildren(declarator).filter((child) => child.name != "Definition" && child.name != "AssignOp")
|
|
452
|
+
|
|
453
|
+
if (dimensions.length > 0) return unsupportedSyntax("java", "array local declaration", nodeLocation(declarator, filename, source))
|
|
454
|
+
if (!assignment || nodeText(assignment, source) != "=" || initializerNodes.length != 1) {
|
|
455
|
+
return unsupportedSyntax("java", "uninitialized local declaration", nodeLocation(declarator, filename, source))
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const name = nodeText(definition, source)
|
|
459
|
+
|
|
460
|
+
return withParserRanges({
|
|
461
|
+
initializer: convertExpression(initializerNodes[0], filename, source),
|
|
462
|
+
kind: "LocalDeclaration",
|
|
463
|
+
location,
|
|
464
|
+
mutable: modifierChildren.length == 0,
|
|
465
|
+
name,
|
|
466
|
+
type: convertType(typeNode, `Local '${name}'`, location, filename, source)
|
|
467
|
+
}, {name: nodeLocation(definition, filename, source), operator: nodeLocation(assignment, filename, source)})
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (statement.name == "ExpressionStatement") {
|
|
471
|
+
const assignment = statement.getChild("AssignmentExpression")
|
|
472
|
+
|
|
473
|
+
if (!assignment) return unsupportedSyntax("java", statement.name, location)
|
|
474
|
+
|
|
475
|
+
const children = structuralChildren(assignment)
|
|
476
|
+
const operator = assignment.getChild("AssignOp")
|
|
477
|
+
const target = children[0]
|
|
478
|
+
const expression = children.at(-1)
|
|
479
|
+
|
|
480
|
+
if (!operator || nodeText(operator, source) != "=") return unsupportedSyntax("java", "compound assignment", nodeLocation(assignment, filename, source))
|
|
481
|
+
if (!target || target.name != "Identifier") return unsupportedSyntax("java", target?.name ?? "assignment target", target ? nodeLocation(target, filename, source) : location)
|
|
482
|
+
if (!expression || expression == target || expression == operator) return unsupportedSyntax("java", "assignment expression", nodeLocation(assignment, filename, source))
|
|
483
|
+
|
|
484
|
+
const targetExpression = withParserRanges({
|
|
485
|
+
kind: /** @type {const} */ ("IdentifierExpression"),
|
|
486
|
+
location: nodeLocation(target, filename, source),
|
|
487
|
+
name: nodeText(target, source)
|
|
488
|
+
}, {name: nodeLocation(target, filename, source)})
|
|
489
|
+
|
|
490
|
+
return withParserRanges({
|
|
491
|
+
expression: convertExpression(expression, filename, source),
|
|
492
|
+
kind: "AssignmentStatement",
|
|
493
|
+
location,
|
|
494
|
+
target: targetExpression
|
|
495
|
+
}, {operator: nodeLocation(operator, filename, source)})
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return unsupportedSyntax("java", statement.name, location)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Converts one exhaustive direct Java block child.
|
|
503
|
+
* @param {import("@lezer/common").SyntaxNode} statement - Java statement.
|
|
504
|
+
* @param {string} filename - Source filename.
|
|
505
|
+
* @param {string} source - Complete source.
|
|
506
|
+
* @returns {import("../semantic/types.js").Statement} Semantic statement.
|
|
507
|
+
*/
|
|
508
|
+
function convertStatement(statement, filename, source) {
|
|
509
|
+
if (statement.name == "ReturnStatement") return convertReturn(statement, filename, source)
|
|
510
|
+
if (statement.name == "IfStatement") return convertIf(statement, filename, source)
|
|
511
|
+
if (statement.name == "LocalVariableDeclaration") return convertLocalStatement(statement, filename, source)
|
|
512
|
+
if (statement.name == "ExpressionStatement") {
|
|
513
|
+
if (statement.getChild("AssignmentExpression")) return convertLocalStatement(statement, filename, source)
|
|
514
|
+
if (!statement.getChild("MethodInvocation")) {
|
|
515
|
+
return unsupportedSyntax("java", statement.name, nodeLocation(statement, filename, source))
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return convertPrint(statement, filename, source)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return unsupportedSyntax("java", statement.name, nodeLocation(statement, filename, source))
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Converts direct structural children of one Java block in source order.
|
|
526
|
+
* @param {import("@lezer/common").SyntaxNode} node - Java Block node.
|
|
527
|
+
* @param {string} filename - Source filename.
|
|
528
|
+
* @param {string} source - Complete source.
|
|
529
|
+
* @returns {import("../semantic/types.js").Block} Semantic block.
|
|
530
|
+
*/
|
|
531
|
+
function convertBlock(node, filename, source) {
|
|
532
|
+
if (node.name != "Block") return unsupportedSyntax("java", `expected block, received ${node.name}`, nodeLocation(node, filename, source))
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
kind: "Block",
|
|
536
|
+
location: nodeLocation(node, filename, source),
|
|
537
|
+
statements: structuralChildren(node).map((statement) => convertStatement(statement, filename, source))
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Requires an exact Java scalar type node.
|
|
543
|
+
* @param {import("@lezer/common").SyntaxNode | null} sourceType - Java type node.
|
|
544
|
+
* @param {string} subject - Typed subject.
|
|
545
|
+
* @param {import("../semantic/types.js").SourceLocation} location - Source location.
|
|
546
|
+
* @param {string} filename - Source filename.
|
|
547
|
+
* @param {string} source - Complete source.
|
|
548
|
+
* @returns {import("../semantic/types.js").TypeReference} Semantic type.
|
|
549
|
+
*/
|
|
550
|
+
function convertType(sourceType, subject, location, filename, source) {
|
|
551
|
+
if (!sourceType) return missingType("java", subject, location)
|
|
552
|
+
if (!["PrimitiveType", "TypeName", "ScopedTypeName"].includes(sourceType.name)) {
|
|
553
|
+
return unsupportedSyntax("java", "unsupported scalar type", location)
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const type = sourceScalarType("java", nodeText(sourceType, source), nodeLocation(sourceType, filename, source))
|
|
557
|
+
|
|
558
|
+
if (!type) return unsupportedSyntax("java", "unsupported scalar type", nodeLocation(sourceType, location.filename, source))
|
|
559
|
+
|
|
560
|
+
return type
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Returns the direct Java declaration type node supported by scalar conversion.
|
|
565
|
+
* @param {import("@lezer/common").SyntaxNode} node - Declaration node.
|
|
566
|
+
* @returns {import("@lezer/common").SyntaxNode | null} Type syntax node.
|
|
567
|
+
*/
|
|
568
|
+
function declarationType(node) {
|
|
569
|
+
return node.getChild("PrimitiveType") ?? node.getChild("TypeName") ?? node.getChild("ScopedTypeName")
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Converts the supported Java function method.
|
|
574
|
+
* @param {import("@lezer/common").SyntaxNode} node - Method declaration.
|
|
575
|
+
* @param {string} filename - Source filename.
|
|
576
|
+
* @param {string} source - Complete source.
|
|
577
|
+
* @returns {import("../semantic/types.js").FunctionDeclaration} Semantic function.
|
|
578
|
+
*/
|
|
579
|
+
function convertFunction(node, filename, source) {
|
|
580
|
+
const location = nodeLocation(node, filename, source)
|
|
581
|
+
const definition = requiredChild(node, "Definition", filename, source)
|
|
582
|
+
const name = nodeText(definition, source)
|
|
583
|
+
const parametersNode = requiredChild(node, "FormalParameters", filename, source)
|
|
584
|
+
const parameters = parametersNode.getChildren("FormalParameter").map((parameter) => {
|
|
585
|
+
const parameterLocation = nodeLocation(parameter, filename, source)
|
|
586
|
+
const parameterNameNode = requiredChild(parameter, "Definition", filename, source)
|
|
587
|
+
const parameterName = nodeText(parameterNameNode, source)
|
|
588
|
+
|
|
589
|
+
const semanticParameter = {
|
|
590
|
+
kind: /** @type {const} */ ("Parameter"),
|
|
591
|
+
location: parameterLocation,
|
|
592
|
+
name: parameterName,
|
|
593
|
+
type: convertType(declarationType(parameter), `Parameter '${parameterName}'`, parameterLocation, filename, source)
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return withParserRanges(semanticParameter, {name: nodeLocation(parameterNameNode, filename, source)})
|
|
597
|
+
})
|
|
598
|
+
const block = requiredChild(node, "Block", filename, source)
|
|
599
|
+
const body = convertBlock(block, filename, source)
|
|
600
|
+
|
|
601
|
+
return withParserRanges({
|
|
602
|
+
body,
|
|
603
|
+
kind: "FunctionDeclaration",
|
|
604
|
+
location,
|
|
605
|
+
name,
|
|
606
|
+
parameters,
|
|
607
|
+
returnType: convertType(declarationType(node), `Function '${name}' return`, location, filename, source)
|
|
608
|
+
}, {name: nodeLocation(definition, filename, source)})
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Converts the existing exact Java if/else terminal with restricted branch prefixes.
|
|
613
|
+
* @param {import("@lezer/common").SyntaxNode} node - Java if statement.
|
|
614
|
+
* @param {string} filename - Source filename.
|
|
615
|
+
* @param {string} source - Complete source.
|
|
616
|
+
* @returns {import("../semantic/types.js").IfStatement} Semantic branch.
|
|
617
|
+
*/
|
|
618
|
+
function convertIf(node, filename, source) {
|
|
619
|
+
const location = nodeLocation(node, filename, source)
|
|
620
|
+
const conditionContainer = requiredChild(node, "ParenthesizedExpression", filename, source)
|
|
621
|
+
const conditionNodes = structuralChildren(conditionContainer)
|
|
622
|
+
const condition = conditionNodes.length == 1 ? conditionNodes[0] : undefined
|
|
623
|
+
const direct = directChildren(node)
|
|
624
|
+
const conditionIndex = direct.findIndex((child) => child.name == conditionContainer.name &&
|
|
625
|
+
child.from == conditionContainer.from && child.to == conditionContainer.to)
|
|
626
|
+
const consequentNode = direct[conditionIndex + 1]
|
|
627
|
+
const alternateNode = direct.at(-1) == consequentNode ? undefined : direct.at(-1)
|
|
628
|
+
|
|
629
|
+
if (!condition || !consequentNode || consequentNode.name != "Block") {
|
|
630
|
+
return unsupportedSyntax("java", "if without block consequent", location)
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
let alternate
|
|
634
|
+
|
|
635
|
+
if (alternateNode?.name == "Block") alternate = convertBlock(alternateNode, filename, source)
|
|
636
|
+
else if (alternateNode?.name == "IfStatement") {
|
|
637
|
+
alternate = {
|
|
638
|
+
kind: /** @type {const} */ ("Block"),
|
|
639
|
+
location: nodeLocation(alternateNode, filename, source),
|
|
640
|
+
statements: [convertIf(alternateNode, filename, source)]
|
|
641
|
+
}
|
|
642
|
+
} else if (alternateNode && alternateNode.name != "else") {
|
|
643
|
+
return unsupportedSyntax("java", `if alternate ${alternateNode.name}`, nodeLocation(alternateNode, filename, source))
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return {
|
|
647
|
+
...(alternate ? {alternate} : {}),
|
|
648
|
+
condition: convertExpression(condition, filename, source),
|
|
649
|
+
consequent: convertBlock(consequentNode, filename, source),
|
|
650
|
+
kind: "IfStatement",
|
|
651
|
+
location
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Converts Java's supported main method.
|
|
657
|
+
* @param {import("@lezer/common").SyntaxNode} node - Main method.
|
|
658
|
+
* @param {string} filename - Source filename.
|
|
659
|
+
* @param {string} source - Complete source.
|
|
660
|
+
* @returns {import("../semantic/types.js").EntryPoint} Semantic entry point.
|
|
661
|
+
*/
|
|
662
|
+
function convertEntryPoint(node, filename, source) {
|
|
663
|
+
const location = nodeLocation(node, filename, source)
|
|
664
|
+
const block = requiredChild(node, "Block", filename, source)
|
|
665
|
+
const body = convertBlock(block, filename, source)
|
|
666
|
+
|
|
667
|
+
return {body, kind: "EntryPoint", location}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Converts one supported Java print statement.
|
|
672
|
+
* @param {import("@lezer/common").SyntaxNode} statement - Expression statement.
|
|
673
|
+
* @param {string} filename - Source filename.
|
|
674
|
+
* @param {string} source - Complete source.
|
|
675
|
+
* @returns {import("../semantic/types.js").PrintStatement} Semantic print.
|
|
676
|
+
*/
|
|
677
|
+
function convertPrint(statement, filename, source) {
|
|
678
|
+
const location = nodeLocation(statement, filename, source)
|
|
679
|
+
|
|
680
|
+
const printInvocation = statement.getChild("MethodInvocation")
|
|
681
|
+
|
|
682
|
+
if (!printInvocation) return unsupportedSyntax("java", "main without System.out.println", location)
|
|
683
|
+
|
|
684
|
+
const fieldAccess = printInvocation.getChild("FieldAccess")
|
|
685
|
+
const methodName = printInvocation.getChild("MethodName")
|
|
686
|
+
|
|
687
|
+
if (!fieldAccess || !methodName || nodeText(fieldAccess, source) != "System.out" || nodeText(methodName, source) != "println") {
|
|
688
|
+
return unsupportedSyntax("java", "main without System.out.println", nodeLocation(printInvocation, filename, source))
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const argumentList = requiredChild(printInvocation, "ArgumentList", filename, source)
|
|
692
|
+
const arguments_ = structuralChildren(argumentList)
|
|
693
|
+
|
|
694
|
+
if (arguments_.length != 1) {
|
|
695
|
+
return unsupportedSyntax("java", "println without one supported argument", nodeLocation(argumentList, filename, source))
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const printLocation = nodeLocation(printInvocation, filename, source)
|
|
699
|
+
|
|
700
|
+
return {expression: convertExpression(arguments_[0], filename, source), kind: "PrintStatement", location: printLocation}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Parses Java into the shared semantic module.
|
|
705
|
+
* @param {object} input - Parser input.
|
|
706
|
+
* @param {string} input.filename - Source filename.
|
|
707
|
+
* @param {string} input.source - Source text.
|
|
708
|
+
* @returns {import("../semantic/types.js").SemanticModule} Semantic module.
|
|
709
|
+
*/
|
|
710
|
+
export function parseJava({filename, source}) {
|
|
711
|
+
const tree = parser.parse(source)
|
|
712
|
+
const error = descendants(tree.topNode, "⚠")[0]
|
|
713
|
+
|
|
714
|
+
if (error) {
|
|
715
|
+
throw new SemantifoldDiagnostic({
|
|
716
|
+
code: "PARSE_ERROR",
|
|
717
|
+
language: "java",
|
|
718
|
+
location: nodeLocation(error, filename, source),
|
|
719
|
+
message: "Java parser reported invalid syntax."
|
|
720
|
+
})
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const methods = descendants(tree.topNode, "MethodDeclaration")
|
|
724
|
+
const mainMethod = methods.find((method) => nodeText(requiredChild(method, "Definition", filename, source), source) == "main")
|
|
725
|
+
const functionMethods = methods.filter((method) => method != mainMethod)
|
|
726
|
+
const location = moduleLocation(filename, source)
|
|
727
|
+
|
|
728
|
+
if (functionMethods.length == 0) return unsupportedSyntax("java", "class without a semantic function", location)
|
|
729
|
+
if (!mainMethod) return unsupportedSyntax("java", "class without main", location)
|
|
730
|
+
|
|
731
|
+
const functions = functionMethods.map((method) => convertFunction(method, filename, source))
|
|
732
|
+
const entryPoint = convertEntryPoint(mainMethod, filename, source)
|
|
733
|
+
|
|
734
|
+
return {entryPoint, functions, kind: "Module", location}
|
|
735
|
+
}
|