compiler-frontend

v2026.09.24

Compiler frontend skill for lexing, parsing, and type checking. Use when building a lexer/parser, designing AST nodes, implementing symbol tables, type checking, error recovery, or emitting LLVM IR. Activates on queries about lexer, Pratt parser, recursive descent, AST, symbol table, Hindley-Milner, or llvm-sys.

GitHub
安装命令
npx skhub add mohitmishra786/compiler-frontend
Markdown
SKILL.md

Compiler Frontend

Purpose

Guide agents through building a compiler frontend: lexers (hand-written DFA vs flex), Pratt parsing for expressions, recursive-descent for statements, AST design, symbol tables with scoped hash maps, type checking basics, error recovery strategies, and LLVM IR generation via the C API or llvm-sys.

When to Use

  • Implementing a new programming language or DSL
  • Adding expression parsing to an interpreter or config language
  • Designing AST node hierarchies in C or Rust
  • Building scoped symbol tables for variables and functions
  • Implementing basic type inference or checking
  • Emitting LLVM IR from a typed AST

Workflow

1. Pipeline overview

Source → Lexer (tokens) → Parser (AST) → Type checker → IR generator → LLVM IR

2. Lexer — hand-written DFA

typedef enum {
    TOK_EOF, TOK_INT, TOK_IDENT, TOK_PLUS, TOK_MINUS,
    TOK_LPAREN, TOK_RPAREN, TOK_SEMI, TOK_EQ,
} TokenKind;

typedef struct {
    TokenKind kind;
    const char *start;
    int length;
    int64_t int_val;
} Token;

typedef struct {
    const char *src;
    int pos;
    int line;
} Lexer;

static void skip_whitespace(Lexer *l) {
    while (l->src[l->pos] == ' ' || l->src[l->pos] == '\n') l->pos++;
}

Token lexer_next(Lexer *l) {
    skip_whitespace(l);
    const char *start = &l->src[l->pos];

    if (isdigit(l->src[l->pos])) {
        int64_t val = 0;
        while (isdigit(l->src[l->pos]))
            val = val * 10 + (l->src[l->pos++] - '0');
        return (Token){ TOK_INT, start, l->pos - (start - l->src), val };
    }
    if (isalpha(l->src[l->pos])) {
        while (isalnum(l->src[l->pos])) l->pos++;
        return (Token){ TOK_IDENT, start, l->pos - (start - l->src), 0 };
    }
    char c = l->src[l->pos++];
    switch (c) {
        case '+': return (Token){ TOK_PLUS, start, 1, 0 };
        case '(': return (Token){ TOK_LPAREN, start, 1, 0 };
        case ')': return (Token){ TOK_RPAREN, start, 1, 0 };
        case ';': return (Token){ TOK_SEMI, start, 1, 0 };
        default:  return (Token){ TOK_EOF, start, 0, 0 };
    }
}
# flex alternative
flex lexer.l && gcc -o lexer lexer.tab.c -lfl

3. Pratt parser for expressions

typedef enum { AST_INT, AST_BINOP, AST_VAR } AstKind;

typedef struct AstNode {
    AstKind kind;
    union {
        int64_t int_val;
        struct { int op; struct AstNode *lhs, *rhs; } binop;
        char *name;
    };
} AstNode;

// Binding powers: higher = tighter precedence
enum { BP_NONE = 0, BP_SUM = 10, BP_PRODUCT = 20 };

AstNode *parse_expression(Parser *p, int min_bp) {
    AstNode *left = parse_prefix(p);
    for (;;) {
        int lbp, rbp;
        if (!infix_binding_power(p->cur.kind, &lbp, &rbp) || lbp < min_bp)
            break;
        advance(p);
        AstNode *right = parse_expression(p, rbp);
        left = make_binop(p->cur.kind, left, right);
    }
    return left;
}

Pratt handles operator precedence cleanly without massive grammar tables.

4. Recursive descent for statements

AstNode *parse_statement(Parser *p) {
    if (match(p, TOK_IDENT) && peek(p) == TOK_EQ) {
        char *name = p->prev.text;
        advance(p);  // =
        AstNode *expr = parse_expression(p, BP_NONE);
        expect(p, TOK_SEMI);
        return make_assign(name, expr);
    }
    if (match(p, TOK_RETURN)) {
        AstNode *expr = parse_expression(p, BP_NONE);
        expect(p, TOK_SEMI);
        return make_return(expr);
    }
    return parse_expression_statement(p);
}

5. AST and symbol table

typedef struct Symbol {
    char *name;
    Type *type;
    LLVMValueRef llvm_val;  // after codegen
    struct Symbol *next;
} Symbol;

typedef struct Scope {
    Symbol *symbols;       // hash map bucket chain
    struct Scope *parent;
} Scope;

Symbol *scope_lookup(Scope *s, const char *name) {
    for (Scope *cur = s; cur; cur = cur->parent) {
        for (Symbol *sym = cur->symbols; sym; sym = sym->next)
            if (strcmp(sym->name, name) == 0)
                return sym;
    }
    return NULL;
}

void scope_define(Scope *s, const char *name, Type *type) {
    Symbol *sym = malloc(sizeof(Symbol));
    sym->name = strdup(name);
    sym->type = type;
    sym->next = s->symbols;
    s->symbols = sym;
}

6. Type checker (basics)

typedef enum { TY_INT, TY_BOOL, TY_FUNC, TY_VOID } TypeKind;

Type *check_expr(Scope *s, AstNode *node) {
    switch (node->kind) {
    case AST_INT: return type_int();
    case AST_VAR: {
        Symbol *sym = scope_lookup(s, node->name);
        if (!sym) error("undefined variable %s", node->name);
        return sym->type;
    }
    case AST_BINOP: {
        Type *lt = check_expr(s, node->binop.lhs);
        Type *rt = check_expr(s, node->binop.rhs);
        if (!type_equal(lt, rt))
            error("type mismatch in binary op");
        return lt;
    }
    }
    return type_void();
}

Hindley-Milner (full inference): assign type variables, unify on constraints — use for ML-like languages.

7. Error recovery

StrategyWhen
Panic modeSkip tokens until synchronizing token (;, })
Synchronization setsDefine recovery tokens per nonterminal
Error productionsGrammar rules for common mistakes
Single-token insertion/deletionIDE-friendly recovery
void synchronize(Parser *p) {
    advance(p);
    while (p->cur.kind != TOK_EOF) {
        if (p->prev.kind == TOK_SEMI) return;
        if (p->cur.kind == TOK_RETURN || p->cur.kind == TOK_IDENT) return;
        advance(p);
    }
}

8. LLVM IR generation

#include <llvm-c/Core.h>
#include <llvm-c/ExecutionEngine.h>

LLVMModuleRef module;
LLVMBuilderRef builder;

LLVMValueRef codegen_expr(Scope *s, AstNode *node) {
    switch (node->kind) {
    case AST_INT:
        return LLVMConstInt(LLVMInt32Type(), node->int_val, 1);
    case AST_BINOP: {
        LLVMValueRef l = codegen_expr(s, node->binop.lhs);
        LLVMValueRef r = codegen_expr(s, node->binop.rhs);
        if (node->binop.op == TOK_PLUS)
            return LLVMBuildAdd(builder, l, r, "add");
        break;
    }
    case AST_VAR: {
        Symbol *sym = scope_lookup(s, node->name);
        return LLVMBuildLoad(builder, sym->llvm_val, node->name);
    }
    }
    return NULL;
}
# Dump IR
LLVMDumpModule(module);
# Verify
char *err = NULL;
LLVMVerifyModule(module, LLVMReturnStatusAction, &err);

Rust: use inkwell or llvm-sys crates for safer bindings.

Common Problems

SymptomCauseFix
Parser infinite loopMissing advance on errorAudit all branches advance token
Wrong operator precedenceIncorrect binding powersTable-test expressions
Duplicate symbol in scopeNo shadowing checkLookup parent only for read; define in current
LLVM verify failsType mismatch in IRCheck signedness, pointer levels
flex token clashOverlapping patternsLongest match; order rules
Error cascadeNo synchronizationImplement panic mode recovery

Related Skills

  • skills/compiler-internals/llvm-passes — optimize generated IR
  • skills/compiler-internals/jit-compilation — execute generated IR
  • skills/compiler-internals/mlir — higher-level IR for DSLs
  • skills/compilers/llvm — LLVM as a user (opt, llc)
  • skills/low-level-programming/interpreters — bytecode VM alternative
  • skills/languages/carbon-lang — modern language frontend example
发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

2026年9月24日

分类

未分类

许可证

MIT

源路径

skills/compiler-internals/compiler-frontend

默认分支

main

最新提交

bdc5847

Tree SHA

1178323