diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/ast/PbsAst.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/ast/PbsAst.java
index d8f13806..df69d499 100644
--- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/ast/PbsAst.java
+++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/ast/PbsAst.java
@@ -4,6 +4,10 @@ import p.studio.compiler.source.Span;
import p.studio.utilities.structures.ReadOnlyList;
public final class PbsAst {
+ public sealed interface Statement permits LetStatement, ReturnStatement, ExpressionStatement {
+ Span span();
+ }
+
private PbsAst() {
}
@@ -37,10 +41,6 @@ public final class PbsAst {
Span span) {
}
- public sealed interface Statement permits LetStatement, ReturnStatement, ExpressionStatement {
- Span span();
- }
-
public record LetStatement(
String name,
TypeRef explicitType,
diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsExprParser.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsExprParser.java
new file mode 100644
index 00000000..66dce85e
--- /dev/null
+++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsExprParser.java
@@ -0,0 +1,331 @@
+package p.studio.compiler.pbs.parser;
+
+import p.studio.compiler.pbs.ast.PbsAst;
+import p.studio.compiler.pbs.lexer.PbsToken;
+import p.studio.compiler.pbs.lexer.PbsTokenKind;
+import p.studio.compiler.source.Span;
+import p.studio.compiler.source.diagnostics.DiagnosticSink;
+import p.studio.compiler.source.identifiers.FileId;
+import p.studio.utilities.structures.ReadOnlyList;
+
+import java.util.ArrayList;
+
+/**
+ * Dedicated expression parser for PBS.
+ *
+ *
This keeps precedence handling separate from declaration parsing and makes
+ * the top-level parser easier to read and extend.
+ */
+final class PbsExprParser {
+ private final PbsTokenCursor cursor;
+ private final FileId fileId;
+ private final DiagnosticSink diagnostics;
+
+ PbsExprParser(
+ final PbsTokenCursor cursor,
+ final FileId fileId,
+ final DiagnosticSink diagnostics) {
+ this.cursor = cursor;
+ this.fileId = fileId;
+ this.diagnostics = diagnostics;
+ }
+
+ /**
+ * Entry point for expression parsing.
+ */
+ PbsAst.Expression parseExpression() {
+ return parseOr();
+ }
+
+ /**
+ * Parses left-associative logical-or expressions such as {@code a || b || c}.
+ */
+ private PbsAst.Expression parseOr() {
+ var expression = parseAnd();
+ while (cursor.match(PbsTokenKind.OR_OR)) {
+ final var operator = cursor.previous();
+ final var right = parseAnd();
+ expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
+ span(expression.span().getStart(), right.span().getEnd()));
+ }
+ return expression;
+ }
+
+ /**
+ * Parses left-associative logical-and expressions such as {@code a && b && c}.
+ */
+ private PbsAst.Expression parseAnd() {
+ var expression = parseEquality();
+ while (cursor.match(PbsTokenKind.AND_AND)) {
+ final var operator = cursor.previous();
+ final var right = parseEquality();
+ expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
+ span(expression.span().getStart(), right.span().getEnd()));
+ }
+ return expression;
+ }
+
+ /**
+ * Parses equality expressions and rejects chained non-associative forms.
+ *
+ *
Rejected: {@code a == b == c}
+ */
+ private PbsAst.Expression parseEquality() {
+ var expression = parseComparison();
+ if (cursor.match(PbsTokenKind.EQUAL_EQUAL, PbsTokenKind.BANG_EQUAL)) {
+ final var operator = cursor.previous();
+ final var right = parseComparison();
+ expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
+ span(expression.span().getStart(), right.span().getEnd()));
+ if (cursor.check(PbsTokenKind.EQUAL_EQUAL) || cursor.check(PbsTokenKind.BANG_EQUAL)) {
+ report(cursor.peek(), ParseErrors.E_PARSE_NON_ASSOC, "Chained equality is not allowed");
+ while (cursor.match(PbsTokenKind.EQUAL_EQUAL, PbsTokenKind.BANG_EQUAL)) {
+ parseComparison();
+ }
+ }
+ }
+ return expression;
+ }
+
+ /**
+ * Parses comparison expressions and rejects chained non-associative forms.
+ *
+ *
Rejected: {@code a < b < c}
+ */
+ private PbsAst.Expression parseComparison() {
+ var expression = parseTerm();
+ if (cursor.match(PbsTokenKind.LESS, PbsTokenKind.LESS_EQUAL, PbsTokenKind.GREATER, PbsTokenKind.GREATER_EQUAL)) {
+ final var operator = cursor.previous();
+ final var right = parseTerm();
+ expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
+ span(expression.span().getStart(), right.span().getEnd()));
+ if (cursor.check(PbsTokenKind.LESS) || cursor.check(PbsTokenKind.LESS_EQUAL)
+ || cursor.check(PbsTokenKind.GREATER) || cursor.check(PbsTokenKind.GREATER_EQUAL)) {
+ report(cursor.peek(), ParseErrors.E_PARSE_NON_ASSOC, "Chained comparison is not allowed");
+ while (cursor.match(PbsTokenKind.LESS, PbsTokenKind.LESS_EQUAL, PbsTokenKind.GREATER, PbsTokenKind.GREATER_EQUAL)) {
+ parseTerm();
+ }
+ }
+ }
+ return expression;
+ }
+
+ /**
+ * Parses additive expressions such as {@code a + b - c}.
+ */
+ private PbsAst.Expression parseTerm() {
+ var expression = parseFactor();
+ while (cursor.match(PbsTokenKind.PLUS, PbsTokenKind.MINUS)) {
+ final var operator = cursor.previous();
+ final var right = parseFactor();
+ expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
+ span(expression.span().getStart(), right.span().getEnd()));
+ }
+ return expression;
+ }
+
+ /**
+ * Parses multiplicative expressions such as {@code a * b / c % d}.
+ */
+ private PbsAst.Expression parseFactor() {
+ var expression = parseUnary();
+ while (cursor.match(PbsTokenKind.STAR, PbsTokenKind.SLASH, PbsTokenKind.PERCENT)) {
+ final var operator = cursor.previous();
+ final var right = parseUnary();
+ expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
+ span(expression.span().getStart(), right.span().getEnd()));
+ }
+ return expression;
+ }
+
+ /**
+ * Parses unary prefix operators such as {@code -x} and {@code !ready}.
+ */
+ private PbsAst.Expression parseUnary() {
+ if (cursor.match(PbsTokenKind.BANG, PbsTokenKind.MINUS)) {
+ final var operator = cursor.previous();
+ final var right = parseUnary();
+ return new PbsAst.UnaryExpr(
+ operator.lexeme(),
+ right,
+ span(operator.start(), right.span().getEnd()));
+ }
+ return parseCall();
+ }
+
+ /**
+ * Parses call chains after a primary expression.
+ *
+ *
();
+ if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) {
+ do {
+ arguments.add(parseExpression());
+ } while (cursor.match(PbsTokenKind.COMMA));
+ }
+ final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after arguments");
+ expression = new PbsAst.CallExpr(
+ expression,
+ ReadOnlyList.wrap(arguments),
+ span(expression.span().getStart(), close.end()));
+
+ // Avoid endless loops on malformed "f((" forms.
+ if (open.start() == close.start()) {
+ break;
+ }
+ }
+
+ return expression;
+ }
+
+ /**
+ * Parses primary expressions: literals, identifiers, and grouped expressions.
+ */
+ private PbsAst.Expression parsePrimary() {
+ if (cursor.match(PbsTokenKind.TRUE)) {
+ final var token = cursor.previous();
+ return new PbsAst.BoolLiteralExpr(true, span(token.start(), token.end()));
+ }
+ if (cursor.match(PbsTokenKind.FALSE)) {
+ final var token = cursor.previous();
+ return new PbsAst.BoolLiteralExpr(false, span(token.start(), token.end()));
+ }
+ if (cursor.match(PbsTokenKind.INT_LITERAL)) {
+ final var token = cursor.previous();
+ return new PbsAst.IntLiteralExpr(parseLongOrDefault(token.lexeme()), span(token.start(), token.end()));
+ }
+ if (cursor.match(PbsTokenKind.FLOAT_LITERAL)) {
+ final var token = cursor.previous();
+ return new PbsAst.FloatLiteralExpr(parseDoubleOrDefault(token.lexeme()), span(token.start(), token.end()));
+ }
+ if (cursor.match(PbsTokenKind.BOUNDED_LITERAL)) {
+ final var token = cursor.previous();
+ final var raw = token.lexeme().substring(0, Math.max(token.lexeme().length() - 1, 0));
+ return new PbsAst.BoundedLiteralExpr(parseIntOrDefault(raw), span(token.start(), token.end()));
+ }
+ if (cursor.match(PbsTokenKind.STRING_LITERAL)) {
+ final var token = cursor.previous();
+ return new PbsAst.StringLiteralExpr(unescapeString(token.lexeme()), span(token.start(), token.end()));
+ }
+ if (cursor.match(PbsTokenKind.IDENTIFIER)) {
+ final var token = cursor.previous();
+ return new PbsAst.IdentifierExpr(token.lexeme(), span(token.start(), token.end()));
+ }
+ if (cursor.match(PbsTokenKind.LEFT_PAREN)) {
+ final var open = cursor.previous();
+ final var expression = parseExpression();
+ final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after grouped expression");
+ return new PbsAst.GroupExpr(expression, span(open.start(), close.end()));
+ }
+
+ final var token = cursor.peek();
+ report(token, ParseErrors.E_PARSE_UNEXPECTED_TOKEN, "Unexpected token in expression: " + token.kind());
+ cursor.advance();
+ return new PbsAst.IntLiteralExpr(0L, span(token.start(), token.end()));
+ }
+
+ /**
+ * Consumes a required token and reports an error if it is missing.
+ *
+ * The parser advances on failure when possible so recovery can continue.
+ */
+ private PbsToken consume(final PbsTokenKind kind, final String message) {
+ if (cursor.check(kind)) {
+ return cursor.advance();
+ }
+ final var token = cursor.peek();
+ report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, message + ", found " + token.kind());
+ if (!cursor.isAtEnd()) {
+ return cursor.advance();
+ }
+ return token;
+ }
+
+ /**
+ * Builds a source span for the current file.
+ */
+ private Span span(final long start, final long end) {
+ return new Span(fileId, start, end);
+ }
+
+ /**
+ * Reports an expression parser diagnostic at the given token span.
+ */
+ private void report(final PbsToken token, final ParseErrors parseErrors, final String message) {
+ diagnostics.error(parseErrors.name(), message, new Span(fileId, token.start(), token.end()));
+ }
+
+ /**
+ * Parses an integer literal for AST construction and falls back to zero on malformed input.
+ */
+ private long parseLongOrDefault(final String text) {
+ try {
+ return Long.parseLong(text);
+ } catch (NumberFormatException ignored) {
+ return 0L;
+ }
+ }
+
+ /**
+ * Parses a bounded literal payload and falls back to zero on malformed input.
+ */
+ private int parseIntOrDefault(final String text) {
+ try {
+ return Integer.parseInt(text);
+ } catch (NumberFormatException ignored) {
+ return 0;
+ }
+ }
+
+ /**
+ * Parses a floating-point literal for AST construction and falls back to zero on malformed input.
+ */
+ private double parseDoubleOrDefault(final String text) {
+ try {
+ return Double.parseDouble(text);
+ } catch (NumberFormatException ignored) {
+ return 0.0;
+ }
+ }
+
+ /**
+ * Converts a quoted token lexeme such as {@code "\"hello\\n\""} into its unescaped runtime text.
+ */
+ private String unescapeString(final String lexeme) {
+ if (lexeme.length() < 2) {
+ return "";
+ }
+ final var raw = lexeme.substring(1, lexeme.length() - 1);
+ final var sb = new StringBuilder(raw.length());
+ for (int i = 0; i < raw.length(); i++) {
+ final char c = raw.charAt(i);
+ if (c != '\\' || i + 1 >= raw.length()) {
+ sb.append(c);
+ continue;
+ }
+ final char next = raw.charAt(++i);
+ switch (next) {
+ case 'n' -> sb.append('\n');
+ case 'r' -> sb.append('\r');
+ case 't' -> sb.append('\t');
+ case '"' -> sb.append('"');
+ case '\\' -> sb.append('\\');
+ default -> sb.append(next);
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java
index b4badd2f..fd81ac2b 100644
--- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java
+++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java
@@ -10,20 +10,25 @@ import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList;
+/**
+ * High-level manual parser for PBS source files.
+ *
+ *
This class focuses on declarations, blocks, statements, and error recovery.
+ * Expression precedence is delegated to {@link PbsExprParser}, while raw token
+ * navigation is delegated to {@link PbsTokenCursor}.
+ */
public final class PbsParser {
- private final ArrayList tokens;
+ private final PbsTokenCursor cursor;
+ private final PbsExprParser exprParser;
private final FileId fileId;
private final DiagnosticSink diagnostics;
- private int current;
private PbsParser(
final ReadOnlyList tokens,
final FileId fileId,
final DiagnosticSink diagnostics) {
- this.tokens = new ArrayList<>();
- for (final var token : tokens) {
- this.tokens.add(token);
- }
+ this.cursor = new PbsTokenCursor(tokens);
+ this.exprParser = new PbsExprParser(cursor, fileId, diagnostics);
this.fileId = fileId;
this.diagnostics = diagnostics;
}
@@ -53,33 +58,34 @@ public final class PbsParser {
private PbsAst.File parseFile() {
final var functions = new ArrayList();
- while (!isAtEnd()) {
- if (match(PbsTokenKind.IMPORT)) {
+ while (!cursor.isAtEnd()) {
+ if (cursor.match(PbsTokenKind.IMPORT)) {
parseAndDiscardImport();
continue;
}
- if (match(PbsTokenKind.FN)) {
- functions.add(parseFunction(previous()));
+ if (cursor.match(PbsTokenKind.FN)) {
+ functions.add(parseFunction(cursor.previous()));
continue;
}
- if (match(PbsTokenKind.MOD, PbsTokenKind.PUB)) {
- report(previous(), ParseErrors.E_PARSE_VISIBILITY_IN_SOURCE,
+ if (cursor.match(PbsTokenKind.MOD, PbsTokenKind.PUB)) {
+ report(cursor.previous(), ParseErrors.E_PARSE_VISIBILITY_IN_SOURCE,
"Visibility modifiers are barrel-only and cannot appear in .pbs declarations");
synchronizeTopLevel();
continue;
}
- if (check(PbsTokenKind.EOF)) {
+ if (cursor.check(PbsTokenKind.EOF)) {
break;
}
- report(peek(), ParseErrors.E_PARSE_UNEXPECTED_TOKEN, "Expected top-level declaration ('fn') or import");
+ report(cursor.peek(), ParseErrors.E_PARSE_UNEXPECTED_TOKEN,
+ "Expected top-level declaration ('fn') or import");
synchronizeTopLevel();
}
- final var eof = peek();
+ final var eof = cursor.peek();
return new PbsAst.File(ReadOnlyList.wrap(functions), span(0, eof.end()));
}
@@ -93,21 +99,17 @@ public final class PbsParser {
* }
*/
private void parseAndDiscardImport() {
- // Supports both forms:
- // import @core:math;
- // import { A, B as C } from @core:math;
- if (match(PbsTokenKind.LEFT_BRACE)) {
- while (!check(PbsTokenKind.RIGHT_BRACE) && !isAtEnd()) {
- if (match(PbsTokenKind.IDENTIFIER)) {
- if (match(PbsTokenKind.AS)) {
- consume(PbsTokenKind.IDENTIFIER,
- "Expected alias identifier after 'as'");
+ if (cursor.match(PbsTokenKind.LEFT_BRACE)) {
+ while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
+ if (cursor.match(PbsTokenKind.IDENTIFIER)) {
+ if (cursor.match(PbsTokenKind.AS)) {
+ consume(PbsTokenKind.IDENTIFIER, "Expected alias identifier after 'as'");
}
- match(PbsTokenKind.COMMA);
+ cursor.match(PbsTokenKind.COMMA);
continue;
}
- report(peek(), ParseErrors.E_PARSE_UNEXPECTED_TOKEN, "Invalid import item");
- advance();
+ report(cursor.peek(), ParseErrors.E_PARSE_UNEXPECTED_TOKEN, "Invalid import item");
+ cursor.advance();
}
consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' in import list");
consume(PbsTokenKind.FROM, "Expected 'from' in named import");
@@ -125,7 +127,7 @@ public final class PbsParser {
consume(PbsTokenKind.IDENTIFIER, "Expected project identifier in module reference");
consume(PbsTokenKind.COLON, "Expected ':' in module reference");
consume(PbsTokenKind.IDENTIFIER, "Expected module identifier");
- while (match(PbsTokenKind.SLASH)) {
+ while (cursor.match(PbsTokenKind.SLASH)) {
consume(PbsTokenKind.IDENTIFIER, "Expected module path segment after '/'");
}
}
@@ -145,28 +147,28 @@ public final class PbsParser {
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name");
final var parameters = new ArrayList();
- if (!check(PbsTokenKind.RIGHT_PAREN)) {
+ if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) {
do {
- final var pStart = peek();
- final var pName = consume(PbsTokenKind.IDENTIFIER, "Expected parameter name");
+ final var parameterStart = cursor.peek();
+ final var parameterName = consume(PbsTokenKind.IDENTIFIER, "Expected parameter name");
consume(PbsTokenKind.COLON, "Expected ':' after parameter name");
final var typeRef = parseTypeRef();
parameters.add(new PbsAst.Parameter(
- pName.lexeme(),
+ parameterName.lexeme(),
typeRef,
- span(pStart.start(), typeRef.span().getEnd())));
- } while (match(PbsTokenKind.COMMA));
+ span(parameterStart.start(), typeRef.span().getEnd())));
+ } while (cursor.match(PbsTokenKind.COMMA));
}
consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after parameter list");
PbsAst.TypeRef returnType = null;
- if (match(PbsTokenKind.COLON)) {
+ if (cursor.match(PbsTokenKind.COLON)) {
returnType = parseTypeRef();
}
PbsAst.Expression elseFallback = null;
- if (match(PbsTokenKind.ELSE)) {
- elseFallback = parseExpression();
+ if (cursor.match(PbsTokenKind.ELSE)) {
+ elseFallback = exprParser.parseExpression();
}
final var body = parseBlock();
@@ -201,7 +203,7 @@ public final class PbsParser {
private PbsAst.Block parseBlock() {
final var leftBrace = consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start block");
final var statements = new ArrayList();
- while (!check(PbsTokenKind.RIGHT_BRACE) && !isAtEnd()) {
+ while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
statements.add(parseStatement());
}
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end block");
@@ -211,14 +213,14 @@ public final class PbsParser {
/**
* Parses one statement inside a block.
*
- * The current slice supports `let`, `return`, and expression statements.
+ *
The current slice supports {@code let}, {@code return}, and expression statements.
*/
private PbsAst.Statement parseStatement() {
- if (match(PbsTokenKind.LET)) {
- return parseLetStatement(previous());
+ if (cursor.match(PbsTokenKind.LET)) {
+ return parseLetStatement(cursor.previous());
}
- if (match(PbsTokenKind.RETURN)) {
- return parseReturnStatement(previous());
+ if (cursor.match(PbsTokenKind.RETURN)) {
+ return parseReturnStatement(cursor.previous());
}
return parseExpressionStatement();
}
@@ -236,12 +238,12 @@ public final class PbsParser {
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected variable name");
PbsAst.TypeRef explicitType = null;
- if (match(PbsTokenKind.COLON)) {
+ if (cursor.match(PbsTokenKind.COLON)) {
explicitType = parseTypeRef();
}
consume(PbsTokenKind.EQUAL, "Expected '=' in let statement");
- final var initializer = parseExpression();
+ final var initializer = exprParser.parseExpression();
final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after let statement");
return new PbsAst.LetStatement(
@@ -256,8 +258,8 @@ public final class PbsParser {
*/
private PbsAst.Statement parseReturnStatement(final PbsToken returnToken) {
PbsAst.Expression value = null;
- if (!check(PbsTokenKind.SEMICOLON)) {
- value = parseExpression();
+ if (!cursor.check(PbsTokenKind.SEMICOLON)) {
+ value = exprParser.parseExpression();
}
final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after return");
return new PbsAst.ReturnStatement(value, span(returnToken.start(), semicolon.end()));
@@ -269,302 +271,45 @@ public final class PbsParser {
*
Example: {@code log(value);}
*/
private PbsAst.Statement parseExpressionStatement() {
- final var expression = parseExpression();
+ final var expression = exprParser.parseExpression();
final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after expression");
return new PbsAst.ExpressionStatement(expression, span(expression.span().getStart(), semicolon.end()));
}
- /**
- * Entry point for expression parsing.
- */
- private PbsAst.Expression parseExpression() {
- return parseOr();
- }
-
- /**
- * Parses left-associative logical-or expressions such as {@code a || b || c}.
- */
- private PbsAst.Expression parseOr() {
- var expression = parseAnd();
- while (match(PbsTokenKind.OR_OR)) {
- final var operator = previous();
- final var right = parseAnd();
- expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
- span(expression.span().getStart(), right.span().getEnd()));
- }
- return expression;
- }
-
- /**
- * Parses left-associative logical-and expressions such as {@code a && b && c}.
- */
- private PbsAst.Expression parseAnd() {
- var expression = parseEquality();
- while (match(PbsTokenKind.AND_AND)) {
- final var operator = previous();
- final var right = parseEquality();
- expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
- span(expression.span().getStart(), right.span().getEnd()));
- }
- return expression;
- }
-
- /**
- * Parses equality expressions and rejects chained non-associative forms.
- *
- *
Accepted: {@code a == b}
- *
Rejected: {@code a == b == c}
- */
- private PbsAst.Expression parseEquality() {
- var expression = parseComparison();
- if (match(PbsTokenKind.EQUAL_EQUAL, PbsTokenKind.BANG_EQUAL)) {
- final var operator = previous();
- final var right = parseComparison();
- expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
- span(expression.span().getStart(), right.span().getEnd()));
- if (check(PbsTokenKind.EQUAL_EQUAL) || check(PbsTokenKind.BANG_EQUAL)) {
- report(peek(), ParseErrors.E_PARSE_NON_ASSOC, "Chained equality is not allowed");
- while (match(PbsTokenKind.EQUAL_EQUAL, PbsTokenKind.BANG_EQUAL)) {
- parseComparison();
- }
- }
- }
- return expression;
- }
-
- /**
- * Parses comparison expressions and rejects chained non-associative forms.
- *
- *
Accepted: {@code a < b}
- *
Rejected: {@code a < b < c}
- */
- private PbsAst.Expression parseComparison() {
- var expression = parseTerm();
- if (match(PbsTokenKind.LESS, PbsTokenKind.LESS_EQUAL, PbsTokenKind.GREATER, PbsTokenKind.GREATER_EQUAL)) {
- final var operator = previous();
- final var right = parseTerm();
- expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
- span(expression.span().getStart(), right.span().getEnd()));
- if (check(PbsTokenKind.LESS) || check(PbsTokenKind.LESS_EQUAL)
- || check(PbsTokenKind.GREATER) || check(PbsTokenKind.GREATER_EQUAL)) {
- report(peek(), ParseErrors.E_PARSE_NON_ASSOC, "Chained comparison is not allowed");
- while (match(PbsTokenKind.LESS, PbsTokenKind.LESS_EQUAL, PbsTokenKind.GREATER, PbsTokenKind.GREATER_EQUAL)) {
- parseTerm();
- }
- }
- }
- return expression;
- }
-
- /**
- * Parses additive expressions such as {@code a + b - c}.
- */
- private PbsAst.Expression parseTerm() {
- var expression = parseFactor();
- while (match(PbsTokenKind.PLUS, PbsTokenKind.MINUS)) {
- final var operator = previous();
- final var right = parseFactor();
- expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
- span(expression.span().getStart(), right.span().getEnd()));
- }
- return expression;
- }
-
- /**
- * Parses multiplicative expressions such as {@code a * b / c % d}.
- */
- private PbsAst.Expression parseFactor() {
- var expression = parseUnary();
- while (match(PbsTokenKind.STAR, PbsTokenKind.SLASH, PbsTokenKind.PERCENT)) {
- final var operator = previous();
- final var right = parseUnary();
- expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
- span(expression.span().getStart(), right.span().getEnd()));
- }
- return expression;
- }
-
- /**
- * Parses unary prefix operators such as {@code -x} and {@code !ready}.
- */
- private PbsAst.Expression parseUnary() {
- if (match(PbsTokenKind.BANG, PbsTokenKind.MINUS)) {
- final var operator = previous();
- final var right = parseUnary();
- return new PbsAst.UnaryExpr(
- operator.lexeme(),
- right,
- span(operator.start(), right.span().getEnd()));
- }
- return parseCall();
- }
-
- /**
- * Parses call chains after a primary expression.
- *
- *
Examples:
- *
{@code
- * f()
- * sum(a, b)
- * factory()(1)
- * }
- */
- private PbsAst.Expression parseCall() {
- var expression = parsePrimary();
-
- while (match(PbsTokenKind.LEFT_PAREN)) {
- final var open = previous();
- final var arguments = new ArrayList();
- if (!check(PbsTokenKind.RIGHT_PAREN)) {
- do {
- arguments.add(parseExpression());
- } while (match(PbsTokenKind.COMMA));
- }
- final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after arguments");
- expression = new PbsAst.CallExpr(
- expression,
- ReadOnlyList.wrap(arguments),
- span(expression.span().getStart(), close.end()));
-
- // Avoid endless loops on malformed "f((" forms.
- if (open.start() == close.start()) {
- break;
- }
- }
-
- return expression;
- }
-
- /**
- * Parses primary expressions: literals, identifiers, and grouped expressions.
- */
- private PbsAst.Expression parsePrimary() {
- if (match(PbsTokenKind.TRUE)) {
- final var token = previous();
- return new PbsAst.BoolLiteralExpr(true, span(token.start(), token.end()));
- }
- if (match(PbsTokenKind.FALSE)) {
- final var token = previous();
- return new PbsAst.BoolLiteralExpr(false, span(token.start(), token.end()));
- }
- if (match(PbsTokenKind.INT_LITERAL)) {
- final var token = previous();
- return new PbsAst.IntLiteralExpr(parseLongOrDefault(token.lexeme()), span(token.start(), token.end()));
- }
- if (match(PbsTokenKind.FLOAT_LITERAL)) {
- final var token = previous();
- return new PbsAst.FloatLiteralExpr(parseDoubleOrDefault(token.lexeme()), span(token.start(), token.end()));
- }
- if (match(PbsTokenKind.BOUNDED_LITERAL)) {
- final var token = previous();
- final var raw = token.lexeme().substring(0, Math.max(token.lexeme().length() - 1, 0));
- return new PbsAst.BoundedLiteralExpr(parseIntOrDefault(raw), span(token.start(), token.end()));
- }
- if (match(PbsTokenKind.STRING_LITERAL)) {
- final var token = previous();
- return new PbsAst.StringLiteralExpr(unescapeString(token.lexeme()), span(token.start(), token.end()));
- }
- if (match(PbsTokenKind.IDENTIFIER)) {
- final var token = previous();
- return new PbsAst.IdentifierExpr(token.lexeme(), span(token.start(), token.end()));
- }
- if (match(PbsTokenKind.LEFT_PAREN)) {
- final var open = previous();
- final var expression = parseExpression();
- final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after grouped expression");
- return new PbsAst.GroupExpr(expression, span(open.start(), close.end()));
- }
-
- final var token = peek();
- report(token, ParseErrors.E_PARSE_UNEXPECTED_TOKEN, "Unexpected token in expression: " + token.kind());
- advance();
- return new PbsAst.IntLiteralExpr(0L, span(token.start(), token.end()));
- }
-
/**
* Skips tokens until a safe top-level restart point is reached.
*
* This allows the parser to continue reporting more than one diagnostic per file.
*/
private void synchronizeTopLevel() {
- while (!isAtEnd()) {
- if (check(PbsTokenKind.FN) || check(PbsTokenKind.IMPORT)) {
+ while (!cursor.isAtEnd()) {
+ if (cursor.check(PbsTokenKind.FN) || cursor.check(PbsTokenKind.IMPORT)) {
return;
}
- if (match(PbsTokenKind.SEMICOLON)) {
+ if (cursor.match(PbsTokenKind.SEMICOLON)) {
return;
}
- advance();
+ cursor.advance();
}
}
- /**
- * Consumes the next token if it matches any provided kind.
- */
- private boolean match(final PbsTokenKind... kinds) {
- for (final var kind : kinds) {
- if (check(kind)) {
- advance();
- return true;
- }
- }
- return false;
- }
-
/**
* Consumes a required token and reports an error if it is missing.
*
*
The parser advances on failure when possible so recovery can continue.
*/
private PbsToken consume(final PbsTokenKind kind, final String message) {
- if (check(kind)) {
- return advance();
+ if (cursor.check(kind)) {
+ return cursor.advance();
}
- final var token = peek();
+ final var token = cursor.peek();
report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, message + ", found " + token.kind());
- if (!isAtEnd()) {
- return advance();
+ if (!cursor.isAtEnd()) {
+ return cursor.advance();
}
return token;
}
- /**
- * Returns whether the current token matches the expected kind.
- */
- private boolean check(final PbsTokenKind kind) {
- if (isAtEnd()) return kind == PbsTokenKind.EOF;
- return peek().kind() == kind;
- }
-
- /**
- * Advances to the next token and returns the previously current token.
- */
- private PbsToken advance() {
- if (!isAtEnd()) current++;
- return previous();
- }
-
- /**
- * Returns whether the parser reached the synthetic EOF token.
- */
- private boolean isAtEnd() {
- return peek().kind() == PbsTokenKind.EOF;
- }
-
- /**
- * Returns the current token without consuming it.
- */
- private PbsToken peek() {
- return tokens.get(current);
- }
-
- /**
- * Returns the most recently consumed token.
- */
- private PbsToken previous() {
- return tokens.get(Math.max(current - 1, 0));
- }
-
/**
* Builds a source span for the current file.
*/
@@ -578,65 +323,4 @@ public final class PbsParser {
private void report(final PbsToken token, final ParseErrors parseErrors, final String message) {
diagnostics.error(parseErrors.name(), message, new Span(fileId, token.start(), token.end()));
}
-
- /**
- * Parses an integer literal for AST construction and falls back to zero on malformed input.
- */
- private long parseLongOrDefault(final String text) {
- try {
- return Long.parseLong(text);
- } catch (NumberFormatException ignored) {
- return 0L; // fallback
- }
- }
-
- /**
- * Parses a bounded literal payload and falls back to zero on malformed input.
- */
- private int parseIntOrDefault(final String text) {
- try {
- return Integer.parseInt(text);
- } catch (NumberFormatException ignored) {
- return 0; // fallback
- }
- }
-
- /**
- * Parses a floating-point literal for AST construction and falls back to zero on malformed input.
- */
- private double parseDoubleOrDefault(final String text) {
- try {
- return Double.parseDouble(text);
- } catch (NumberFormatException ignored) {
- return 0.0; // fallback
- }
- }
-
- /**
- * Converts a quoted token lexeme such as {@code "\"hello\\n\""} into its unescaped runtime text.
- */
- private String unescapeString(final String lexeme) {
- if (lexeme.length() < 2) {
- return "";
- }
- final var raw = lexeme.substring(1, lexeme.length() - 1);
- final var sb = new StringBuilder(raw.length());
- for (int i = 0; i < raw.length(); i++) {
- final char c = raw.charAt(i);
- if (c != '\\' || i + 1 >= raw.length()) {
- sb.append(c);
- continue;
- }
- final char next = raw.charAt(++i);
- switch (next) {
- case 'n' -> sb.append('\n');
- case 'r' -> sb.append('\r');
- case 't' -> sb.append('\t');
- case '"' -> sb.append('"');
- case '\\' -> sb.append('\\');
- default -> sb.append(next);
- }
- }
- return sb.toString();
- }
}
diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsTokenCursor.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsTokenCursor.java
new file mode 100644
index 00000000..df331330
--- /dev/null
+++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsTokenCursor.java
@@ -0,0 +1,74 @@
+package p.studio.compiler.pbs.parser;
+
+import p.studio.compiler.pbs.lexer.PbsToken;
+import p.studio.compiler.pbs.lexer.PbsTokenKind;
+import p.studio.utilities.structures.ReadOnlyList;
+
+import java.util.ArrayList;
+
+/**
+ * Lightweight cursor used by the manual parser to navigate the token stream.
+ *
+ *
This type keeps token access logic out of the higher-level parser so the
+ * declaration parser can read more like grammar and less like array plumbing.
+ */
+final class PbsTokenCursor {
+ private final ArrayList tokens = new ArrayList<>();
+ private int current;
+
+ PbsTokenCursor(final ReadOnlyList tokens) {
+ for (final var token : tokens) {
+ this.tokens.add(token);
+ }
+ }
+
+ /**
+ * Consumes the next token if it matches any provided kind.
+ */
+ boolean match(final PbsTokenKind... kinds) {
+ for (final var kind : kinds) {
+ if (check(kind)) {
+ advance();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether the current token matches the expected kind.
+ */
+ boolean check(final PbsTokenKind kind) {
+ if (isAtEnd()) return kind == PbsTokenKind.EOF;
+ return peek().kind() == kind;
+ }
+
+ /**
+ * Advances to the next token and returns the previously current token.
+ */
+ PbsToken advance() {
+ if (!isAtEnd()) current++;
+ return previous();
+ }
+
+ /**
+ * Returns whether the cursor reached the synthetic EOF token.
+ */
+ boolean isAtEnd() {
+ return peek().kind() == PbsTokenKind.EOF;
+ }
+
+ /**
+ * Returns the current token without consuming it.
+ */
+ PbsToken peek() {
+ return tokens.get(current);
+ }
+
+ /**
+ * Returns the most recently consumed token.
+ */
+ PbsToken previous() {
+ return tokens.get(Math.max(current - 1, 0));
+ }
+}
diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java
index 68513fe1..27757181 100644
--- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java
+++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java
@@ -8,10 +8,7 @@ import p.studio.compiler.models.IRFunction;
import p.studio.compiler.pbs.PbsFrontendCompiler;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.utilities.logs.LogAggregator;
-import p.studio.utilities.structures.ReadOnlyList;
-
-import java.util.ArrayList;
-import java.util.Comparator;
+import p.studio.utilities.structures.MutableList;
@Slf4j
public class PBSFrontendPhaseService implements FrontendPhaseService {
@@ -19,38 +16,40 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
@Override
public IRBackend compile(final FrontendPhaseContext ctx, final LogAggregator logs, final BuildingIssueSink issues) {
- final var functions = new ArrayList();
+ final MutableList functions = MutableList.create();
for (final var pId : ctx.stack.reverseTopologicalOrder) {
- final var fileIds = ctx.fileTable
- .getFiles(pId)
- .stream()
- .sorted(Comparator.comparing(fId -> ctx.fileTable.get(fId).getCanonPath().toString()))
- .toList();
+ final var fileIds = ctx.fileTable.getFiles(pId);
for (final var fId : fileIds) {
final var sourceHandle = ctx.fileTable.get(fId);
- if (!sourceHandle.getFilename().endsWith(".pbs")) {
- continue;
+ switch (sourceHandle.getExtension()) {
+ case "pbs": {
+ sourceHandle.readUtf8().ifPresentOrElse(
+ utf8Content -> {
+ final var diagnostics = DiagnosticSink.empty();
+ functions.addAll(frontendCompiler
+ .compileFile(fId, utf8Content, diagnostics)
+ .stream()
+ .toList());
+ adaptDiagnostics(sourceHandle.getCanonPath().toString(), diagnostics, issues);
+ },
+ () -> issues.report(builder -> builder
+ .error(true)
+ .message("Failed to read file content: %s".formatted(sourceHandle.toString()))));
+ } break;
+ case "barrel":
+ break;
+ default:
}
-
- sourceHandle.readUtf8().ifPresentOrElse(
- utf8Content -> {
- final var diagnostics = DiagnosticSink.empty();
- functions.addAll(frontendCompiler
- .compileFile(fId, utf8Content, diagnostics)
- .stream()
- .toList());
- adaptDiagnostics(sourceHandle.getCanonPath().toString(), diagnostics, issues);
- },
- () -> issues.report(builder -> builder
- .error(true)
- .message("Failed to read file content: %s".formatted(sourceHandle.toString()))));
}
}
logs.using(log).debug("PBS frontend lowered %d function(s) to IR".formatted(functions.size()));
- return new IRBackend(ReadOnlyList.wrap(functions));
+ return IRBackend
+ .builder()
+ .functions(functions.toReadOnlyList())
+ .build();
}
private void adaptDiagnostics(
diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/lexer/PbsLexerTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/lexer/PbsLexerTest.java
index bd82e346..3aa2b625 100644
--- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/lexer/PbsLexerTest.java
+++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/lexer/PbsLexerTest.java
@@ -11,7 +11,12 @@ class PbsLexerTest {
@Test
void shouldLexFunctionTokens() {
- final var source = "fn sum(a: int, b: int): int { return a + b; }";
+ final var source = """
+ fn sum(a: int, b: int): int
+ {
+ return a + b;
+ }
+ """;
final var diagnostics = DiagnosticSink.empty();
final var tokens = PbsLexer.lex(source, new FileId(0), diagnostics);
diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/parser/PbsParserTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/parser/PbsParserTest.java
index 5689f830..f88cbfab 100644
--- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/parser/PbsParserTest.java
+++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/parser/PbsParserTest.java
@@ -15,10 +15,11 @@ class PbsParserTest {
@Test
void shouldParseSingleFunction() {
final var source = """
- fn sum(a: int, b: int): int {
- return a + b;
- }
- """;
+ fn sum(a: int, b: int): int
+ {
+ return a + b;
+ }
+ """;
final var diagnostics = DiagnosticSink.empty();
final var fileId = new FileId(0);
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/FrontendPhasePipelineStage.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/FrontendPhasePipelineStage.java
index 2a1d5303..24916aa0 100644
--- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/FrontendPhasePipelineStage.java
+++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/FrontendPhasePipelineStage.java
@@ -25,6 +25,8 @@ public class FrontendPhasePipelineStage implements PipelineStage {
final var frontendPhaseContext = new FrontendPhaseContext(projectTable, fileTable, ctx.resolvedWorkspace.stack());
final var issues = BuildingIssueSink.empty();
ctx.irBackend = service.get().compile(frontendPhaseContext, logs, issues);
+ logs.using(log).debug("IR Backend: " + ctx.irBackend);
+ logs.using(log).debug("Frontend phase completed successfully!");
return issues;
}
}
diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/LoadSourcesPipelineStage.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/LoadSourcesPipelineStage.java
index b2c7e197..32a4af2c 100644
--- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/LoadSourcesPipelineStage.java
+++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/LoadSourcesPipelineStage.java
@@ -46,7 +46,7 @@ public class LoadSourcesPipelineStage implements PipelineStage {
try {
Files.walkFileTree(sourceRootPath, sourceCrawler);
- paths.sort(Path::compareTo); // do we really need this for deterministic builds?
+ paths.sort(Path::compareTo);
} catch (IOException e) {
issues.report(builder -> builder
.error(true)
diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/messages/BuildingIssueSink.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/messages/BuildingIssueSink.java
index 77e3b83c..4b1b6000 100644
--- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/messages/BuildingIssueSink.java
+++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/messages/BuildingIssueSink.java
@@ -3,31 +3,37 @@ package p.studio.compiler.messages;
import p.studio.utilities.structures.ReadOnlyCollection;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.function.Consumer;
-public class BuildingIssueSink extends ReadOnlyCollection {
+public class BuildingIssueSink implements ReadOnlyCollection {
+ private final ArrayList issues = new ArrayList<>();
private boolean hasErrors = false;
protected BuildingIssueSink() {
- super(new ArrayList<>());
}
public static BuildingIssueSink empty() {
return new BuildingIssueSink();
}
+ @Override
+ public Collection asCollection() {
+ return issues;
+ }
+
public BuildingIssueSink report(final Consumer consumer) {
final var builder = BuildingIssue.builder();
consumer.accept(builder);
final var issue = builder.build();
hasErrors |= issue.isError();
- collection.add(issue);
+ issues.add(issue);
return this;
}
public BuildingIssueSink merge(final BuildingIssueSink issues) {
hasErrors |= issues.hasErrors();
- collection.addAll(issues.collection);
+ this.issues.addAll(issues.issues);
return this;
}
diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/models/SourceHandle.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/models/SourceHandle.java
index c11b7796..ffde62b7 100644
--- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/models/SourceHandle.java
+++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/models/SourceHandle.java
@@ -2,6 +2,7 @@ package p.studio.compiler.models;
import lombok.EqualsAndHashCode;
import lombok.Getter;
+import org.apache.commons.io.FilenameUtils;
import p.studio.compiler.source.identifiers.ProjectId;
import p.studio.compiler.utilities.SourceProvider;
import p.studio.compiler.utilities.SourceProviderFactory;
@@ -26,6 +27,8 @@ public class SourceHandle {
@Getter
private final String filename;
@Getter
+ private final String extension;
+ @Getter
private final long size;
@Getter
private final long lastModified;
@@ -42,6 +45,7 @@ public class SourceHandle {
this.relativePath = relativePath;
this.canonPath = canonPath;
this.filename = canonPath.getFileName().toString();
+ this.extension = FilenameUtils.getExtension(this.filename);
this.size = size;
this.lastModified = lastModified;
this.provider = factory.create(canonPath);
diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/DiagnosticSink.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/DiagnosticSink.java
index 956cfdfd..f51929e2 100644
--- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/DiagnosticSink.java
+++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/DiagnosticSink.java
@@ -4,21 +4,27 @@ import p.studio.compiler.source.Span;
import p.studio.utilities.structures.ReadOnlyCollection;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Objects;
-public class DiagnosticSink extends ReadOnlyCollection {
+public class DiagnosticSink implements ReadOnlyCollection {
+ private final ArrayList diagnostics = new ArrayList<>();
private int errorCount = 0;
private int warningCount = 0;
protected DiagnosticSink() {
- super(new ArrayList<>());
}
public static DiagnosticSink empty() {
return new DiagnosticSink();
}
+ @Override
+ public Collection asCollection() {
+ return diagnostics;
+ }
+
public DiagnosticSink report(final Diagnostic diagnostic) {
Objects.requireNonNull(diagnostic);
if (diagnostic.getSeverity().isError()) {
@@ -26,7 +32,7 @@ public class DiagnosticSink extends ReadOnlyCollection {
} else {
warningCount++;
}
- collection.add(diagnostic);
+ diagnostics.add(diagnostic);
return this;
}
diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/FileTable.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/FileTable.java
index e0b35a29..b6949a9b 100644
--- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/FileTable.java
+++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/FileTable.java
@@ -6,9 +6,9 @@ import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.identifiers.ProjectId;
import p.studio.utilities.structures.ReadOnlyList;
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.List;
public class FileTable extends InternTable implements FileTableReader {
@@ -23,20 +23,20 @@ public class FileTable extends InternTable implements File
@Override
public FileId register(final SourceHandle value) {
final var fileId = super.register(value);
- projectFiles[value.getProjectId().getIndex()].set.add(fileId);
+ projectFiles[value.getProjectId().getIndex()].collection.add(fileId);
return fileId;
}
@Override
public ReadOnlyList getFiles(final ProjectId projectId) {
final var fileIds = projectFiles[projectId.getIndex()];
- if (CollectionUtils.isEmpty(fileIds.set)) {
+ if (CollectionUtils.isEmpty(fileIds.collection)) {
return ReadOnlyList.empty();
}
- return ReadOnlyList.wrap(fileIds.set);
+ return ReadOnlyList.wrap(fileIds.collection);
}
private static class FileIds {
- public final Set set = new HashSet<>();
+ public final List collection = new ArrayList<>();
}
}
\ No newline at end of file
diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java
index 846bfdcd..903758f5 100644
--- a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java
+++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java
@@ -1,19 +1,47 @@
package p.studio.compiler.models;
+import lombok.Builder;
+import lombok.Getter;
import p.studio.utilities.structures.ReadOnlyList;
+@Builder
+@Getter
public class IRBackend {
- private final ReadOnlyList functions;
+ @Builder.Default
+ private final ReadOnlyList functions = ReadOnlyList.empty();
- public IRBackend() {
- this(ReadOnlyList.empty());
+ /**
+ * Returns a stable human-readable representation useful for debug logs and tests.
+ */
+ @Override
+ public String toString() {
+ final var sb = new StringBuilder();
+ sb.append("IRBackend{functions=").append(functions.size()).append('}');
+
+ if (functions.isEmpty()) {
+ return sb.toString();
+ }
+
+ sb.append(System.lineSeparator());
+ for (final var function : functions) {
+ sb.append(" - fn ")
+ .append(function.name())
+ .append('(')
+ .append(function.parameterCount())
+ .append(" params)")
+ .append(" returnType=")
+ .append(function.hasExplicitReturnType() ? "explicit" : "inferred")
+ .append(" fileId=")
+ .append(function.fileId())
+ .append(" span=[")
+ .append(function.span().getStart())
+ .append(',')
+ .append(function.span().getEnd())
+ .append(']')
+ .append(System.lineSeparator());
+ }
+
+ return sb.toString().trim();
}
- public IRBackend(final ReadOnlyList functions) {
- this.functions = functions == null ? ReadOnlyList.empty() : functions;
- }
-
- public ReadOnlyList getFunctions() {
- return functions;
- }
}
diff --git a/prometeu-infra/src/main/java/p/studio/utilities/structures/MutableList.java b/prometeu-infra/src/main/java/p/studio/utilities/structures/MutableList.java
new file mode 100644
index 00000000..16a09d3f
--- /dev/null
+++ b/prometeu-infra/src/main/java/p/studio/utilities/structures/MutableList.java
@@ -0,0 +1,240 @@
+package p.studio.utilities.structures;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+public interface MutableList extends ReadOnlyCollection, List {
+ static MutableList create() {
+ return wrapInternal(new ArrayList<>());
+ }
+
+ static MutableList withCapacity(final int initialCapacity) {
+ return wrapInternal(new ArrayList<>(initialCapacity));
+ }
+
+ static MutableList wrap(final Collection c) {
+ if (c == null || c.isEmpty()) {
+ return MutableList.create();
+ }
+ return wrapInternal(new ArrayList<>(c));
+ }
+
+ @SafeVarargs
+ static MutableList from(final T... values) {
+ if (values == null || values.length == 0) {
+ return MutableList.create();
+ }
+ return MutableList.wrap(Stream.of(values).toList());
+ }
+
+ private static MutableList wrapInternal(final List list) {
+ return new MutableList<>() {
+ @Override
+ public List asList() {
+ return list;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o == this) return true;
+ if (o instanceof MutableList> other) {
+ return elementsEqual(other);
+ }
+ if (o instanceof ReadOnlyList> otherReadOnly) {
+ return elementsEqual(otherReadOnly);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return list.hashCode();
+ }
+ };
+ }
+
+ List asList();
+
+ @Override
+ default Collection asCollection() {
+ return asList();
+ }
+
+ @Override
+ default int size() {
+ return asList().size();
+ }
+
+ @Override
+ default boolean isEmpty() {
+ return asList().isEmpty();
+ }
+
+ @Override
+ default boolean contains(final Object o) {
+ return asList().contains(o);
+ }
+
+ @Override
+ default java.util.Iterator iterator() {
+ return asList().iterator();
+ }
+
+ @Override
+ default java.util.Spliterator spliterator() {
+ return asList().spliterator();
+ }
+
+ @Override
+ default Object[] toArray() {
+ return asList().toArray();
+ }
+
+ @Override
+ default T1[] toArray(final T1[] a) {
+ return asList().toArray(a);
+ }
+
+ @Override
+ default boolean add(final T t) {
+ return asList().add(t);
+ }
+
+ @Override
+ default boolean remove(final Object o) {
+ return asList().remove(o);
+ }
+
+ @Override
+ default boolean containsAll(final Collection> c) {
+ return asList().containsAll(c);
+ }
+
+ @Override
+ default boolean addAll(final Collection extends T> c) {
+ return asList().addAll(c);
+ }
+
+ @Override
+ default boolean addAll(final int index, final Collection extends T> c) {
+ return asList().addAll(index, c);
+ }
+
+ @Override
+ default boolean removeAll(final Collection> c) {
+ return asList().removeAll(c);
+ }
+
+ @Override
+ default boolean retainAll(final Collection> c) {
+ return asList().retainAll(c);
+ }
+
+ @Override
+ default void clear() {
+ asList().clear();
+ }
+
+ @Override
+ default T get(final int index) {
+ return asList().get(index);
+ }
+
+ @Override
+ default T set(final int index, final T element) {
+ return asList().set(index, element);
+ }
+
+ @Override
+ default void add(final int index, final T element) {
+ asList().add(index, element);
+ }
+
+ @Override
+ default T remove(final int index) {
+ return asList().remove(index);
+ }
+
+ @Override
+ default int indexOf(final Object o) {
+ return asList().indexOf(o);
+ }
+
+ @Override
+ default int lastIndexOf(final Object o) {
+ return asList().lastIndexOf(o);
+ }
+
+ @Override
+ default ListIterator listIterator() {
+ return asList().listIterator();
+ }
+
+ @Override
+ default ListIterator listIterator(final int index) {
+ return asList().listIterator(index);
+ }
+
+ @Override
+ default List subList(final int fromIndex, final int toIndex) {
+ return asList().subList(fromIndex, toIndex);
+ }
+
+ @Override
+ default Stream stream() {
+ return asList().stream();
+ }
+
+ default T getFirst() {
+ return asList().getFirst();
+ }
+
+ default T getLast() {
+ return asList().getLast();
+ }
+
+ default MutableList mutableSubList(final int fromIndex, final int toIndex) {
+ return MutableList.wrap(subList(fromIndex, toIndex));
+ }
+
+ default ReadOnlyList toReadOnlyList() {
+ return ReadOnlyList.wrap(asList());
+ }
+
+ default ReadOnlyList immutableSubList(final int fromIndex, final int toIndex) {
+ return ReadOnlyList.wrap(subList(fromIndex, toIndex));
+ }
+
+ default ReadOnlyList immutableSort(final java.util.Comparator super T> c) {
+ if (c == null) {
+ return ReadOnlyList.wrap(asList().stream().sorted().toList());
+ }
+ return ReadOnlyList.wrap(asList().stream().sorted(c).toList());
+ }
+
+ default ReadOnlyList invert() {
+ final var inverted = new ArrayList<>(asList());
+ java.util.Collections.reverse(inverted);
+ return ReadOnlyList.wrap(inverted);
+ }
+
+ default boolean elementsEqual(final ReadOnlyList> other) {
+ if (other == null || other.size() != size()) {
+ return false;
+ }
+ for (int i = 0; i < size(); i++) {
+ if (!Objects.equals(get(i), other.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ default boolean elementsEqual(final MutableList> other) {
+ return elementsEqual(other == null ? null : other.toReadOnlyList());
+ }
+}
diff --git a/prometeu-infra/src/main/java/p/studio/utilities/structures/ReadOnlyCollection.java b/prometeu-infra/src/main/java/p/studio/utilities/structures/ReadOnlyCollection.java
index 169a0758..b0699d23 100644
--- a/prometeu-infra/src/main/java/p/studio/utilities/structures/ReadOnlyCollection.java
+++ b/prometeu-infra/src/main/java/p/studio/utilities/structures/ReadOnlyCollection.java
@@ -1,94 +1,115 @@
package p.studio.utilities.structures;
-import java.util.*;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Spliterator;
import java.util.function.Function;
import java.util.function.Supplier;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
-public class ReadOnlyCollection implements Iterable {
- protected final Collection collection;
-
- protected ReadOnlyCollection(final Collection collection) {
- this.collection = collection;
+public interface ReadOnlyCollection extends Iterable {
+ static ReadOnlyCollection empty() {
+ return wrap(List.of());
}
- public static ReadOnlyCollection empty() {
- return ReadOnlyCollection.wrap(List.of());
- }
-
- public static ReadOnlyCollection wrap(final Collection collection) {
- return new ReadOnlyCollection<>(collection);
+ static ReadOnlyCollection wrap(final Collection collection) {
+ if (collection == null || collection.isEmpty()) {
+ return wrapInternal(List.of());
+ }
+ return wrapInternal(List.copyOf(collection));
}
@SafeVarargs
- public static ReadOnlyCollection with(final T... ts) {
- if (ts == null) return ReadOnlyCollection.empty();
- if (ts.length == 0) return ReadOnlyCollection.empty();
- return new ReadOnlyCollection<>(Stream.of(ts).toList());
+ static ReadOnlyCollection with(final T... values) {
+ if (values == null || values.length == 0) {
+ return ReadOnlyCollection.empty();
+ }
+ return ReadOnlyCollection.wrap(Stream.of(values).toList());
}
- public static boolean isEmpty(final ReadOnlyCollection c) {
- if (c == null) return true;
- return c.isEmpty();
+ static boolean isEmpty(final ReadOnlyCollection c) {
+ return c == null || c.isEmpty();
}
- public static boolean isNotEmpty(final ReadOnlyCollection c) {
+ static boolean isNotEmpty(final ReadOnlyCollection c) {
return !isEmpty(c);
}
- public int size() {
- return collection.size();
+ private static ReadOnlyCollection wrapInternal(final Collection collection) {
+ return new ReadOnlyCollection<>() {
+ @Override
+ public Collection asCollection() {
+ return collection;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o == this) return true;
+ if (o instanceof ReadOnlyCollection> other) {
+ return asCollection().equals(other.asCollection());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return collection.hashCode();
+ }
+ };
}
- public boolean isEmpty() {
- return collection.isEmpty();
+ Collection asCollection();
+
+ default int size() {
+ return asCollection().size();
}
- public boolean contains(final T o) {
- return collection.contains(o);
+ default boolean isEmpty() {
+ return asCollection().isEmpty();
}
- public boolean containsAll(final ReadOnlyCollection c) {
- return c.stream().allMatch(collection::contains);
+ default boolean contains(final Object o) {
+ return asCollection().contains(o);
}
- public boolean containsAny(final ReadOnlyCollection c) {
- return c.stream().anyMatch(collection::contains);
+ default boolean containsAll(final ReadOnlyCollection c) {
+ if (ReadOnlyCollection.isEmpty(c)) {
+ return true;
+ }
+ return c.stream().allMatch(this::contains);
}
- public Iterator iterator() {
- return collection.iterator();
+ default boolean containsAny(final ReadOnlyCollection c) {
+ if (ReadOnlyCollection.isEmpty(c)) {
+ return false;
+ }
+ return c.stream().anyMatch(this::contains);
}
- public Stream stream() {
- return collection.stream();
+ @Override
+ default Iterator iterator() {
+ return asCollection().iterator();
}
- public Stream map(final Function mapper) {
+ @Override
+ default Spliterator spliterator() {
+ return asCollection().spliterator();
+ }
+
+ default Stream stream() {
+ return asCollection().stream();
+ }
+
+ default Stream map(final Function mapper) {
return stream().map(mapper);
}
- public T[] toArray(final Supplier factory) {
- return collection.toArray(factory.get());
+ default T[] toArray(final Supplier factory) {
+ return asCollection().toArray(factory.get());
}
- @Override
- public boolean equals(final Object o) {
- if (o == this) return true;
- if (o == null) return false;
- if (o instanceof ReadOnlyCollection> c) {
- return c.collection.equals(collection);
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return collection.hashCode();
- }
-
- public Spliterator spliterator() {
- return collection.spliterator();
+ default MutableList toMutableList() {
+ return MutableList.wrap(asCollection());
}
}
diff --git a/prometeu-infra/src/main/java/p/studio/utilities/structures/ReadOnlyList.java b/prometeu-infra/src/main/java/p/studio/utilities/structures/ReadOnlyList.java
index 97a55eec..ecfa30e8 100644
--- a/prometeu-infra/src/main/java/p/studio/utilities/structures/ReadOnlyList.java
+++ b/prometeu-infra/src/main/java/p/studio/utilities/structures/ReadOnlyList.java
@@ -1,92 +1,126 @@
package p.studio.utilities.structures;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Objects;
import java.util.stream.Stream;
-public class ReadOnlyList extends ReadOnlyCollection {
- private ReadOnlyList(final List list) {
- super(list);
+public interface ReadOnlyList extends ReadOnlyCollection {
+ static ReadOnlyList empty() {
+ return wrapInternal(List.of());
}
- public static ReadOnlyList empty() {
- return ReadOnlyList.wrap(List.of());
- }
-
- public static ReadOnlyList wrap(final List list) {
- return new ReadOnlyList<>(list);
- }
-
- public static ReadOnlyList wrap(final ReadOnlyCollection c) {
- if (ReadOnlyCollection.isEmpty(c)) return ReadOnlyList.empty();
- return ReadOnlyList.wrap(new ArrayList<>(c.collection));
- }
-
- public static ReadOnlyList wrap(final Set set) {
- return new ReadOnlyList<>(List.copyOf(set));
+ static ReadOnlyList wrap(final Collection c) {
+ if (c == null || c.isEmpty()) {
+ return ReadOnlyList.empty();
+ }
+ return wrapInternal(List.copyOf(c));
}
@SafeVarargs
- public static ReadOnlyList from(final T... values) {
- if (values == null) return ReadOnlyList.empty();
- if (values.length == 0) return ReadOnlyList.empty();
+ static ReadOnlyList from(final T... values) {
+ if (values == null || values.length == 0) {
+ return ReadOnlyList.empty();
+ }
return ReadOnlyList.wrap(Stream.of(values).toList());
}
+ private static ReadOnlyList wrapInternal(final List list) {
+ return new ReadOnlyList<>() {
+ @Override
+ public List asList() {
+ return list;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o == this) return true;
+ if (o instanceof ReadOnlyList> other) {
+ return elementsEqual(other);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return list.hashCode();
+ }
+ };
+ }
+
+ List asList();
+
@Override
- public boolean equals(final Object o) {
- if (o == this) return true;
- if (o == null) return false;
- if (o instanceof ReadOnlyList> c) {
- return c.collection.equals(collection);
+ default Collection asCollection() {
+ return asList();
+ }
+
+ default T get(final int index) {
+ return asList().get(index);
+ }
+
+ default int indexOf(final Object o) {
+ return asList().indexOf(o);
+ }
+
+ default int lastIndexOf(final Object o) {
+ return asList().lastIndexOf(o);
+ }
+
+ default ListIterator listIterator() {
+ return asList().listIterator();
+ }
+
+ default ListIterator listIterator(final int index) {
+ return asList().listIterator(index);
+ }
+
+ default ReadOnlyList immutableSubList(final int fromIndex, final int toIndex) {
+ return ReadOnlyList.wrap(asList().subList(fromIndex, toIndex));
+ }
+
+ default T getFirst() {
+ return asList().getFirst();
+ }
+
+ default T getLast() {
+ return asList().getLast();
+ }
+
+ default ReadOnlyList immutableSort(final Comparator super T> c) {
+ if (c == null) {
+ return ReadOnlyList.wrap(asList().stream().sorted().toList());
}
- return false;
+ return ReadOnlyList.wrap(asList().stream().sorted(c).toList());
}
- public T get(final int index) {
- return ((List)collection).get(index);
- }
-
- public int indexOf(final T o) {
- return ((List)collection).indexOf(o);
- }
-
- public int lastIndexOf(final T o) {
- return ((List)collection).lastIndexOf(o);
- }
-
- public ListIterator listIterator() {
- return ((List)collection).listIterator();
- }
-
- public ListIterator listIterator(final int index) {
- return ((List)collection).listIterator(index);
- }
-
- public ReadOnlyList subList(final int fromIndex, final int toIndex) {
- return ReadOnlyList.wrap(((List)collection).subList(fromIndex, toIndex));
- }
-
- public T getFirst() {
- return ((List)collection).getFirst();
- }
-
- public T getLast() {
- return ((List)collection).getLast();
- }
-
- public ReadOnlyList sort(final Comparator super T> c) {
- Stream s = collection.stream();
- if (Objects.isNull(c)) {
- s = s.sorted();
- } else {
- s = s.sorted(c);
- }
- return ReadOnlyList.wrap(s.toList());
- }
-
- public ReadOnlyList invert() {
- final var inverted = new ArrayList<>(collection);
+ default ReadOnlyList invert() {
+ final var inverted = new ArrayList<>(asList());
java.util.Collections.reverse(inverted);
return ReadOnlyList.wrap(inverted);
}
+
+ default Stream stream() {
+ return asList().stream();
+ }
+
+ @Override
+ default MutableList toMutableList() {
+ return MutableList.wrap(asList());
+ }
+
+ default boolean elementsEqual(final ReadOnlyList> other) {
+ if (other == null || other.size() != size()) {
+ return false;
+ }
+ for (int i = 0; i < size(); i++) {
+ if (!Objects.equals(get(i), other.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/prometeu-infra/src/main/java/p/studio/utilities/structures/ReadOnlySet.java b/prometeu-infra/src/main/java/p/studio/utilities/structures/ReadOnlySet.java
index a4e2ccb5..7b8fdba6 100644
--- a/prometeu-infra/src/main/java/p/studio/utilities/structures/ReadOnlySet.java
+++ b/prometeu-infra/src/main/java/p/studio/utilities/structures/ReadOnlySet.java
@@ -1,36 +1,69 @@
package p.studio.utilities.structures;
+import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-public class ReadOnlySet extends ReadOnlyCollection {
- protected ReadOnlySet(final Set set) {
- super(set);
+public interface ReadOnlySet extends ReadOnlyCollection {
+ static ReadOnlySet empty() {
+ return wrapInternal(Set.of());
}
- public static ReadOnlySet empty() {
- return ReadOnlySet.wrap(Set.of());
+ static ReadOnlySet wrap(final Set set) {
+ if (set == null || set.isEmpty()) {
+ return ReadOnlySet.empty();
+ }
+ return wrapInternal(Set.copyOf(set));
}
- public static ReadOnlySet wrap(final Set set) {
- return new ReadOnlySet<>(set);
+ static ReadOnlySet wrap(final Collection collection) {
+ if (collection == null || collection.isEmpty()) {
+ return ReadOnlySet.empty();
+ }
+ return wrapInternal(Set.copyOf(collection));
}
@SafeVarargs
- public static ReadOnlySet from(final T... values) {
- if (values == null) return ReadOnlySet.empty();
- if (values.length == 0) return ReadOnlySet.empty();
+ static ReadOnlySet from(final T... values) {
+ if (values == null || values.length == 0) {
+ return ReadOnlySet.empty();
+ }
return ReadOnlySet.wrap(Stream.of(values).collect(Collectors.toSet()));
}
+ private static ReadOnlySet wrapInternal(final Set set) {
+ return new ReadOnlySet<>() {
+ @Override
+ public Set asSet() {
+ return set;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o == this) return true;
+ if (o instanceof ReadOnlySet> other) {
+ return asSet().equals(other.asSet());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return set.hashCode();
+ }
+ };
+ }
+
+ Set asSet();
+
@Override
- public boolean equals(final Object o) {
- if (o == this) return true;
- if (o == null) return false;
- if (o instanceof ReadOnlySet> c) {
- return c.collection.equals(collection);
- }
- return false;
+ default Collection asCollection() {
+ return asSet();
+ }
+
+ @Override
+ default Stream stream() {
+ return asSet().stream();
}
}
diff --git a/test-projects/main/src/module-a/source-1.pbs b/test-projects/main/src/module-a/source-1.pbs
index fde78521..63671dea 100644
--- a/test-projects/main/src/module-a/source-1.pbs
+++ b/test-projects/main/src/module-a/source-1.pbs
@@ -1 +1,3 @@
-source-1.pbs content
\ No newline at end of file
+fn a1()
+{
+}
\ No newline at end of file
diff --git a/test-projects/main/src/module-a/source-2.pbs b/test-projects/main/src/module-a/source-2.pbs
index 0c9c3dc4..abba8a5b 100644
--- a/test-projects/main/src/module-a/source-2.pbs
+++ b/test-projects/main/src/module-a/source-2.pbs
@@ -1 +1,4 @@
-source-2.pbs content
\ No newline at end of file
+fn a2(v1: int, v2: bounded): int
+{
+ return v1 + v2;
+}
\ No newline at end of file
diff --git a/test-projects/main/src/module-a/source-3.pbs b/test-projects/main/src/module-a/source-3.pbs
index 324efdbc..956ab5a4 100644
--- a/test-projects/main/src/module-a/source-3.pbs
+++ b/test-projects/main/src/module-a/source-3.pbs
@@ -1 +1,4 @@
-source-3.pbs content
\ No newline at end of file
+fn a3(): int
+{
+ return 1;
+}
\ No newline at end of file