mimo-lang 1.1.1 → 2.0.6
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/.gitattributes +24 -0
- package/LICENSE +21 -0
- package/README.md +71 -39
- package/adapters/browserAdapter.js +86 -0
- package/adapters/nodeAdapter.js +101 -0
- package/bin/cli.js +80 -0
- package/bin/commands/convert.js +27 -0
- package/bin/commands/doctor.js +139 -0
- package/bin/commands/eval.js +39 -0
- package/bin/commands/fmt.js +109 -0
- package/bin/commands/help.js +72 -0
- package/bin/commands/lint.js +117 -0
- package/bin/commands/repl.js +24 -0
- package/bin/commands/run.js +64 -0
- package/bin/commands/test.js +126 -0
- package/bin/utils/colors.js +38 -0
- package/bin/utils/formatError.js +47 -0
- package/bin/utils/fs.js +57 -0
- package/bin/utils/version.js +8 -0
- package/build.js +18 -0
- package/bun.lock +74 -0
- package/index.js +48 -77
- package/index.web.js +364 -0
- package/interpreter/BuiltinFunction.js +32 -0
- package/interpreter/ErrorHandler.js +120 -0
- package/interpreter/ExpressionEvaluator.js +106 -0
- package/interpreter/Interpreter.js +172 -0
- package/interpreter/MimoError.js +112 -0
- package/interpreter/ModuleLoader.js +236 -0
- package/interpreter/StatementExecutor.js +107 -0
- package/interpreter/Utils.js +82 -0
- package/interpreter/Values.js +87 -0
- package/interpreter/coreBuiltins.js +490 -0
- package/interpreter/environment.js +99 -0
- package/interpreter/evaluators/binaryExpressionEvaluator.js +111 -0
- package/interpreter/evaluators/collectionEvaluator.js +151 -0
- package/interpreter/evaluators/functionCallEvaluator.js +76 -0
- package/interpreter/evaluators/literalEvaluator.js +27 -0
- package/interpreter/evaluators/moduleAccessEvaluator.js +25 -0
- package/interpreter/evaluators/templateLiteralEvaluator.js +20 -0
- package/interpreter/executors/BaseExecutor.js +37 -0
- package/interpreter/executors/ControlFlowExecutor.js +206 -0
- package/interpreter/executors/FunctionExecutor.js +126 -0
- package/interpreter/executors/PatternMatchExecutor.js +93 -0
- package/interpreter/executors/VariableExecutor.js +144 -0
- package/interpreter/index.js +8 -0
- package/interpreter/stdlib/array/accessFunctions.js +61 -0
- package/interpreter/stdlib/array/arrayUtils.js +36 -0
- package/interpreter/stdlib/array/higherOrderFunctions.js +285 -0
- package/interpreter/stdlib/array/searchFunctions.js +77 -0
- package/interpreter/stdlib/array/setFunctions.js +49 -0
- package/interpreter/stdlib/array/transformationFunctions.js +68 -0
- package/interpreter/stdlib/array.js +85 -0
- package/interpreter/stdlib/assert.js +143 -0
- package/interpreter/stdlib/datetime.js +170 -0
- package/interpreter/stdlib/env.js +54 -0
- package/interpreter/stdlib/fs.js +161 -0
- package/interpreter/stdlib/http.js +92 -0
- package/interpreter/stdlib/json.js +70 -0
- package/interpreter/stdlib/math.js +309 -0
- package/interpreter/stdlib/object.js +142 -0
- package/interpreter/stdlib/path.js +69 -0
- package/interpreter/stdlib/regex.js +134 -0
- package/interpreter/stdlib/string.js +260 -0
- package/interpreter/suggestions.js +46 -0
- package/lexer/Lexer.js +245 -0
- package/lexer/TokenTypes.js +131 -0
- package/lexer/createToken.js +11 -0
- package/lexer/tokenizers/commentTokenizer.js +45 -0
- package/lexer/tokenizers/literalTokenizer.js +163 -0
- package/lexer/tokenizers/symbolTokenizer.js +69 -0
- package/lexer/tokenizers/whitespaceTokenizer.js +36 -0
- package/package.json +29 -13
- package/parser/ASTNodes.js +448 -0
- package/parser/Parser.js +188 -0
- package/parser/expressions/atomicExpressions.js +165 -0
- package/parser/expressions/conditionalExpressions.js +0 -0
- package/parser/expressions/operatorExpressions.js +79 -0
- package/parser/expressions/primaryExpressions.js +77 -0
- package/parser/parseStatement.js +184 -0
- package/parser/parserExpressions.js +115 -0
- package/parser/parserUtils.js +19 -0
- package/parser/statements/controlFlowParsers.js +106 -0
- package/parser/statements/functionParsers.js +314 -0
- package/parser/statements/moduleParsers.js +57 -0
- package/parser/statements/patternMatchParsers.js +124 -0
- package/parser/statements/variableParsers.js +155 -0
- package/repl.js +325 -0
- package/test.js +47 -0
- package/tools/PrettyPrinter.js +3 -0
- package/tools/convert/Args.js +46 -0
- package/tools/convert/Registry.js +91 -0
- package/tools/convert/Transpiler.js +78 -0
- package/tools/convert/plugins/README.md +66 -0
- package/tools/convert/plugins/alya/index.js +10 -0
- package/tools/convert/plugins/alya/to_alya.js +289 -0
- package/tools/convert/plugins/alya/visitors/expressions.js +257 -0
- package/tools/convert/plugins/alya/visitors/statements.js +403 -0
- package/tools/convert/plugins/base_converter.js +228 -0
- package/tools/convert/plugins/javascript/index.js +10 -0
- package/tools/convert/plugins/javascript/mimo_runtime.js +265 -0
- package/tools/convert/plugins/javascript/to_js.js +155 -0
- package/tools/convert/plugins/javascript/visitors/expressions.js +197 -0
- package/tools/convert/plugins/javascript/visitors/patterns.js +102 -0
- package/tools/convert/plugins/javascript/visitors/statements.js +236 -0
- package/tools/convert/plugins/python/index.js +10 -0
- package/tools/convert/plugins/python/mimo_runtime.py +811 -0
- package/tools/convert/plugins/python/to_py.js +329 -0
- package/tools/convert/plugins/python/visitors/expressions.js +272 -0
- package/tools/convert/plugins/python/visitors/patterns.js +100 -0
- package/tools/convert/plugins/python/visitors/statements.js +257 -0
- package/tools/convert.js +102 -0
- package/tools/format/CommentAttacher.js +190 -0
- package/tools/format/CommentLexer.js +152 -0
- package/tools/format/Printer.js +849 -0
- package/tools/format/config.js +107 -0
- package/tools/formatter.js +169 -0
- package/tools/lint/Linter.js +391 -0
- package/tools/lint/config.js +114 -0
- package/tools/lint/rules/consistent-return.js +62 -0
- package/tools/lint/rules/max-depth.js +56 -0
- package/tools/lint/rules/no-empty-function.js +45 -0
- package/tools/lint/rules/no-magic-numbers.js +46 -0
- package/tools/lint/rules/no-shadow.js +113 -0
- package/tools/lint/rules/no-unused-vars.js +26 -0
- package/tools/lint/rules/prefer-const.js +19 -0
- package/tools/linter.js +261 -0
- package/tools/replFormatter.js +93 -0
- package/tools/stamp-version.js +32 -0
- package/web/index.js +9 -0
- package/bun.lockb +0 -0
- package/cli.js +0 -84
- package/compiler/execute/interpreter.js +0 -68
- package/compiler/execute/interpreters/binary.js +0 -12
- package/compiler/execute/interpreters/call.js +0 -10
- package/compiler/execute/interpreters/if.js +0 -10
- package/compiler/execute/interpreters/try-catch.js +0 -10
- package/compiler/execute/interpreters/while.js +0 -8
- package/compiler/execute/utils/createfunction.js +0 -11
- package/compiler/execute/utils/evaluate.js +0 -20
- package/compiler/execute/utils/operate.js +0 -23
- package/compiler/lexer/processToken.js +0 -40
- package/compiler/lexer/tokenTypes.js +0 -4
- package/compiler/lexer/tokenizer.js +0 -74
- package/compiler/parser/expression/comparison.js +0 -18
- package/compiler/parser/expression/identifier.js +0 -29
- package/compiler/parser/expression/number.js +0 -10
- package/compiler/parser/expression/operator.js +0 -21
- package/compiler/parser/expression/punctuation.js +0 -31
- package/compiler/parser/expression/string.js +0 -6
- package/compiler/parser/parseExpression.js +0 -27
- package/compiler/parser/parseStatement.js +0 -34
- package/compiler/parser/parser.js +0 -45
- package/compiler/parser/statement/call.js +0 -26
- package/compiler/parser/statement/function.js +0 -29
- package/compiler/parser/statement/if.js +0 -34
- package/compiler/parser/statement/return.js +0 -10
- package/compiler/parser/statement/set.js +0 -11
- package/compiler/parser/statement/show.js +0 -10
- package/compiler/parser/statement/try-catch.js +0 -25
- package/compiler/parser/statement/while.js +0 -22
- package/converter/go/convert.js +0 -110
- package/converter/js/convert.js +0 -107
- package/jsconfig.json +0 -27
- package/vite.config.js +0 -17
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { TokenType } from "../../lexer/TokenTypes.js";
|
|
2
|
+
import { ASTNode } from "../ASTNodes.js";
|
|
3
|
+
import { parseExpression } from "../parserExpressions.js";
|
|
4
|
+
import { parseBlock, isBlockEnd } from "../parserUtils.js";
|
|
5
|
+
|
|
6
|
+
export function parseIfStatement(parser) {
|
|
7
|
+
const ifToken = parser.expectKeyword("if", 'SYN046', 'Expected "if" keyword to start an if statement.');
|
|
8
|
+
const condition = parseExpression(parser);
|
|
9
|
+
// parser.expect(TokenType.Keyword, "then", 'SYNXXX', 'Expected "then" keyword after if condition.'); // If your language uses "then"
|
|
10
|
+
const consequent = parseBlock(parser, ["else", "end"]);
|
|
11
|
+
|
|
12
|
+
let alternate = null;
|
|
13
|
+
if (parser.matchKeyword("else")) {
|
|
14
|
+
if (parser.peek()?.value === "if" && parser.peek()?.type === TokenType.Keyword) {
|
|
15
|
+
alternate = parseIfStatement(parser); // Handles "else if"
|
|
16
|
+
} else {
|
|
17
|
+
alternate = parseBlock(parser, ["end"]);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
parser.expectKeyword("end", 'SYN047', 'Expected "end" keyword to close if statement.');
|
|
22
|
+
return ASTNode.IfStatement(condition, consequent, alternate, ifToken);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function parseGuardStatement(parser) {
|
|
26
|
+
const guardToken = parser.expectKeyword("guard", "SYN120", 'Expected "guard" keyword to start an guard statement.');
|
|
27
|
+
const condition = parseExpression(parser);
|
|
28
|
+
parser.expectKeyword("else", "SYN121", 'Expected "else" keyword after guard condition.');
|
|
29
|
+
const alternate = parseBlock(parser, ["end"]);
|
|
30
|
+
parser.expectKeyword("end", "SYN122", 'Expected "end" keyword to close guard statement.');
|
|
31
|
+
return ASTNode.GuardStatement(condition, alternate, guardToken);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function parseWhileStatement(parser) {
|
|
35
|
+
const whileToken = parser.expect(TokenType.Keyword);
|
|
36
|
+
const condition = parseExpression(parser);
|
|
37
|
+
const body = parseBlock(parser);
|
|
38
|
+
|
|
39
|
+
parser.expect(TokenType.Keyword, "end");
|
|
40
|
+
return ASTNode.WhileStatement(condition, body, whileToken);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function parseForStatement(parser) {
|
|
44
|
+
const forToken = parser.expectKeyword("for", 'SYN048', 'Expected "for" keyword to start a for loop.');
|
|
45
|
+
const loopVar = parser.parseIdentifier('SYN048A', 'Expected an identifier for the loop variable.');
|
|
46
|
+
|
|
47
|
+
parser.expectKeyword("in", 'SYN049', 'Expected "in" keyword in for loop (e.g., for item in iterable).');
|
|
48
|
+
|
|
49
|
+
const iterable = parseExpression(parser);
|
|
50
|
+
|
|
51
|
+
const body = parseBlock(parser, ["end"]);
|
|
52
|
+
parser.expectKeyword("end", 'SYN050', 'Expected "end" keyword to close for loop.');
|
|
53
|
+
|
|
54
|
+
return ASTNode.ForStatement(loopVar, iterable, body, forToken);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function parseLoopStatement(parser) {
|
|
58
|
+
const loopToken = parser.expect(TokenType.Keyword);
|
|
59
|
+
const body = parseBlock(parser);
|
|
60
|
+
parser.expect(TokenType.Keyword, "end");
|
|
61
|
+
|
|
62
|
+
return ASTNode.LoopStatement(body, null, loopToken);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function parseBreakStatement(parser) {
|
|
66
|
+
const breakToken = parser.expect(TokenType.Keyword);
|
|
67
|
+
|
|
68
|
+
// Check for optional label
|
|
69
|
+
let label = null;
|
|
70
|
+
if (parser.peek()?.type === TokenType.Identifier) {
|
|
71
|
+
label = parser.expect(TokenType.Identifier).value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return ASTNode.BreakStatement(label, breakToken);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function parseContinueStatement(parser) {
|
|
78
|
+
const continueToken = parser.expect(TokenType.Keyword);
|
|
79
|
+
|
|
80
|
+
// Check for optional label
|
|
81
|
+
let label = null;
|
|
82
|
+
if (parser.peek()?.type === TokenType.Identifier) {
|
|
83
|
+
label = parser.expect(TokenType.Identifier).value;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return ASTNode.ContinueStatement(label, continueToken);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function parseTryStatement(parser) {
|
|
90
|
+
const tryToken = parser.expectKeyword("try", 'SYN051', 'Expected "try" keyword to start a try-catch block.');
|
|
91
|
+
const tryBlock = parseBlock(parser, ["catch", "end"]);
|
|
92
|
+
|
|
93
|
+
let catchVar = null;
|
|
94
|
+
let catchBlock = []; // Initialize as empty array
|
|
95
|
+
if (parser.matchKeyword("catch")) {
|
|
96
|
+
// Optional: allow specifying a variable for the caught error
|
|
97
|
+
if (parser.peek()?.type === TokenType.Identifier) {
|
|
98
|
+
catchVar = parser.parseIdentifier('SYN051A', 'Expected an identifier for the catch variable.');
|
|
99
|
+
}
|
|
100
|
+
catchBlock = parseBlock(parser, ["end"]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
parser.expectKeyword("end", 'SYN052', 'Expected "end" keyword to close try-catch block.');
|
|
104
|
+
return ASTNode.TryStatement(tryBlock, catchVar, catchBlock, tryToken);
|
|
105
|
+
}
|
|
106
|
+
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { MimoError } from "../../interpreter/MimoError.js";
|
|
2
|
+
import { TokenType } from "../../lexer/TokenTypes.js";
|
|
3
|
+
import { ASTNode } from "../ASTNodes.js";
|
|
4
|
+
import { parseExpression } from "../parserExpressions.js";
|
|
5
|
+
import { isBlockEnd, parseBlock } from "../parserUtils.js";
|
|
6
|
+
|
|
7
|
+
function parseParameterIdentifier(parser) {
|
|
8
|
+
const token = parser.peek();
|
|
9
|
+
if (token?.type === TokenType.Identifier || (token?.type === TokenType.Keyword && token.value === "fn")) {
|
|
10
|
+
parser.consume();
|
|
11
|
+
return ASTNode.Identifier(token.value, token);
|
|
12
|
+
}
|
|
13
|
+
parser.error('Expected a parameter name (identifier).', token, 'SYN021');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function parseDecorator(parser) {
|
|
17
|
+
const atToken = parser.expect(TokenType.At, undefined, 'SYN140', 'Expected "@" at the start of a decorator.');
|
|
18
|
+
const nameToken = parser.expect(TokenType.Identifier, undefined, 'SYN141', 'Expected decorator name after "@".');
|
|
19
|
+
const name = ASTNode.Identifier(nameToken.value, nameToken);
|
|
20
|
+
|
|
21
|
+
let args = null;
|
|
22
|
+
if (parser.match(TokenType.LParen)) {
|
|
23
|
+
args = [];
|
|
24
|
+
if (parser.peek()?.type !== TokenType.RParen) {
|
|
25
|
+
do {
|
|
26
|
+
args.push(parseExpression(parser));
|
|
27
|
+
} while (parser.match(TokenType.Comma));
|
|
28
|
+
}
|
|
29
|
+
parser.expect(TokenType.RParen, undefined, 'SYN142', 'Expected closing parenthesis after decorator arguments.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return ASTNode.Decorator(name, args, atToken);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function parseDecorators(parser) {
|
|
36
|
+
const decorators = [];
|
|
37
|
+
while (parser.peek()?.type === TokenType.At) {
|
|
38
|
+
decorators.push(parseDecorator(parser));
|
|
39
|
+
}
|
|
40
|
+
return decorators;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function parseFunctionDeclaration(parser, isExported = false, exportToken = null, decorators = []) {
|
|
44
|
+
// THIS IS THE FIX: This function now consumes the 'function' keyword itself.
|
|
45
|
+
const funcToken = parser.expectKeyword("function", 'SYNXXX', 'Expected "function" keyword.');
|
|
46
|
+
|
|
47
|
+
// The AST node's location should be the 'export' token if it exists, otherwise the 'function' token.
|
|
48
|
+
const astNodeLocationToken = exportToken || funcToken;
|
|
49
|
+
|
|
50
|
+
const nameToken = parser.expect(TokenType.Identifier, undefined, 'SYN054', 'Expected a function name (identifier).');
|
|
51
|
+
const name = nameToken.value;
|
|
52
|
+
|
|
53
|
+
parser.expect(TokenType.LParen, undefined, 'SYN055', 'Expected an opening parenthesis for function parameters.');
|
|
54
|
+
|
|
55
|
+
const params = [];
|
|
56
|
+
const defaults = {};
|
|
57
|
+
let restParam = null;
|
|
58
|
+
let hasEncounteredDefault = false;
|
|
59
|
+
let hasEncounteredRest = false;
|
|
60
|
+
|
|
61
|
+
if (parser.peek()?.type !== TokenType.RParen && parser.peek()?.value !== "->") {
|
|
62
|
+
do {
|
|
63
|
+
if (hasEncounteredRest) {
|
|
64
|
+
parser.error("No parameters allowed after a rest parameter.", parser.peek(), 'SYN060');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (parser.match(TokenType.Spread)) {
|
|
68
|
+
const spreadToken = parser.peek(-1);
|
|
69
|
+
const paramNameToken = parser.expect(TokenType.Identifier, undefined, 'SYN056', 'Expected an identifier for rest parameter.');
|
|
70
|
+
|
|
71
|
+
restParam = ASTNode.Identifier(paramNameToken.value, spreadToken);
|
|
72
|
+
hasEncounteredRest = true;
|
|
73
|
+
if (parser.peek()?.type === TokenType.Comma) {
|
|
74
|
+
parser.error("Rest parameter must be the last parameter.", parser.peek(-1), 'SYN063');
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
} else {
|
|
78
|
+
const paramNode = parseParameterIdentifier(parser);
|
|
79
|
+
params.push(paramNode); // Push the entire node
|
|
80
|
+
|
|
81
|
+
if (parser.match(TokenType.Colon)) {
|
|
82
|
+
defaults[paramNode.name] = parseExpression(parser); // Key by name
|
|
83
|
+
hasEncounteredDefault = true;
|
|
84
|
+
} else if (hasEncounteredDefault && !hasEncounteredRest) {
|
|
85
|
+
parser.error("Parameter without default value cannot follow parameter with default value.", paramNode, 'SYN_DEFAULT_ORDER');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} while (parser.match(TokenType.Comma));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
parser.expect(TokenType.RParen, undefined, 'SYN026', 'Expected a closing parenthesis for function parameters.');
|
|
92
|
+
|
|
93
|
+
const body = parseBlock(parser);
|
|
94
|
+
const endToken = parser.expect(TokenType.Keyword, "end", 'SYN027', 'Expected "end" keyword to close function declaration.');
|
|
95
|
+
|
|
96
|
+
return ASTNode.FunctionDeclaration(
|
|
97
|
+
name,
|
|
98
|
+
params,
|
|
99
|
+
defaults,
|
|
100
|
+
restParam,
|
|
101
|
+
body,
|
|
102
|
+
isExported, // Pass isExported flag
|
|
103
|
+
decorators, // Pass practitioners decorators
|
|
104
|
+
astNodeLocationToken, // Pass the correct location token
|
|
105
|
+
endToken
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// In: parser/statements/functionParsers.js
|
|
110
|
+
|
|
111
|
+
export function parseAnonymousFunction(parser, isFn = false) {
|
|
112
|
+
const funcToken = parser.peek(-1); // The `function` or `fn` keyword was just consumed.
|
|
113
|
+
|
|
114
|
+
if (!isFn) {
|
|
115
|
+
parser.expect(TokenType.LParen, undefined, 'SYN020', 'Expected an opening parenthesis for function parameters.');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const params = [];
|
|
119
|
+
const defaults = {};
|
|
120
|
+
let restParam = null;
|
|
121
|
+
let hasEncounteredDefault = false;
|
|
122
|
+
let hasEncounteredRest = false;
|
|
123
|
+
|
|
124
|
+
if (isFn) {
|
|
125
|
+
while (parser.peek() && (parser.peek().type !== TokenType.Operator || parser.peek().value !== "->")) {
|
|
126
|
+
if (hasEncounteredRest) {
|
|
127
|
+
parser.error("No parameters allowed after a rest parameter.", parser.peek(), 'SYN060');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (parser.match(TokenType.Spread)) {
|
|
131
|
+
const spreadToken = parser.peek(-1);
|
|
132
|
+
const paramNameToken = parser.expect(TokenType.Identifier, undefined, 'SYN056', 'Expected an identifier for rest parameter.');
|
|
133
|
+
restParam = ASTNode.Identifier(paramNameToken.value, spreadToken);
|
|
134
|
+
hasEncounteredRest = true;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const paramNode = parseParameterIdentifier(parser);
|
|
139
|
+
params.push(paramNode);
|
|
140
|
+
|
|
141
|
+
if (parser.match(TokenType.Colon)) {
|
|
142
|
+
defaults[paramNode.name] = parseExpression(parser);
|
|
143
|
+
hasEncounteredDefault = true;
|
|
144
|
+
} else if (hasEncounteredDefault && !hasEncounteredRest) {
|
|
145
|
+
parser.error("Parameter without default value cannot follow parameter with default value.", paramNode, 'SYN_DEFAULT_ORDER');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
parser.expect(TokenType.Operator, "->", 'SYN136', 'Expected "->" after fn parameters.');
|
|
150
|
+
const expression = parseExpression(parser);
|
|
151
|
+
const body = [ASTNode.ReturnStatement(expression, funcToken)];
|
|
152
|
+
|
|
153
|
+
return ASTNode.AnonymousFunction(
|
|
154
|
+
params,
|
|
155
|
+
defaults,
|
|
156
|
+
restParam,
|
|
157
|
+
body,
|
|
158
|
+
funcToken,
|
|
159
|
+
true
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (parser.peek()?.type !== TokenType.RParen) {
|
|
164
|
+
do {
|
|
165
|
+
if (parser.peek()?.type === TokenType.Operator && parser.peek()?.value === "->") {
|
|
166
|
+
parser.consume();
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
if (hasEncounteredRest) {
|
|
170
|
+
parser.error("No parameters allowed after a rest parameter.", parser.peek(), 'SYN060');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (parser.match(TokenType.Spread)) {
|
|
174
|
+
const spreadToken = parser.peek(-1);
|
|
175
|
+
const paramNameToken = parser.expect(TokenType.Identifier, undefined, 'SYN056', 'Expected an identifier for rest parameter.');
|
|
176
|
+
restParam = ASTNode.Identifier(paramNameToken.value, spreadToken);
|
|
177
|
+
hasEncounteredRest = true;
|
|
178
|
+
if (parser.peek()?.type === TokenType.Comma) {
|
|
179
|
+
parser.error("Rest parameter must be the last parameter.", parser.peek(-1), 'SYN063');
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
} else {
|
|
183
|
+
const paramNode = parseParameterIdentifier(parser);
|
|
184
|
+
params.push(paramNode); // Push the entire Identifier node
|
|
185
|
+
|
|
186
|
+
if (parser.match(TokenType.Colon)) {
|
|
187
|
+
defaults[paramNode.name] = parseExpression(parser);
|
|
188
|
+
hasEncounteredDefault = true;
|
|
189
|
+
} else if (hasEncounteredDefault && !hasEncounteredRest) {
|
|
190
|
+
parser.error("Parameter without default value cannot follow parameter with default value.", paramNode, 'SYN_DEFAULT_ORDER');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Separator handling: '->' is always allowed. Comma is only for standard 'function' syntax.
|
|
195
|
+
if (parser.match(TokenType.Operator, "->")) {
|
|
196
|
+
// Arrow found — done with params, move to body
|
|
197
|
+
break;
|
|
198
|
+
} else if (parser.match(TokenType.Comma)) {
|
|
199
|
+
if (isFn) {
|
|
200
|
+
parser.error(
|
|
201
|
+
"Comma-separated parameters are not supported in 'fn' syntax. Use '->' to separate parameters from the body.",
|
|
202
|
+
parser.peek(-1),
|
|
203
|
+
'SYN135',
|
|
204
|
+
"Write: fn x y -> expression end or use 'function' keyword for multi-param functions."
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
// If !isFn, we just consumed the comma normally, which is correct for standard syntax.
|
|
208
|
+
}
|
|
209
|
+
} while (parser.peek()?.type !== TokenType.RParen && parser.peek()?.value !== "->");
|
|
210
|
+
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!isFn) {
|
|
214
|
+
parser.expect(TokenType.RParen, undefined, 'SYN026', 'Expected a closing parenthesis for function parameters.');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const body = parseBlock(parser);
|
|
218
|
+
const endToken = parser.expect(TokenType.Keyword, "end", 'SYN027', 'Expected "end" keyword to close function declaration.');
|
|
219
|
+
|
|
220
|
+
return ASTNode.AnonymousFunction(
|
|
221
|
+
params,
|
|
222
|
+
defaults,
|
|
223
|
+
restParam,
|
|
224
|
+
body,
|
|
225
|
+
funcToken,
|
|
226
|
+
false
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function parseCallExpressionParts(parser, callToken) {
|
|
231
|
+
let callee;
|
|
232
|
+
// Allow module access like MyModule.myFunction or simple identifier
|
|
233
|
+
const firstToken = parser.expect(TokenType.Identifier, undefined, 'SYN064', 'Expected a function name (identifier) or module name after "call".');
|
|
234
|
+
|
|
235
|
+
if (parser.match(TokenType.Operator, ".")) {
|
|
236
|
+
const propertyToken = parser.expect(TokenType.Identifier, undefined, 'SYN065', 'Expected a property name (identifier) after "." for module access.');
|
|
237
|
+
callee = ASTNode.ModuleAccess(firstToken.value, propertyToken.value, firstToken); // firstToken is start of module.prop
|
|
238
|
+
} else {
|
|
239
|
+
callee = ASTNode.Identifier(firstToken.value, firstToken);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
parser.expect(TokenType.LParen, undefined, 'SYN066', 'Expected an opening parenthesis for function arguments.');
|
|
243
|
+
const args = [];
|
|
244
|
+
|
|
245
|
+
if (parser.peek()?.type !== TokenType.RParen) {
|
|
246
|
+
do {
|
|
247
|
+
if (parser.peek()?.type === TokenType.Spread) {
|
|
248
|
+
const spreadToken = parser.consume();
|
|
249
|
+
const argument = parseExpression(parser);
|
|
250
|
+
args.push(ASTNode.SpreadElement(argument, spreadToken));
|
|
251
|
+
} else {
|
|
252
|
+
args.push(parseExpression(parser));
|
|
253
|
+
}
|
|
254
|
+
} while (parser.match(TokenType.Comma));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
parser.expect(TokenType.RParen, undefined, 'SYN067', 'Expected a closing parenthesis for function arguments.');
|
|
258
|
+
|
|
259
|
+
return ASTNode.CallExpression(callee, args, callToken); // Returns a CallExpression AST node
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function parseCallStatement(parser) {
|
|
263
|
+
const callToken = parser.expectKeyword("call", 'SYN063', 'Expected "call" keyword to initiate a function call.');
|
|
264
|
+
|
|
265
|
+
// Use the new reusable function to parse the `callee(args)` part
|
|
266
|
+
const callExpression = parseCallExpressionParts(parser, callToken); // Pass the callToken for location
|
|
267
|
+
|
|
268
|
+
let destination = null;
|
|
269
|
+
if (parser.match(TokenType.Operator, "->")) {
|
|
270
|
+
const destinationToken = parser.expect(TokenType.Identifier, undefined, 'SYN069', 'Expected an identifier for the assignment destination after "->".');
|
|
271
|
+
destination = ASTNode.Identifier(destinationToken.value, destinationToken); // Store as ASTNode.Identifier
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Now, create the CallStatement AST node.
|
|
275
|
+
// It will embed the CallExpression.
|
|
276
|
+
return ASTNode.CallStatement(
|
|
277
|
+
callExpression.callee, // The function being called
|
|
278
|
+
callExpression.arguments, // The arguments to the call
|
|
279
|
+
destination, // The optional destination for the result
|
|
280
|
+
callToken // The original 'call' token for location
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function parseReturnStatement(parser) {
|
|
285
|
+
const returnToken = parser.expectKeyword("return", 'SYN070', 'Expected "return" keyword.');
|
|
286
|
+
let argument = null;
|
|
287
|
+
|
|
288
|
+
// If the next token is the end of a block, there is no return value.
|
|
289
|
+
// This handles `return end` or `return else` etc.
|
|
290
|
+
const nextToken = parser.peek();
|
|
291
|
+
if (
|
|
292
|
+
parser.isAtEnd() ||
|
|
293
|
+
(nextToken.type === TokenType.Keyword && ['end', 'else', 'catch', 'case', 'default'].includes(nextToken.value))
|
|
294
|
+
) {
|
|
295
|
+
// This is a `return` statement with no value, so argument remains null.
|
|
296
|
+
} else {
|
|
297
|
+
// Otherwise, there MUST be an expression to return.
|
|
298
|
+
argument = parseExpression(parser);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return ASTNode.ReturnStatement(argument, returnToken);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function parseShowStatement(parser) {
|
|
305
|
+
const showToken = parser.expectKeyword("show", 'SYN071', 'Expected "show" keyword.');
|
|
306
|
+
const expression = parseExpression(parser);
|
|
307
|
+
return ASTNode.ShowStatement(expression, showToken);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function parseThrowStatement(parser) {
|
|
311
|
+
const throwToken = parser.expectKeyword("throw", 'SYN072', 'Expected "throw" keyword.');
|
|
312
|
+
const argument = parseExpression(parser);
|
|
313
|
+
return ASTNode.ThrowStatement(argument, throwToken);
|
|
314
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { TokenType } from "../../lexer/TokenTypes.js";
|
|
2
|
+
import { ASTNode } from "../ASTNodes.js";
|
|
3
|
+
import { parseVariableOrAssignment } from "./variableParsers.js";
|
|
4
|
+
import { parseFunctionDeclaration } from "./functionParsers.js";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export function parseImportStatement(parser) {
|
|
8
|
+
const importToken = parser.expectKeyword("import", 'SYN073', 'Expected "import" keyword.');
|
|
9
|
+
// now we expect: import <Identifier> from <String> [as <Identifier>]
|
|
10
|
+
const nameToken = parser.expect(TokenType.Identifier, undefined, 'SYN073a', 'Expected an identifier for the module to import.');
|
|
11
|
+
|
|
12
|
+
parser.expectKeyword("from", 'SYN074a', 'Expected "from" keyword after imported name.');
|
|
13
|
+
|
|
14
|
+
const pathToken = parser.expect(TokenType.String, undefined, 'SYN074', 'Expected a string literal for the module path (e.g., "my_module").');
|
|
15
|
+
|
|
16
|
+
let aliasToken = null;
|
|
17
|
+
if (parser.matchKeyword("as")) {
|
|
18
|
+
aliasToken = parser.expect(TokenType.Identifier, undefined, 'SYN076', 'Expected an identifier for the module alias.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return ASTNode.ImportStatement(
|
|
22
|
+
pathToken.value,
|
|
23
|
+
aliasToken ? aliasToken.value : nameToken.value,
|
|
24
|
+
importToken
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function parseExportStatement(parser, decorators = []) {
|
|
29
|
+
const exportToken = parser.expectKeyword("export", 'SYN077', 'Expected "export" keyword.');
|
|
30
|
+
|
|
31
|
+
const nextToken = parser.peek();
|
|
32
|
+
if (!nextToken || parser.isAtEnd()) { // Check isAtEnd as well
|
|
33
|
+
parser.error("Unexpected end of input after 'export'.", exportToken, 'SYN078', "Expected a variable or function declaration to export.");
|
|
34
|
+
}
|
|
35
|
+
if (nextToken.type === TokenType.Keyword) {
|
|
36
|
+
switch (nextToken.value) {
|
|
37
|
+
case "set":
|
|
38
|
+
case "let":
|
|
39
|
+
case "const":
|
|
40
|
+
case "global":
|
|
41
|
+
// Pass exportToken as the location token for the AST node
|
|
42
|
+
return parseVariableOrAssignment(parser, true, exportToken);
|
|
43
|
+
case "function":
|
|
44
|
+
// Pass exportToken as the location token for the AST node
|
|
45
|
+
return parseFunctionDeclaration(parser, true, exportToken, decorators);
|
|
46
|
+
default:
|
|
47
|
+
parser.error(
|
|
48
|
+
`Cannot export statement of type '${nextToken.value}'. Expected a declaration.`,
|
|
49
|
+
nextToken,
|
|
50
|
+
'SYN079',
|
|
51
|
+
"Only 'set', 'let', 'const', 'global', or 'function' declarations can be exported."
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// If not a keyword, it must be an error because export must be followed by a declaration keyword.
|
|
56
|
+
parser.error("Expected a declaration keyword (set, let, const, global, function) after 'export'.", nextToken, 'SYN080', 'Only variable and function declarations can be exported.');
|
|
57
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { TokenType } from "../../lexer/TokenTypes.js";
|
|
2
|
+
import { ASTNode } from "../ASTNodes.js";
|
|
3
|
+
import { parseExpression } from "../parserExpressions.js";
|
|
4
|
+
import { parseBlock, isBlockEnd } from "../parserUtils.js";
|
|
5
|
+
|
|
6
|
+
export function parseMatchStatement(parser) {
|
|
7
|
+
const matchToken = parser.expectKeyword("match", 'SYN081', 'Expected "match" keyword to start a match statement.');
|
|
8
|
+
const discriminant = parseExpression(parser);
|
|
9
|
+
|
|
10
|
+
const cases = [];
|
|
11
|
+
|
|
12
|
+
// Parse case clauses
|
|
13
|
+
while (
|
|
14
|
+
parser.peek()?.type === TokenType.Keyword &&
|
|
15
|
+
(parser.peek()?.value === "case" || parser.peek()?.value === "default")
|
|
16
|
+
) {
|
|
17
|
+
if (parser.peek()?.value === "case") {
|
|
18
|
+
const caseToken = parser.expectKeyword("case", 'SYN082', 'Expected "case" keyword for a match clause.');
|
|
19
|
+
const pattern = parsePattern(parser);
|
|
20
|
+
|
|
21
|
+
let guard = null;
|
|
22
|
+
if (parser.matchKeyword("when")) {
|
|
23
|
+
guard = parseExpression(parser);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
parser.expect(TokenType.Colon, undefined, 'SYN083', 'Expected a colon (:) after the pattern in a match clause.');
|
|
27
|
+
const consequent = parseBlock(parser, ["case", "default", "end"]);
|
|
28
|
+
|
|
29
|
+
cases.push(ASTNode.CaseClause(pattern, guard, consequent, caseToken));
|
|
30
|
+
} else if (parser.peek()?.value === "default") {
|
|
31
|
+
const defaultToken = parser.expectKeyword("default", 'SYN084', 'Expected "default" keyword for the fallback match clause.');
|
|
32
|
+
parser.expect(TokenType.Colon, undefined, 'SYN085', 'Expected a colon (:) after "default" in a match clause.');
|
|
33
|
+
const consequent = parseBlock(parser, ["case", "default", "end"]);
|
|
34
|
+
|
|
35
|
+
cases.push(ASTNode.CaseClause(null, null, consequent, defaultToken)); // null pattern and guard for default
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (cases.length === 0) {
|
|
39
|
+
parser.error("Match statement must have at least one 'case' or 'default' clause.", matchToken, 'SYN085A', "Add at least one 'case ... : ...' or 'default: ...' block.");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
parser.expectKeyword("end", 'SYN086', 'Expected "end" keyword to close match statement.');
|
|
43
|
+
return ASTNode.MatchStatement(discriminant, cases, matchToken);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function parsePattern(parser) {
|
|
47
|
+
const token = parser.peek();
|
|
48
|
+
|
|
49
|
+
if (token?.type === TokenType.LBracket) {
|
|
50
|
+
// Array pattern: [a, b] or [1, 2, 3]
|
|
51
|
+
const bracketToken = parser.expect(TokenType.LBracket, undefined, 'SYN087', 'Expected an opening square bracket to start an array pattern.');
|
|
52
|
+
const elements = [];
|
|
53
|
+
|
|
54
|
+
if (parser.peek()?.type !== TokenType.RBracket) {
|
|
55
|
+
do {
|
|
56
|
+
elements.push(parsePatternElement(parser));
|
|
57
|
+
} while (parser.match(TokenType.Comma));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
parser.expect(TokenType.RBracket, undefined, 'SYN088', 'Expected a closing square bracket to end an array pattern.');
|
|
61
|
+
return ASTNode.ArrayPattern(elements, bracketToken);
|
|
62
|
+
}
|
|
63
|
+
// Simple pattern: literal, identifier
|
|
64
|
+
return parsePatternElement(parser);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function parsePatternElement(parser) {
|
|
68
|
+
const token = parser.peek();
|
|
69
|
+
if (!token) {
|
|
70
|
+
parser.error("Unexpected end of input while parsing pattern element.", parser.peek(-1) || parser.tokens[0], 'SYN088A', "A pattern element (literal, identifier, or array) is expected here.");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
switch (token.type) {
|
|
74
|
+
case TokenType.LBracket: // Nested array pattern
|
|
75
|
+
{
|
|
76
|
+
const bracketToken = parser.expect(TokenType.LBracket, undefined, 'SYN089', 'Expected an opening square bracket for nested array pattern.');
|
|
77
|
+
const elements = [];
|
|
78
|
+
if (parser.peek()?.type !== TokenType.RBracket) {
|
|
79
|
+
do {
|
|
80
|
+
elements.push(parsePatternElement(parser));
|
|
81
|
+
} while (parser.match(TokenType.Comma));
|
|
82
|
+
}
|
|
83
|
+
parser.expect(TokenType.RBracket, undefined, 'SYN090', 'Expected a closing square bracket for nested array pattern.');
|
|
84
|
+
return ASTNode.ArrayPattern(elements, bracketToken);
|
|
85
|
+
}
|
|
86
|
+
case TokenType.Number:
|
|
87
|
+
{
|
|
88
|
+
const numToken = parser.consume();
|
|
89
|
+
// Ensure the number is an integer for pattern matching if required by language spec
|
|
90
|
+
// For now, allowing any number literal.
|
|
91
|
+
return ASTNode.Literal(Number.parseFloat(numToken.value), numToken); // Or parseInt if only integers allowed
|
|
92
|
+
}
|
|
93
|
+
case TokenType.String:
|
|
94
|
+
{
|
|
95
|
+
const strToken = parser.consume();
|
|
96
|
+
return ASTNode.Literal(strToken.value, strToken);
|
|
97
|
+
}
|
|
98
|
+
case TokenType.Boolean: // Lexer produces this for true/false literals
|
|
99
|
+
{
|
|
100
|
+
const boolToken = parser.consume();
|
|
101
|
+
return ASTNode.Literal(boolToken.value === "true" || boolToken.value === true, boolToken);
|
|
102
|
+
}
|
|
103
|
+
case TokenType.Null: // Lexer produces this for null literal
|
|
104
|
+
{
|
|
105
|
+
const nullToken = parser.consume();
|
|
106
|
+
return ASTNode.Literal(null, nullToken);
|
|
107
|
+
}
|
|
108
|
+
case TokenType.Identifier:
|
|
109
|
+
{
|
|
110
|
+
// Could be a variable to bind or a named constant to match (e.g. if you have enums or consts)
|
|
111
|
+
// For now, assume it's a variable to bind.
|
|
112
|
+
const idToken = parser.consume();
|
|
113
|
+
return ASTNode.Identifier(idToken.value, idToken);
|
|
114
|
+
}
|
|
115
|
+
default:
|
|
116
|
+
parser.error(
|
|
117
|
+
`Unexpected token '${token.value}' (${token.type}) in pattern. Expected a literal, identifier, or array pattern.`,
|
|
118
|
+
token,
|
|
119
|
+
'SYN091',
|
|
120
|
+
'Patterns can be simple values (like 10, "hello", true), identifiers (to bind values), or array patterns (like [a, b]).'
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|