Skip to content

Error Handling and Diagnostics

AXSLC provides clear, actionable error messages with source context.

Error Format

All compiler errors follow the format:

file:line:col: message
N-2 | source line
N-1 | source line
  N | source line where error occurred
    |       ^
N+1 | source line
N+2 | source line

Without Filename

When no filename is provided (e.g., when calling Compiler::compile without a filename argument):

line:col: message

Source Context

The error includes up to 2 lines of context before and after the error line (configurable via format_source_context's context_lines parameter).

The caret ^ appears under column col of the error line, pointing to the exact error location.

Example Error

shader.axsl:15:20: unresolved symbol: 'unknown_func'
   13 | void main() {
   14 |     vec3 pos = a.a_pos;
   15 |     vec3 result = unknown_func(pos);
      |                   ^
   16 |     gl_Position = vec4(result, 1.0);
   17 | }

Error Categories

Parse Errors

Unexpected token:

shader.axsl:5:1: expected ';' after expression
    3 | void main() {
    4 |     x = 1.0
      |            ^
    5 | }

Missing closing brace:

shader.axsl:8:1: expected '}' to close block
    6 | void main() {
    7 |     x = 1.0;
    8 |
      | ^

Linker Errors

Unresolved symbol:

shader.axsl:12:15: unresolved symbol: 'missing_func'
   10 | void main() {
   11 |     vec3 color = vec3(1.0);
   12 |     float value = missing_func(color);
      |                   ^
   13 | }

Undefined identifier:

shader.axsl:8:10: undefined identifier: 'undefined_var'
    6 | void main() {
    7 |     float x = 1.0;
    8 |     float y = undefined_var + 1.0;
      |              ^
    9 | }

Cannot open include:

shader.axsl:2:1: cannot open include: 'missing.axsl'
    1 | @version 450;
    2 | @include "missing.axsl";
      | ^
    3 |

Include file errors:

When an included file has errors, they are prefixed with the include file path:

[common/defs.axsl] line 5:10: expected ';' after expression

Diagnostic Macros

The linker uses diagnostic macros for common error patterns:

MacroProduces
PUSH_UNDEFINED_IDENTIFIER(r, name, loc)"undefined identifier: 'name' at line:col"
PUSH_UNRESOLVED_SYMBOL(r, name, loc)"unresolved symbol: 'name' at line:col"
PUSH_CANNOT_OPEN_INCLUDE(r, path)"cannot open include: 'path'"
PUSH_PREFIXED_INCLUDE_ERROR(r, path, err)"[path] error message"

Parse Error Recovery

When the parser encounters an unexpected token, it calls synchronize():

cpp
void Parser::synchronize() {
    while (!check(TokenKind::Eof)) {
        if (peek().kind == TokenKind::Semicolon) {
            advance();
            return;
        }
        if (peek().kind == TokenKind::RBrace) return;
        advance();
    }
}

Behavior

  • Discards tokens until it finds a ; (consumed) or a } (not consumed)
  • Returns a placeholder LiteralExpr{0} to keep the AST structurally intact
  • Allows the parser to continue and report further errors

Example

axsl
void main() {
    x = ;  // parse error here
    y = 2.0;  // this line can still be parsed after recovery
}

Both errors (if any) will be reported, not just the first one.

Parser Stuck Detection

The parser includes guards to detect when it hasn't advanced after attempting to parse an item:

cpp
size_t pos_before = m_pos;
parse_something();
if (m_pos == pos_before) {
    fprintf(stderr, "[axslc] parse: stuck at token ...\n");
    advance();  // force progress
}

This prevents infinite loops on malformed input. The stuck warning goes to stderr (not the error list) and is primarily a debugging aid.

Where It's Used

  • parse() - top-level declarations
  • parse_stage_block() - stage items
  • parse_block_stmt() - block statements

format_source_context

cpp
std::string format_source_context(
    std::string_view source,
    uint32_t line,
    uint32_t col,
    uint32_t context_lines = 2
);

Formats an error message with source context.

Behavior

  • Returns an empty string if line == 0 or line > total_lines
  • Right-aligns line numbers to the width of the last line number
  • Uses | as a separator between line numbers and source
  • Caret row has no line number, just spaces and the ^ at column col

Example Output

   13 | void main() {
   14 |     vec3 pos = a.a_pos;
   15 |     vec3 result = unknown_func(pos);
      |                   ^
   16 |     gl_Position = vec4(result, 1.0);
   17 | }

Multiple Errors

The compiler collects all errors and reports them together:

shader.axsl:8:10: undefined identifier: 'var1'
    6 | void main() {
    7 |     float x = 1.0;
    8 |     float y = var1 + 1.0;
      |              ^
    9 | }

shader.axsl:15:20: unresolved symbol: 'func1'
   13 | void compute() {
   14 |     vec3 pos = vec3(0.0);
   15 |     vec3 result = func1(pos);
      |                   ^
   16 | }

All errors are reported at once, allowing you to fix multiple issues in a single iteration.

Type System Limitations

AXSLC does not perform type checking. Type errors, mismatches, and semantic validation are handled by the downstream GLSL compiler.

See Limitations for details on what AXSLC doesn't check and how to handle GLSL compilation errors.