$npx -y skills add mohitmishra786/low-level-dev-skills --skill compiler-frontendCompiler 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 ta
| 1 | # Compiler Frontend |
| 2 | |
| 3 | ## Purpose |
| 4 | |
| 5 | 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`. |
| 6 | |
| 7 | ## When to Use |
| 8 | |
| 9 | - Implementing a new programming language or DSL |
| 10 | - Adding expression parsing to an interpreter or config language |
| 11 | - Designing AST node hierarchies in C or Rust |
| 12 | - Building scoped symbol tables for variables and functions |
| 13 | - Implementing basic type inference or checking |
| 14 | - Emitting LLVM IR from a typed AST |
| 15 | |
| 16 | ## Workflow |
| 17 | |
| 18 | ### 1. Pipeline overview |
| 19 | |
| 20 | ``` |
| 21 | Source → Lexer (tokens) → Parser (AST) → Type checker → IR generator → LLVM IR |
| 22 | ``` |
| 23 | |
| 24 | ### 2. Lexer — hand-written DFA |
| 25 | |
| 26 | ```c |
| 27 | typedef enum { |
| 28 | TOK_EOF, TOK_INT, TOK_IDENT, TOK_PLUS, TOK_MINUS, |
| 29 | TOK_LPAREN, TOK_RPAREN, TOK_SEMI, TOK_EQ, |
| 30 | } TokenKind; |
| 31 | |
| 32 | typedef struct { |
| 33 | TokenKind kind; |
| 34 | const char *start; |
| 35 | int length; |
| 36 | int64_t int_val; |
| 37 | } Token; |
| 38 | |
| 39 | typedef struct { |
| 40 | const char *src; |
| 41 | int pos; |
| 42 | int line; |
| 43 | } Lexer; |
| 44 | |
| 45 | static void skip_whitespace(Lexer *l) { |
| 46 | while (l->src[l->pos] == ' ' || l->src[l->pos] == '\n') l->pos++; |
| 47 | } |
| 48 | |
| 49 | Token lexer_next(Lexer *l) { |
| 50 | skip_whitespace(l); |
| 51 | const char *start = &l->src[l->pos]; |
| 52 | |
| 53 | if (isdigit(l->src[l->pos])) { |
| 54 | int64_t val = 0; |
| 55 | while (isdigit(l->src[l->pos])) |
| 56 | val = val * 10 + (l->src[l->pos++] - '0'); |
| 57 | return (Token){ TOK_INT, start, l->pos - (start - l->src), val }; |
| 58 | } |
| 59 | if (isalpha(l->src[l->pos])) { |
| 60 | while (isalnum(l->src[l->pos])) l->pos++; |
| 61 | return (Token){ TOK_IDENT, start, l->pos - (start - l->src), 0 }; |
| 62 | } |
| 63 | char c = l->src[l->pos++]; |
| 64 | switch (c) { |
| 65 | case '+': return (Token){ TOK_PLUS, start, 1, 0 }; |
| 66 | case '(': return (Token){ TOK_LPAREN, start, 1, 0 }; |
| 67 | case ')': return (Token){ TOK_RPAREN, start, 1, 0 }; |
| 68 | case ';': return (Token){ TOK_SEMI, start, 1, 0 }; |
| 69 | default: return (Token){ TOK_EOF, start, 0, 0 }; |
| 70 | } |
| 71 | } |
| 72 | ``` |
| 73 | |
| 74 | ```bash |
| 75 | # flex alternative |
| 76 | flex lexer.l && gcc -o lexer lexer.tab.c -lfl |
| 77 | ``` |
| 78 | |
| 79 | ### 3. Pratt parser for expressions |
| 80 | |
| 81 | ```c |
| 82 | typedef enum { AST_INT, AST_BINOP, AST_VAR } AstKind; |
| 83 | |
| 84 | typedef struct AstNode { |
| 85 | AstKind kind; |
| 86 | union { |
| 87 | int64_t int_val; |
| 88 | struct { int op; struct AstNode *lhs, *rhs; } binop; |
| 89 | char *name; |
| 90 | }; |
| 91 | } AstNode; |
| 92 | |
| 93 | // Binding powers: higher = tighter precedence |
| 94 | enum { BP_NONE = 0, BP_SUM = 10, BP_PRODUCT = 20 }; |
| 95 | |
| 96 | AstNode *parse_expression(Parser *p, int min_bp) { |
| 97 | AstNode *left = parse_prefix(p); |
| 98 | for (;;) { |
| 99 | int lbp, rbp; |
| 100 | if (!infix_binding_power(p->cur.kind, &lbp, &rbp) || lbp < min_bp) |
| 101 | break; |
| 102 | advance(p); |
| 103 | AstNode *right = parse_expression(p, rbp); |
| 104 | left = make_binop(p->cur.kind, left, right); |
| 105 | } |
| 106 | return left; |
| 107 | } |
| 108 | ``` |
| 109 | |
| 110 | Pratt handles operator precedence cleanly without massive grammar tables. |
| 111 | |
| 112 | ### 4. Recursive descent for statements |
| 113 | |
| 114 | ```c |
| 115 | AstNode *parse_statement(Parser *p) { |
| 116 | if (match(p, TOK_IDENT) && peek(p) == TOK_EQ) { |
| 117 | char *name = p->prev.text; |
| 118 | advance(p); // = |
| 119 | AstNode *expr = parse_expression(p, BP_NONE); |
| 120 | expect(p, TOK_SEMI); |
| 121 | return make_assign(name, expr); |
| 122 | } |
| 123 | if (match(p, TOK_RETURN)) { |
| 124 | AstNode *expr = parse_expression(p, BP_NONE); |
| 125 | expect(p, TOK_SEMI); |
| 126 | return make_return(expr); |
| 127 | } |
| 128 | return parse_expression_statement(p); |
| 129 | } |
| 130 | ``` |
| 131 | |
| 132 | ### 5. AST and symbol table |
| 133 | |
| 134 | ```c |
| 135 | typedef struct Symbol { |
| 136 | char *name; |
| 137 | Type *type; |
| 138 | LLVMValueRef llvm_val; // after codegen |
| 139 | struct Symbol *next; |
| 140 | } Symbol; |
| 141 | |
| 142 | typedef struct Scope { |
| 143 | Symbol *symbols; // hash map bucket chain |
| 144 | struct Scope *parent; |
| 145 | } Scope; |
| 146 | |
| 147 | Symbol *scope_lookup(Scope *s, const char *name) { |
| 148 | for (Scope *cur = s; cur; cur = cur->parent) { |
| 149 | for (Symbol *sym = cur->symbols; sym; sym = sym->next) |
| 150 | if (strcmp(sym->name, name) == 0) |
| 151 | return sym; |
| 152 | } |
| 153 | return NULL; |
| 154 | } |
| 155 | |
| 156 | void scope_define(Scope *s, const char *name, Type *type) { |
| 157 | Symbol *sym = malloc(sizeof(Symbol)); |
| 158 | sym->name = strdup(name); |
| 159 | sym->type = type; |
| 160 | sym->next = s->symbols; |
| 161 | s->symbols = sym; |
| 162 | } |
| 163 | ``` |
| 164 | |
| 165 | ### 6. Type checker (basics) |
| 166 | |
| 167 | ```c |
| 168 | typedef enum { TY_INT, TY_BOOL, TY_FUNC, TY_VOID } TypeKind; |
| 169 | |
| 170 | Type *check_expr(Scope *s, AstNode *node) { |
| 171 | switch (node->kind) { |
| 172 | case AST_INT: return type_int(); |
| 173 | case AST_VAR: { |
| 174 | Symbol *sym = scope_lookup(s, node->name); |
| 175 | if (!sym) error("undefined variable %s", node->name); |
| 176 | retur |