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`
- `service`, `fn`
- `declare`, `struct`, `contract`, `error`
- `let`, `mut`
- `let`
- `if`, `else`, `when`, `for`, `in`, `return`
- `true`, `false`
@ -221,7 +221,7 @@ ServiceMember ::= 'fn' Identifier ParamList ReturnType? Block
```ebnf
FunctionDecl ::= 'fn' Identifier ParamList ReturnType? ElseFallback? Block
ParamList ::= '(' Param (',' Param)* ')'
Param ::= 'mut'? Identifier ':' TypeRef
Param ::= Identifier ':' TypeRef
ReturnType ::= ':' TypeRef
ElseFallback ::= 'else' Expr
```
@ -248,7 +248,7 @@ Block ::= '{' Stmt* TailExpr? '}'
Stmt ::= LetStmt | ReturnStmt | IfStmt | ForStmt | ExprStmt
TailExpr ::= Expr
LetStmt ::= 'let' 'mut'? Identifier (':' TypeRef)? '=' Expr ';'
LetStmt ::= 'let' Identifier (':' TypeRef)? '=' Expr ';'
ReturnStmt ::= 'return' Expr? ';'
ExprStmt ::= Expr ';'

View File

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

View File

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

View File

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

View File

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