added first steps on pbs

This commit is contained in:
bQUARKz 2026-02-26 19:28:14 +00:00
parent 801109b993
commit 14da21fe9f
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
5 changed files with 90 additions and 28 deletions

View File

@ -73,7 +73,7 @@ Active keywords in `.pbs` files (v0 Core):
- `import`, `from`, `as` - `import`, `from`, `as`
- `service`, `fn` - `service`, `fn`
- `declare`, `struct`, `contract`, `error` - `declare`, `struct`, `contract`, `error`
- `let`, `mut` - `let`
- `if`, `else`, `when`, `for`, `in`, `return` - `if`, `else`, `when`, `for`, `in`, `return`
- `true`, `false` - `true`, `false`
@ -221,7 +221,7 @@ ServiceMember ::= 'fn' Identifier ParamList ReturnType? Block
```ebnf ```ebnf
FunctionDecl ::= 'fn' Identifier ParamList ReturnType? ElseFallback? Block FunctionDecl ::= 'fn' Identifier ParamList ReturnType? ElseFallback? Block
ParamList ::= '(' Param (',' Param)* ')' ParamList ::= '(' Param (',' Param)* ')'
Param ::= 'mut'? Identifier ':' TypeRef Param ::= Identifier ':' TypeRef
ReturnType ::= ':' TypeRef ReturnType ::= ':' TypeRef
ElseFallback ::= 'else' Expr ElseFallback ::= 'else' Expr
``` ```
@ -248,7 +248,7 @@ Block ::= '{' Stmt* TailExpr? '}'
Stmt ::= LetStmt | ReturnStmt | IfStmt | ForStmt | ExprStmt Stmt ::= LetStmt | ReturnStmt | IfStmt | ForStmt | ExprStmt
TailExpr ::= Expr TailExpr ::= Expr
LetStmt ::= 'let' 'mut'? Identifier (':' TypeRef)? '=' Expr ';' LetStmt ::= 'let' Identifier (':' TypeRef)? '=' Expr ';'
ReturnStmt ::= 'return' Expr? ';' ReturnStmt ::= 'return' Expr? ';'
ExprStmt ::= Expr ';' ExprStmt ::= Expr ';'

View File

@ -23,7 +23,6 @@ public final class PbsAst {
public record Parameter( public record Parameter(
String name, String name,
boolean mutable,
TypeRef typeRef, TypeRef typeRef,
Span span) { Span span) {
} }
@ -44,7 +43,6 @@ public final class PbsAst {
public record LetStatement( public record LetStatement(
String name, String name,
boolean mutable,
TypeRef explicitType, TypeRef explicitType,
Expression initializer, Expression initializer,
Span span) implements Statement { Span span) implements Statement {

View File

@ -10,6 +10,14 @@ import java.util.Map;
public final class PbsLexer { public final class PbsLexer {
private static final Map<String, PbsTokenKind> KEYWORDS = buildKeywords(); private static final Map<String, PbsTokenKind> KEYWORDS = buildKeywords();
private enum LexerState {
DEFAULT,
IDENTIFIER,
NUMBER,
STRING,
LINE_COMMENT
}
private final String source; private final String source;
private final String sourceLabel; private final String sourceLabel;
private final BuildingIssueSink issues; private final BuildingIssueSink issues;
@ -17,6 +25,7 @@ public final class PbsLexer {
private int start; private int start;
private int current; private int current;
private LexerState state = LexerState.DEFAULT;
private PbsLexer(final String source, final String sourceLabel, final BuildingIssueSink issues) { private PbsLexer(final String source, final String sourceLabel, final BuildingIssueSink issues) {
this.source = source == null ? "" : source; this.source = source == null ? "" : source;
@ -33,15 +42,24 @@ public final class PbsLexer {
} }
private ReadOnlyList<PbsToken> lexInternal() { private ReadOnlyList<PbsToken> lexInternal() {
while (!isAtEnd()) { while (!isAtEnd() || state != LexerState.DEFAULT) {
start = current; switch (state) {
scanToken(); case DEFAULT -> scanDefaultState();
case IDENTIFIER -> scanIdentifierState();
case NUMBER -> scanNumberState();
case STRING -> scanStringState();
case LINE_COMMENT -> scanLineCommentState();
}
} }
tokens.add(new PbsToken(PbsTokenKind.EOF, "", current, current)); tokens.add(new PbsToken(PbsTokenKind.EOF, "", current, current));
return ReadOnlyList.wrap(tokens); return ReadOnlyList.wrap(tokens);
} }
private void scanToken() { private void scanDefaultState() {
if (isAtEnd()) {
return;
}
start = current;
final char c = advance(); final char c = advance();
switch (c) { switch (c) {
case ' ', '\r', '\t', '\n' -> { case ' ', '\r', '\t', '\n' -> {
@ -88,22 +106,19 @@ public final class PbsLexer {
} }
case '/' -> { case '/' -> {
if (match('/')) { if (match('/')) {
// Line comment state = LexerState.LINE_COMMENT;
while (!isAtEnd() && peek() != '\n') {
advance();
}
return; return;
} }
addToken(PbsTokenKind.SLASH); addToken(PbsTokenKind.SLASH);
} }
case '"' -> string(); case '"' -> state = LexerState.STRING;
default -> { default -> {
if (isDigit(c)) { if (isDigit(c)) {
number(); state = LexerState.NUMBER;
return; return;
} }
if (isIdentifierStart(c)) { if (isIdentifierStart(c)) {
identifier(); state = LexerState.IDENTIFIER;
return; return;
} }
report("E_LEX_INVALID_CHAR", "Invalid character: '%s'".formatted(c)); report("E_LEX_INVALID_CHAR", "Invalid character: '%s'".formatted(c));
@ -111,7 +126,7 @@ public final class PbsLexer {
} }
} }
private void identifier() { private void scanIdentifierState() {
while (!isAtEnd() && isIdentifierPart(peek())) { while (!isAtEnd() && isIdentifierPart(peek())) {
advance(); advance();
} }
@ -119,9 +134,10 @@ public final class PbsLexer {
final String text = source.substring(start, current); final String text = source.substring(start, current);
final PbsTokenKind kind = KEYWORDS.getOrDefault(text, PbsTokenKind.IDENTIFIER); final PbsTokenKind kind = KEYWORDS.getOrDefault(text, PbsTokenKind.IDENTIFIER);
addToken(kind); addToken(kind);
state = LexerState.DEFAULT;
} }
private void number() { private void scanNumberState() {
while (!isAtEnd() && isDigit(peek())) { while (!isAtEnd() && isDigit(peek())) {
advance(); advance();
} }
@ -138,13 +154,15 @@ public final class PbsLexer {
if (!isFloat && !isAtEnd() && peek() == 'b') { if (!isFloat && !isAtEnd() && peek() == 'b') {
advance(); advance();
addToken(PbsTokenKind.BOUNDED_LITERAL); addToken(PbsTokenKind.BOUNDED_LITERAL);
state = LexerState.DEFAULT;
return; return;
} }
addToken(isFloat ? PbsTokenKind.FLOAT_LITERAL : PbsTokenKind.INT_LITERAL); addToken(isFloat ? PbsTokenKind.FLOAT_LITERAL : PbsTokenKind.INT_LITERAL);
state = LexerState.DEFAULT;
} }
private void string() { private void scanStringState() {
while (!isAtEnd() && peek() != '"') { while (!isAtEnd() && peek() != '"') {
if (peek() == '\\' && !isAtEnd()) { if (peek() == '\\' && !isAtEnd()) {
advance(); advance();
@ -158,12 +176,21 @@ public final class PbsLexer {
if (isAtEnd()) { if (isAtEnd()) {
report("E_LEX_UNTERMINATED_STRING", "Unterminated string literal"); report("E_LEX_UNTERMINATED_STRING", "Unterminated string literal");
state = LexerState.DEFAULT;
return; return;
} }
// Closing quote. // Closing quote.
advance(); advance();
addToken(PbsTokenKind.STRING_LITERAL); addToken(PbsTokenKind.STRING_LITERAL);
state = LexerState.DEFAULT;
}
private void scanLineCommentState() {
while (!isAtEnd() && peek() != '\n') {
advance();
}
state = LexerState.DEFAULT;
} }
private void addToken(final PbsTokenKind kind) { private void addToken(final PbsTokenKind kind) {
@ -224,7 +251,6 @@ public final class PbsLexer {
map.put("service", PbsTokenKind.SERVICE); map.put("service", PbsTokenKind.SERVICE);
map.put("fn", PbsTokenKind.FN); map.put("fn", PbsTokenKind.FN);
map.put("let", PbsTokenKind.LET); map.put("let", PbsTokenKind.LET);
map.put("mut", PbsTokenKind.MUT);
map.put("declare", PbsTokenKind.DECLARE); map.put("declare", PbsTokenKind.DECLARE);
map.put("struct", PbsTokenKind.STRUCT); map.put("struct", PbsTokenKind.STRUCT);
map.put("contract", PbsTokenKind.CONTRACT); map.put("contract", PbsTokenKind.CONTRACT);

View File

@ -1,37 +1,70 @@
package p.studio.compiler.pbs.lexer; package p.studio.compiler.pbs.lexer;
/**
* Token kinds produced by the PBS lexer.
*/
public enum PbsTokenKind { public enum PbsTokenKind {
// End of file marker.
EOF, EOF,
// User-defined names.
// Example: `sum`, `Vector`, `input_state`
IDENTIFIER, IDENTIFIER,
// Literal values.
// Example: `42`, `3.14`, `255b`, `"hello"`
INT_LITERAL, INT_LITERAL,
FLOAT_LITERAL, FLOAT_LITERAL,
BOUNDED_LITERAL, BOUNDED_LITERAL,
STRING_LITERAL, STRING_LITERAL,
// Keywords // Declaration and import keywords.
// Example:
// `import { Vec2 as V2 } from @core:math;`
IMPORT, IMPORT,
FROM, FROM,
AS, AS,
// Barrel visibility keywords (reserved in source parser for now).
// Example in barrel:
// `pub fn sum;`
PUB, PUB,
MOD, MOD,
// Top-level declaration keywords.
// Example:
// `service Audio { fn play(): int { return 1; } }`
// `declare struct Point(x: int, y: int);`
SERVICE, SERVICE,
FN, FN,
LET,
MUT,
DECLARE, DECLARE,
// Statement and type declaration keywords.
// Example:
// `let x: int = 1;`
LET,
STRUCT, STRUCT,
CONTRACT, CONTRACT,
ERROR, ERROR,
// Control-flow keywords.
// Example:
// `if cond { return 1; } else { return 0; }`
IF, IF,
ELSE, ELSE,
WHEN, WHEN,
FOR, FOR,
IN, IN,
RETURN, RETURN,
// Boolean literals.
// Example: `true && false`
TRUE, TRUE,
FALSE, FALSE,
// Punctuation / operators // Delimiters and separators.
// Example:
// `fn f(a: int) { return a; }`
LEFT_PAREN, LEFT_PAREN,
RIGHT_PAREN, RIGHT_PAREN,
LEFT_BRACE, LEFT_BRACE,
@ -44,12 +77,18 @@ public enum PbsTokenKind {
AT, AT,
DOT_DOT, DOT_DOT,
// Arithmetic and unary operators.
// Example: `-a + b * 2`
PLUS, PLUS,
MINUS, MINUS,
STAR, STAR,
SLASH, SLASH,
PERCENT, PERCENT,
BANG, BANG,
// Comparison and assignment operators.
// Example:
// `a == b`, `x <= y`, `value = 10`
BANG_EQUAL, BANG_EQUAL,
EQUAL, EQUAL,
EQUAL_EQUAL, EQUAL_EQUAL,
@ -57,6 +96,9 @@ public enum PbsTokenKind {
LESS_EQUAL, LESS_EQUAL,
GREATER, GREATER,
GREATER_EQUAL, GREATER_EQUAL,
// Logical operators.
// Example: `a && b || c`
AND_AND, AND_AND,
OR_OR OR_OR
} }

View File

@ -115,13 +115,11 @@ public final class PbsParser {
if (!check(PbsTokenKind.RIGHT_PAREN)) { if (!check(PbsTokenKind.RIGHT_PAREN)) {
do { do {
final var pStart = peek(); final var pStart = peek();
final boolean mutable = match(PbsTokenKind.MUT);
final var pName = consume(PbsTokenKind.IDENTIFIER, "E_PARSE_EXPECTED_TOKEN", "Expected parameter name"); final var pName = consume(PbsTokenKind.IDENTIFIER, "E_PARSE_EXPECTED_TOKEN", "Expected parameter name");
consume(PbsTokenKind.COLON, "E_PARSE_EXPECTED_TOKEN", "Expected ':' after parameter name"); consume(PbsTokenKind.COLON, "E_PARSE_EXPECTED_TOKEN", "Expected ':' after parameter name");
final var typeRef = parseTypeRef(); final var typeRef = parseTypeRef();
parameters.add(new PbsAst.Parameter( parameters.add(new PbsAst.Parameter(
pName.lexeme(), pName.lexeme(),
mutable,
typeRef, typeRef,
span(pStart.start(), typeRef.span().getEnd()))); span(pStart.start(), typeRef.span().getEnd())));
} while (match(PbsTokenKind.COMMA)); } while (match(PbsTokenKind.COMMA));
@ -174,7 +172,6 @@ public final class PbsParser {
} }
private PbsAst.Statement parseLetStatement(final PbsToken letToken) { private PbsAst.Statement parseLetStatement(final PbsToken letToken) {
final boolean mutable = match(PbsTokenKind.MUT);
final var name = consume(PbsTokenKind.IDENTIFIER, "E_PARSE_EXPECTED_TOKEN", "Expected variable name"); final var name = consume(PbsTokenKind.IDENTIFIER, "E_PARSE_EXPECTED_TOKEN", "Expected variable name");
PbsAst.TypeRef explicitType = null; PbsAst.TypeRef explicitType = null;
@ -188,7 +185,6 @@ public final class PbsParser {
return new PbsAst.LetStatement( return new PbsAst.LetStatement(
name.lexeme(), name.lexeme(),
mutable,
explicitType, explicitType,
initializer, initializer,
span(letToken.start(), semicolon.end())); span(letToken.start(), semicolon.end()));