added lexer and parser basics
This commit is contained in:
parent
833c6ec049
commit
d55ec48707
@ -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,
|
||||
|
||||
@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Accepted: {@code a == b}
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Accepted: {@code a < b}
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Examples:
|
||||
* <pre>{@code
|
||||
* f()
|
||||
* sum(a, b)
|
||||
* factory()(1)
|
||||
* }</pre>
|
||||
*/
|
||||
private PbsAst.Expression parseCall() {
|
||||
var expression = parsePrimary();
|
||||
|
||||
while (cursor.match(PbsTokenKind.LEFT_PAREN)) {
|
||||
final var open = cursor.previous();
|
||||
final var arguments = new ArrayList<PbsAst.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.
|
||||
*
|
||||
* <p>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();
|
||||
}
|
||||
}
|
||||
@ -10,20 +10,25 @@ import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* High-level manual parser for PBS source files.
|
||||
*
|
||||
* <p>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<PbsToken> tokens;
|
||||
private final PbsTokenCursor cursor;
|
||||
private final PbsExprParser exprParser;
|
||||
private final FileId fileId;
|
||||
private final DiagnosticSink diagnostics;
|
||||
private int current;
|
||||
|
||||
private PbsParser(
|
||||
final ReadOnlyList<PbsToken> 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<PbsAst.FunctionDecl>();
|
||||
|
||||
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 {
|
||||
* }</pre>
|
||||
*/
|
||||
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<PbsAst.Parameter>();
|
||||
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<PbsAst.Statement>();
|
||||
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.
|
||||
*
|
||||
* <p>The current slice supports `let`, `return`, and expression statements.
|
||||
* <p>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 {
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Accepted: {@code a == b}
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Accepted: {@code a < b}
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Examples:
|
||||
* <pre>{@code
|
||||
* f()
|
||||
* sum(a, b)
|
||||
* factory()(1)
|
||||
* }</pre>
|
||||
*/
|
||||
private PbsAst.Expression parseCall() {
|
||||
var expression = parsePrimary();
|
||||
|
||||
while (match(PbsTokenKind.LEFT_PAREN)) {
|
||||
final var open = previous();
|
||||
final var arguments = new ArrayList<PbsAst.Expression>();
|
||||
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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
*
|
||||
* <p>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<PbsToken> tokens = new ArrayList<>();
|
||||
private int current;
|
||||
|
||||
PbsTokenCursor(final ReadOnlyList<PbsToken> 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));
|
||||
}
|
||||
}
|
||||
@ -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<IRFunction>();
|
||||
final MutableList<IRFunction> 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(
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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<BuildingIssue> {
|
||||
public class BuildingIssueSink implements ReadOnlyCollection<BuildingIssue> {
|
||||
private final ArrayList<BuildingIssue> issues = new ArrayList<>();
|
||||
private boolean hasErrors = false;
|
||||
|
||||
protected BuildingIssueSink() {
|
||||
super(new ArrayList<>());
|
||||
}
|
||||
|
||||
public static BuildingIssueSink empty() {
|
||||
return new BuildingIssueSink();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BuildingIssue> asCollection() {
|
||||
return issues;
|
||||
}
|
||||
|
||||
public BuildingIssueSink report(final Consumer<BuildingIssue.BuildingIssueBuilder> 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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<Diagnostic> {
|
||||
public class DiagnosticSink implements ReadOnlyCollection<Diagnostic> {
|
||||
private final ArrayList<Diagnostic> 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<Diagnostic> 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<Diagnostic> {
|
||||
} else {
|
||||
warningCount++;
|
||||
}
|
||||
collection.add(diagnostic);
|
||||
diagnostics.add(diagnostic);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@ -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<FileId, SourceHandle> implements FileTableReader {
|
||||
|
||||
@ -23,20 +23,20 @@ public class FileTable extends InternTable<FileId, SourceHandle> 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<FileId> 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<FileId> set = new HashSet<>();
|
||||
public final List<FileId> collection = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
@ -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<IRFunction> functions;
|
||||
@Builder.Default
|
||||
private final ReadOnlyList<IRFunction> 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<IRFunction> functions) {
|
||||
this.functions = functions == null ? ReadOnlyList.empty() : functions;
|
||||
}
|
||||
|
||||
public ReadOnlyList<IRFunction> getFunctions() {
|
||||
return functions;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<T> extends ReadOnlyCollection<T>, List<T> {
|
||||
static <T> MutableList<T> create() {
|
||||
return wrapInternal(new ArrayList<>());
|
||||
}
|
||||
|
||||
static <T> MutableList<T> withCapacity(final int initialCapacity) {
|
||||
return wrapInternal(new ArrayList<>(initialCapacity));
|
||||
}
|
||||
|
||||
static <T> MutableList<T> wrap(final Collection<T> c) {
|
||||
if (c == null || c.isEmpty()) {
|
||||
return MutableList.create();
|
||||
}
|
||||
return wrapInternal(new ArrayList<>(c));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
static <T> MutableList<T> from(final T... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return MutableList.create();
|
||||
}
|
||||
return MutableList.wrap(Stream.of(values).toList());
|
||||
}
|
||||
|
||||
private static <T> MutableList<T> wrapInternal(final List<T> list) {
|
||||
return new MutableList<>() {
|
||||
@Override
|
||||
public List<T> 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<T> asList();
|
||||
|
||||
@Override
|
||||
default Collection<T> 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<T> iterator() {
|
||||
return asList().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
default java.util.Spliterator<T> spliterator() {
|
||||
return asList().spliterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
default Object[] toArray() {
|
||||
return asList().toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
default <T1> 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<T> listIterator() {
|
||||
return asList().listIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
default ListIterator<T> listIterator(final int index) {
|
||||
return asList().listIterator(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
default List<T> subList(final int fromIndex, final int toIndex) {
|
||||
return asList().subList(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
default Stream<T> stream() {
|
||||
return asList().stream();
|
||||
}
|
||||
|
||||
default T getFirst() {
|
||||
return asList().getFirst();
|
||||
}
|
||||
|
||||
default T getLast() {
|
||||
return asList().getLast();
|
||||
}
|
||||
|
||||
default MutableList<T> mutableSubList(final int fromIndex, final int toIndex) {
|
||||
return MutableList.wrap(subList(fromIndex, toIndex));
|
||||
}
|
||||
|
||||
default ReadOnlyList<T> toReadOnlyList() {
|
||||
return ReadOnlyList.wrap(asList());
|
||||
}
|
||||
|
||||
default ReadOnlyList<T> immutableSubList(final int fromIndex, final int toIndex) {
|
||||
return ReadOnlyList.wrap(subList(fromIndex, toIndex));
|
||||
}
|
||||
|
||||
default ReadOnlyList<T> 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<T> 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());
|
||||
}
|
||||
}
|
||||
@ -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<T> implements Iterable<T> {
|
||||
protected final Collection<T> collection;
|
||||
|
||||
protected ReadOnlyCollection(final Collection<T> collection) {
|
||||
this.collection = collection;
|
||||
public interface ReadOnlyCollection<T> extends Iterable<T> {
|
||||
static <T> ReadOnlyCollection<T> empty() {
|
||||
return wrap(List.of());
|
||||
}
|
||||
|
||||
public static <T> ReadOnlyCollection<T> empty() {
|
||||
return ReadOnlyCollection.wrap(List.of());
|
||||
}
|
||||
|
||||
public static <T> ReadOnlyCollection<T> wrap(final Collection<T> collection) {
|
||||
return new ReadOnlyCollection<>(collection);
|
||||
static <T> ReadOnlyCollection<T> wrap(final Collection<T> collection) {
|
||||
if (collection == null || collection.isEmpty()) {
|
||||
return wrapInternal(List.of());
|
||||
}
|
||||
return wrapInternal(List.copyOf(collection));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> ReadOnlyCollection<T> 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 <T> ReadOnlyCollection<T> with(final T... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return ReadOnlyCollection.empty();
|
||||
}
|
||||
return ReadOnlyCollection.wrap(Stream.of(values).toList());
|
||||
}
|
||||
|
||||
public static <T> boolean isEmpty(final ReadOnlyCollection<T> c) {
|
||||
if (c == null) return true;
|
||||
return c.isEmpty();
|
||||
static <T> boolean isEmpty(final ReadOnlyCollection<T> c) {
|
||||
return c == null || c.isEmpty();
|
||||
}
|
||||
|
||||
public static <T> boolean isNotEmpty(final ReadOnlyCollection<T> c) {
|
||||
static <T> boolean isNotEmpty(final ReadOnlyCollection<T> c) {
|
||||
return !isEmpty(c);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return collection.size();
|
||||
private static <T> ReadOnlyCollection<T> wrapInternal(final Collection<T> collection) {
|
||||
return new ReadOnlyCollection<>() {
|
||||
@Override
|
||||
public Collection<T> 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<T> 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<T> c) {
|
||||
return c.stream().allMatch(collection::contains);
|
||||
default boolean contains(final Object o) {
|
||||
return asCollection().contains(o);
|
||||
}
|
||||
|
||||
public boolean containsAny(final ReadOnlyCollection<T> c) {
|
||||
return c.stream().anyMatch(collection::contains);
|
||||
default boolean containsAll(final ReadOnlyCollection<T> c) {
|
||||
if (ReadOnlyCollection.isEmpty(c)) {
|
||||
return true;
|
||||
}
|
||||
return c.stream().allMatch(this::contains);
|
||||
}
|
||||
|
||||
public Iterator<T> iterator() {
|
||||
return collection.iterator();
|
||||
default boolean containsAny(final ReadOnlyCollection<T> c) {
|
||||
if (ReadOnlyCollection.isEmpty(c)) {
|
||||
return false;
|
||||
}
|
||||
return c.stream().anyMatch(this::contains);
|
||||
}
|
||||
|
||||
public Stream<T> stream() {
|
||||
return collection.stream();
|
||||
@Override
|
||||
default Iterator<T> iterator() {
|
||||
return asCollection().iterator();
|
||||
}
|
||||
|
||||
public <R> Stream<R> map(final Function<T, R> mapper) {
|
||||
@Override
|
||||
default Spliterator<T> spliterator() {
|
||||
return asCollection().spliterator();
|
||||
}
|
||||
|
||||
default Stream<T> stream() {
|
||||
return asCollection().stream();
|
||||
}
|
||||
|
||||
default <R> Stream<R> map(final Function<T, R> mapper) {
|
||||
return stream().map(mapper);
|
||||
}
|
||||
|
||||
public T[] toArray(final Supplier<T[]> factory) {
|
||||
return collection.toArray(factory.get());
|
||||
default T[] toArray(final Supplier<T[]> 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<T> spliterator() {
|
||||
return collection.spliterator();
|
||||
default MutableList<T> toMutableList() {
|
||||
return MutableList.wrap(asCollection());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<T> extends ReadOnlyCollection<T> {
|
||||
private ReadOnlyList(final List<T> list) {
|
||||
super(list);
|
||||
public interface ReadOnlyList<T> extends ReadOnlyCollection<T> {
|
||||
static <T> ReadOnlyList<T> empty() {
|
||||
return wrapInternal(List.of());
|
||||
}
|
||||
|
||||
public static <T> ReadOnlyList<T> empty() {
|
||||
return ReadOnlyList.wrap(List.of());
|
||||
}
|
||||
|
||||
public static <T> ReadOnlyList<T> wrap(final List<T> list) {
|
||||
return new ReadOnlyList<>(list);
|
||||
}
|
||||
|
||||
public static <T> ReadOnlyList<T> wrap(final ReadOnlyCollection<T> c) {
|
||||
if (ReadOnlyCollection.isEmpty(c)) return ReadOnlyList.empty();
|
||||
return ReadOnlyList.wrap(new ArrayList<>(c.collection));
|
||||
}
|
||||
|
||||
public static <T> ReadOnlyList<T> wrap(final Set<T> set) {
|
||||
return new ReadOnlyList<>(List.copyOf(set));
|
||||
static <T> ReadOnlyList<T> wrap(final Collection<T> c) {
|
||||
if (c == null || c.isEmpty()) {
|
||||
return ReadOnlyList.empty();
|
||||
}
|
||||
return wrapInternal(List.copyOf(c));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> ReadOnlyList<T> from(final T... values) {
|
||||
if (values == null) return ReadOnlyList.empty();
|
||||
if (values.length == 0) return ReadOnlyList.empty();
|
||||
static <T> ReadOnlyList<T> from(final T... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return ReadOnlyList.empty();
|
||||
}
|
||||
return ReadOnlyList.wrap(Stream.of(values).toList());
|
||||
}
|
||||
|
||||
private static <T> ReadOnlyList<T> wrapInternal(final List<T> list) {
|
||||
return new ReadOnlyList<>() {
|
||||
@Override
|
||||
public List<T> 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<T> 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<T> 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<T> listIterator() {
|
||||
return asList().listIterator();
|
||||
}
|
||||
|
||||
default ListIterator<T> listIterator(final int index) {
|
||||
return asList().listIterator(index);
|
||||
}
|
||||
|
||||
default ReadOnlyList<T> 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<T> 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<T>)collection).get(index);
|
||||
}
|
||||
|
||||
public int indexOf(final T o) {
|
||||
return ((List<T>)collection).indexOf(o);
|
||||
}
|
||||
|
||||
public int lastIndexOf(final T o) {
|
||||
return ((List<T>)collection).lastIndexOf(o);
|
||||
}
|
||||
|
||||
public ListIterator<T> listIterator() {
|
||||
return ((List<T>)collection).listIterator();
|
||||
}
|
||||
|
||||
public ListIterator<T> listIterator(final int index) {
|
||||
return ((List<T>)collection).listIterator(index);
|
||||
}
|
||||
|
||||
public ReadOnlyList<T> subList(final int fromIndex, final int toIndex) {
|
||||
return ReadOnlyList.wrap(((List<T>)collection).subList(fromIndex, toIndex));
|
||||
}
|
||||
|
||||
public T getFirst() {
|
||||
return ((List<T>)collection).getFirst();
|
||||
}
|
||||
|
||||
public T getLast() {
|
||||
return ((List<T>)collection).getLast();
|
||||
}
|
||||
|
||||
public ReadOnlyList<T> sort(final Comparator<? super T> c) {
|
||||
Stream<T> s = collection.stream();
|
||||
if (Objects.isNull(c)) {
|
||||
s = s.sorted();
|
||||
} else {
|
||||
s = s.sorted(c);
|
||||
}
|
||||
return ReadOnlyList.wrap(s.toList());
|
||||
}
|
||||
|
||||
public ReadOnlyList<T> invert() {
|
||||
final var inverted = new ArrayList<>(collection);
|
||||
default ReadOnlyList<T> invert() {
|
||||
final var inverted = new ArrayList<>(asList());
|
||||
java.util.Collections.reverse(inverted);
|
||||
return ReadOnlyList.wrap(inverted);
|
||||
}
|
||||
|
||||
default Stream<T> stream() {
|
||||
return asList().stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
default MutableList<T> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<T> extends ReadOnlyCollection<T> {
|
||||
protected ReadOnlySet(final Set<T> set) {
|
||||
super(set);
|
||||
public interface ReadOnlySet<T> extends ReadOnlyCollection<T> {
|
||||
static <T> ReadOnlySet<T> empty() {
|
||||
return wrapInternal(Set.of());
|
||||
}
|
||||
|
||||
public static <T> ReadOnlySet<T> empty() {
|
||||
return ReadOnlySet.wrap(Set.of());
|
||||
static <T> ReadOnlySet<T> wrap(final Set<T> set) {
|
||||
if (set == null || set.isEmpty()) {
|
||||
return ReadOnlySet.empty();
|
||||
}
|
||||
return wrapInternal(Set.copyOf(set));
|
||||
}
|
||||
|
||||
public static <T> ReadOnlySet<T> wrap(final Set<T> set) {
|
||||
return new ReadOnlySet<>(set);
|
||||
static <T> ReadOnlySet<T> wrap(final Collection<T> collection) {
|
||||
if (collection == null || collection.isEmpty()) {
|
||||
return ReadOnlySet.empty();
|
||||
}
|
||||
return wrapInternal(Set.copyOf(collection));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> ReadOnlySet<T> from(final T... values) {
|
||||
if (values == null) return ReadOnlySet.empty();
|
||||
if (values.length == 0) return ReadOnlySet.empty();
|
||||
static <T> ReadOnlySet<T> from(final T... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return ReadOnlySet.empty();
|
||||
}
|
||||
return ReadOnlySet.wrap(Stream.of(values).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
private static <T> ReadOnlySet<T> wrapInternal(final Set<T> set) {
|
||||
return new ReadOnlySet<>() {
|
||||
@Override
|
||||
public Set<T> 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<T> 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<T> asCollection() {
|
||||
return asSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
default Stream<T> stream() {
|
||||
return asSet().stream();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +1,3 @@
|
||||
source-1.pbs content
|
||||
fn a1()
|
||||
{
|
||||
}
|
||||
@ -1 +1,4 @@
|
||||
source-2.pbs content
|
||||
fn a2(v1: int, v2: bounded): int
|
||||
{
|
||||
return v1 + v2;
|
||||
}
|
||||
@ -1 +1,4 @@
|
||||
source-3.pbs content
|
||||
fn a3(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user