CtrlK
BlogDocsLog inGet started
Tessl Logo
PluginPublic

mcclowes/language-design

Use when designing language features - covers lexer, parser, AST, and interpreter patterns

Quality

95%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

parser.mdreferences/

Parser — full recursive descent implementation

The core loop and key precedence levels live in SKILL.md. This is the complete recursive-descent parser, showing every precedence level wired top to bottom.

class Parser {
  private tokens: Token[];
  private current = 0;

  constructor(tokens: Token[]) {
    this.tokens = tokens;
  }

  parse(): Program {
    const statements: Stmt[] = [];
    while (!this.isAtEnd()) {
      statements.push(this.statement());
    }
    return { type: "Program", body: statements };
  }

  // --- Precedence climbing, lowest to highest ---

  private expression(): Expr {
    return this.pipe();
  }

  private pipe(): Expr {
    let left = this.equality();
    while (this.match(TokenType.PIPE)) {
      const right = this.equality();
      left = { type: "PipeExpr", left, right };
    }
    return left;
  }

  private equality(): Expr {
    let left = this.comparison();
    while (this.match(TokenType.EQEQ, TokenType.NEQ)) {
      const op = this.previous().type;
      const right = this.comparison();
      left = { type: "BinaryExpr", op, left, right };
    }
    return left;
  }

  private comparison(): Expr {
    let left = this.term();
    while (this.match(TokenType.LT, TokenType.GT, TokenType.LTE, TokenType.GTE)) {
      const op = this.previous().type;
      const right = this.term();
      left = { type: "BinaryExpr", op, left, right };
    }
    return left;
  }

  private term(): Expr {
    let left = this.factor();
    while (this.match(TokenType.PLUS, TokenType.MINUS)) {
      const op = this.previous().type;
      const right = this.factor();
      left = { type: "BinaryExpr", op, left, right };
    }
    return left;
  }

  private factor(): Expr {
    let left = this.unary();
    while (this.match(TokenType.STAR, TokenType.SLASH, TokenType.PERCENT)) {
      const op = this.previous().type;
      const right = this.unary();
      left = { type: "BinaryExpr", op, left, right };
    }
    return left;
  }

  private unary(): Expr {
    if (this.match(TokenType.MINUS)) {
      const operand = this.unary();
      return { type: "UnaryExpr", op: TokenType.MINUS, operand };
    }
    return this.primary();
  }

  private primary(): Expr {
    if (this.match(TokenType.NUMBER)) {
      return { type: "NumberLiteral", value: Number(this.previous().value) };
    }
    if (this.match(TokenType.IDENT)) {
      return { type: "Identifier", name: this.previous().value };
    }
    if (this.match(TokenType.LPAREN)) {
      const expr = this.expression();
      this.consume(TokenType.RPAREN, "Expected ')' after expression");
      return expr;
    }
    throw new ParseError(`Unexpected token: ${this.peek().type}`, this.peek());
  }

  // --- Token cursor helpers ---

  private match(...types: TokenType[]): boolean {
    if (types.includes(this.peek().type)) {
      this.advance();
      return true;
    }
    return false;
  }

  private consume(type: TokenType, message: string): Token {
    if (this.peek().type === type) return this.advance();
    throw new ParseError(message, this.peek());
  }

  private advance(): Token {
    if (!this.isAtEnd()) this.current++;
    return this.previous();
  }

  private peek(): Token {
    return this.tokens[this.current];
  }

  private previous(): Token {
    return this.tokens[this.current - 1];
  }

  private isAtEnd(): boolean {
    return this.peek().type === TokenType.EOF;
  }
}

README.md

SKILL.md

tile.json