brett-compiler 1.0.2
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/dist/SemanticAnalyserVisitor.js +191 -0
- package/dist/SymbolTable.js +24 -0
- package/dist/SyntaxErrorListener.js +24 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +56 -0
- package/dist/model.js +7 -0
- package/package.json +30 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
const BrettVisitor = require('./generated/BrettVisitor').default;
|
|
2
|
+
const { SymbolKind } = require('./model.js');
|
|
3
|
+
const { SymbolTable } = require('./SymbolTable.js');
|
|
4
|
+
|
|
5
|
+
const parserModule = require('./generated/BrettParser');
|
|
6
|
+
const BrettParser = parserModule.BrettParser || parserModule.default || parserModule;
|
|
7
|
+
|
|
8
|
+
class SemanticAnalyzerVisitor extends BrettVisitor {
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
this.errors = [];
|
|
12
|
+
this.currentScope = new SymbolTable();
|
|
13
|
+
this.allScopes = [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
addError(message, token, severity = 'error') {
|
|
17
|
+
const line = token ? token.line : 0;
|
|
18
|
+
const column = token ? token.column + 1 : 0;
|
|
19
|
+
const length = token ? token.text.length : 1;
|
|
20
|
+
|
|
21
|
+
this.errors.push({
|
|
22
|
+
message,
|
|
23
|
+
line,
|
|
24
|
+
column,
|
|
25
|
+
type: severity === 'error' ? 'Semantic Error' : 'Warning',
|
|
26
|
+
length
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
checkAssignment(declaredType, actualType, token) {
|
|
31
|
+
// If either side is 'any', we assume it's valid (or already an error)
|
|
32
|
+
if (declaredType === 'any' || actualType === 'any') {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Strict string comparison
|
|
37
|
+
if (String(declaredType) !== String(actualType)) {
|
|
38
|
+
this.addError(
|
|
39
|
+
`Type '${actualType}' kan niet worden toegewezen aan type '${declaredType}'`,
|
|
40
|
+
token
|
|
41
|
+
);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getSymbols() {
|
|
48
|
+
const allSymbols = new Map();
|
|
49
|
+
this.allScopes.forEach(scope => {
|
|
50
|
+
if (scope.symbols) {
|
|
51
|
+
for (const [name, symbol] of scope.symbols.entries()) {
|
|
52
|
+
if (!allSymbols.has(name)) allSymbols.set(name, symbol);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
['string', 'number', 'bool', 'any'].forEach(t =>
|
|
57
|
+
allSymbols.set(t, { name: t, kind: 'Type', type: 'type' })
|
|
58
|
+
);
|
|
59
|
+
return Array.from(allSymbols.values());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
visitProgram(ctx) { this.visitChildren(ctx); }
|
|
63
|
+
|
|
64
|
+
visitBlock(ctx) {
|
|
65
|
+
this.currentScope = new SymbolTable(this.currentScope);
|
|
66
|
+
this.visitChildren(ctx);
|
|
67
|
+
this.allScopes.push(this.currentScope);
|
|
68
|
+
this.currentScope = this.currentScope.parent;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
visitFunctionDeclaration(ctx) {
|
|
72
|
+
const identifierNode = ctx.IDENTIFIER();
|
|
73
|
+
if (!identifierNode) return;
|
|
74
|
+
|
|
75
|
+
const name = identifierNode.getText();
|
|
76
|
+
const returnType = ctx.type()?.getText() ?? 'any';
|
|
77
|
+
|
|
78
|
+
if (!this.currentScope.define({ name, kind: SymbolKind.Function, type: returnType })) {
|
|
79
|
+
this.addError(`Functie '${name}' is al gedefinieerd.`, identifierNode.getSymbol());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.currentScope = new SymbolTable(this.currentScope);
|
|
83
|
+
if (ctx.parameterList()) this.visit(ctx.parameterList());
|
|
84
|
+
if (ctx.block()) this.visit(ctx.block());
|
|
85
|
+
this.currentScope = this.currentScope.parent;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
visitParameter(ctx) {
|
|
89
|
+
const identifierNode = ctx.IDENTIFIER();
|
|
90
|
+
if (!identifierNode) return;
|
|
91
|
+
const name = identifierNode.getText();
|
|
92
|
+
const type = ctx.type()?.getText() ?? 'any';
|
|
93
|
+
this.currentScope.define({ name, kind: SymbolKind.Parameter, type });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
visitVariableDeclaration(ctx) {
|
|
97
|
+
const identifierNode = ctx.IDENTIFIER();
|
|
98
|
+
if (!identifierNode) return;
|
|
99
|
+
|
|
100
|
+
const name = identifierNode.getText();
|
|
101
|
+
const declaredType = ctx.type()?.getText() ?? 'any';
|
|
102
|
+
const identifierToken = identifierNode.getSymbol();
|
|
103
|
+
|
|
104
|
+
if (!/^[a-z]/.test(name)) {
|
|
105
|
+
this.addError(`Variabele '${name}' moet met kleine letter beginnen.`, identifierToken, 'warning');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!this.currentScope.define({ name, kind: SymbolKind.Variable, type: declaredType })) {
|
|
109
|
+
this.addError(`Variabele '${name}' is al gedefinieerd.`, identifierToken);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (ctx.expression()) {
|
|
113
|
+
const exprType = this.visit(ctx.expression());
|
|
114
|
+
this.checkAssignment(declaredType, exprType, ctx.expression().start);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ================= EXPRESSION VISITORS =================
|
|
119
|
+
|
|
120
|
+
visitAssignmentExpression(ctx) {
|
|
121
|
+
const name = ctx.IDENTIFIER().getText();
|
|
122
|
+
const symbol = this.currentScope.resolve(name);
|
|
123
|
+
const token = ctx.IDENTIFIER().getSymbol();
|
|
124
|
+
|
|
125
|
+
if (!symbol) {
|
|
126
|
+
this.addError(`Variabele '${name}' is niet gevonden in deze scope.`, token);
|
|
127
|
+
} else if (symbol.kind !== SymbolKind.Variable && symbol.kind !== SymbolKind.Parameter) {
|
|
128
|
+
this.addError(`'${name}' is geen variabele.`, token);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const valType = this.visit(ctx.expression());
|
|
132
|
+
|
|
133
|
+
if (symbol) {
|
|
134
|
+
this.checkAssignment(symbol.type, valType, ctx.expression().start);
|
|
135
|
+
}
|
|
136
|
+
return valType;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
visitBinaryExpression(ctx) {
|
|
140
|
+
const leftType = this.visit(ctx.expression(0));
|
|
141
|
+
const rightType = this.visit(ctx.expression(1));
|
|
142
|
+
const operator = ctx.op.text;
|
|
143
|
+
|
|
144
|
+
if (leftType === 'number' && rightType === 'number') {
|
|
145
|
+
if (['<', '>', '<=', '>=', '==', '!='].includes(operator)) return 'bool';
|
|
146
|
+
return 'number';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (operator === '+' && (leftType === 'string' || rightType === 'string')) {
|
|
150
|
+
return 'string';
|
|
151
|
+
}
|
|
152
|
+
return 'any';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
visitIdentifierExpression(ctx) {
|
|
156
|
+
const name = ctx.IDENTIFIER().getText();
|
|
157
|
+
const symbol = this.currentScope.resolve(name);
|
|
158
|
+
|
|
159
|
+
if (!symbol) {
|
|
160
|
+
this.addError(`Variabele '${name}' is niet gevonden in deze scope.`, ctx.IDENTIFIER().getSymbol());
|
|
161
|
+
// Important: Return 'any' so checkAssignment logic above ignores it
|
|
162
|
+
return 'any';
|
|
163
|
+
}
|
|
164
|
+
return symbol.type;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
visitFunctionCallExpression(ctx) {
|
|
168
|
+
const name = ctx.IDENTIFIER().getText();
|
|
169
|
+
const symbol = this.currentScope.resolve(name);
|
|
170
|
+
|
|
171
|
+
if (!symbol || symbol.kind !== SymbolKind.Function) {
|
|
172
|
+
this.addError(`Functie '${name}' is niet gedefinieerd.`, ctx.IDENTIFIER().getSymbol());
|
|
173
|
+
return 'any';
|
|
174
|
+
}
|
|
175
|
+
if (ctx.argList()) this.visit(ctx.argList());
|
|
176
|
+
return symbol.type;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
visitLiteralExpression(ctx) {
|
|
180
|
+
if (ctx.NUMBER()) return 'number';
|
|
181
|
+
if (ctx.STRING()) return 'string';
|
|
182
|
+
if (ctx.getText() === 'true' || ctx.getText() === 'false') return 'bool';
|
|
183
|
+
return 'any';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
visitParenthesizedExpression(ctx) {
|
|
187
|
+
return this.visit(ctx.expression());
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = { SemanticAnalyzerVisitor };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const { SymbolKind } = require('./model.js');
|
|
2
|
+
|
|
3
|
+
class SymbolTable {
|
|
4
|
+
constructor(parent = null) {
|
|
5
|
+
this.symbols = new Map();
|
|
6
|
+
this.parent = parent;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
define(symbol) {
|
|
10
|
+
if (this.symbols.has(symbol.name)) return false;
|
|
11
|
+
this.symbols.set(symbol.name, symbol);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
resolve(name) {
|
|
16
|
+
const s = this.symbols.get(name);
|
|
17
|
+
if (s) return s;
|
|
18
|
+
// Cruciaal: De recursieve lookup in ouderlijke scopes
|
|
19
|
+
if (this.parent) return this.parent.resolve(name);
|
|
20
|
+
return null; // Triggert Fout 8 en 9 in de Visitor
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { SymbolTable };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class SyntaxErrorListener {
|
|
2
|
+
constructor(errorsList) {
|
|
3
|
+
this.errors = errorsList;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
syntaxError(recognizer, offendingSymbol, line, column, msg, e) {
|
|
7
|
+
const length = offendingSymbol ? offendingSymbol.text.length : 1;
|
|
8
|
+
|
|
9
|
+
this.errors.push({
|
|
10
|
+
type: 'Syntax Error',
|
|
11
|
+
message: msg,
|
|
12
|
+
line: line,
|
|
13
|
+
column: column + 1, // 1-based for the user
|
|
14
|
+
length: length // NEW: Store the token length
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Deze lege methodes zijn nodig om als listener te werken
|
|
19
|
+
reportAmbiguity() {}
|
|
20
|
+
reportAttemptingFullContext() {}
|
|
21
|
+
reportContextSensitivity() {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { SyntaxErrorListener };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
interface BrettSymbol {
|
|
3
|
+
name: string;
|
|
4
|
+
kind: 'Variable' | 'Function' | 'Type' | 'Parameter';
|
|
5
|
+
type: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
declare function compile(input: string): {
|
|
9
|
+
type: string;
|
|
10
|
+
message: string;
|
|
11
|
+
line: number;
|
|
12
|
+
column: number;
|
|
13
|
+
length: number;
|
|
14
|
+
}[];
|
|
15
|
+
|
|
16
|
+
declare function analyzeAndGetSymbols(input: string): BrettSymbol[];
|
|
17
|
+
|
|
18
|
+
export { compile, analyzeAndGetSymbols, BrettSymbol };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const antlr4 = require('antlr4');
|
|
2
|
+
|
|
3
|
+
const lexerModule = require('./generated/BrettLexer');
|
|
4
|
+
const parserModule = require('./generated/BrettParser');
|
|
5
|
+
|
|
6
|
+
const BrettLexer = lexerModule.BrettLexer || lexerModule.default || lexerModule;
|
|
7
|
+
const BrettParser = parserModule.BrettParser || parserModule.default || parserModule;
|
|
8
|
+
|
|
9
|
+
const { SemanticAnalyzerVisitor } = require('./SemanticAnalyserVisitor');
|
|
10
|
+
const { SyntaxErrorListener } = require('./SyntaxErrorListener');
|
|
11
|
+
|
|
12
|
+
function compile(input) {
|
|
13
|
+
const chars = new antlr4.InputStream(input);
|
|
14
|
+
const lexer = new BrettLexer(chars);
|
|
15
|
+
const tokens = new antlr4.CommonTokenStream(lexer);
|
|
16
|
+
const parser = new BrettParser(tokens);
|
|
17
|
+
|
|
18
|
+
const errors = [];
|
|
19
|
+
parser.removeErrorListeners();
|
|
20
|
+
parser.addErrorListener(new SyntaxErrorListener(errors));
|
|
21
|
+
|
|
22
|
+
const tree = parser.program();
|
|
23
|
+
|
|
24
|
+
const visitor = new SemanticAnalyzerVisitor();
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
visitor.visit(tree);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const allErrors = errors.concat(visitor.errors);
|
|
32
|
+
|
|
33
|
+
return allErrors;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function analyzeAndGetSymbols(input) {
|
|
37
|
+
const chars = new antlr4.InputStream(input);
|
|
38
|
+
const lexer = new BrettLexer(chars);
|
|
39
|
+
const tokens = new antlr4.CommonTokenStream(lexer);
|
|
40
|
+
const parser = new BrettParser(tokens);
|
|
41
|
+
|
|
42
|
+
parser.removeErrorListeners();
|
|
43
|
+
|
|
44
|
+
const tree = parser.program();
|
|
45
|
+
const visitor = new SemanticAnalyzerVisitor();
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
visitor.visit(tree);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error("Symbol analysis failed due to syntax error");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return visitor.getSymbols();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { compile, analyzeAndGetSymbols };
|
package/dist/model.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "brett-compiler",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Een compiler voor de .Brett taal",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"antlr4": "^4.13.1"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"antlr": "powershell -ExecutionPolicy Bypass -File generate-compiler.ps1",
|
|
15
|
+
"antlrci": "node generate-compiler.js",
|
|
16
|
+
"postinstall": "node generate-compiler.js",
|
|
17
|
+
"start": "node dist/index.js",
|
|
18
|
+
"test": "vitest",
|
|
19
|
+
"build": "npm run antlrci && node build.js",
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"release": "npm run build && standard-version && npm publish --access public"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [],
|
|
24
|
+
"author": "",
|
|
25
|
+
"license": "ISC",
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"standard-version": "^9.5.0",
|
|
28
|
+
"vitest": "^4.0.10"
|
|
29
|
+
}
|
|
30
|
+
}
|