/// /// See `docs/syntax.md#indentation-levels` for an explanation of the indent level algorithm. /// #include #include #include #include #include "../io.h" #include "indent.h" _Bool is_indent(char c) { return c == ' ' || c == '\t'; } _Bool is_newline(char c) { return c == '\r' || c == '\n'; } enum indent_type { INDENT_TABS, INDENT_SPACES, }; struct indent { uint32_t tabs; uint32_t spaces; }; static char good_indent(enum indent_type type) { switch (type) { case INDENT_TABS: return '\t'; case INDENT_SPACES: return ' '; } } static char bad_indent(enum indent_type type) { switch (type) { case INDENT_TABS: return ' '; case INDENT_SPACES: return '\t'; } } static uint32_t indent_levels = 0; static struct indent indents[MAX_INDENTS]; static uint32_t additional_line_length = 0; static _Bool tabs_allowed(void) { return indent_levels == 0 || indents[indent_levels - 1].spaces == 0; } // We only throw errors on bad indentation if the line is not empty. // This function spins to the end of the line to determine whether to throw the error. static void indent_error(enum indent_type type) { char c = peekc(); while (is_indent(c)) { nextc(); c = peekc(); if (is_newline(c)) { return; } } switch (type) { case INDENT_SPACES: fprintf(stderr, "lexical error: previous line used spaces at this indentation level; this line used tabs\n"); exit(1); case INDENT_TABS: fprintf(stderr, "lexical error: previous lines used tabs at this indentation level; this line used spaces\n"); exit(1); } } static void expect_indent(enum indent_type type, uint32_t depth) { char good = good_indent(type); char bad = bad_indent(type); char c = peekc(); for (uint32_t i = 0; i < depth; i++) { if (c == bad) { indent_error(type); return; } if (is_newline(c)) { return; } if (c != good) { fprintf(stderr, "lexical error: indentation does not match any preceding indentation level\n"); exit(1); } nextc(); c = peekc(); } } static uint32_t count_indents(enum indent_type type) { uint32_t counter = 0; char indent = good_indent(type); char c = peekc(); while (c == indent) { counter++; nextc(); c = peekc(); } return counter; } // Returns `true` if a new indentation level was introduced. static _Bool new_indent(void) { struct indent indent = { 0, 0 }; indent.tabs = count_indents(INDENT_TABS); indent.spaces = count_indents(INDENT_SPACES); char c = peekc(); if ((indent.tabs > 0 && !tabs_allowed()) || (c == '\t' && indent.spaces > 0)) { fprintf(stderr, "lexical error: all tabs on a line must precede all spaces\n"); exit(1); } if (indent_levels == MAX_INDENTS && !is_newline(c)) { fprintf(stderr, "lexical error: too many indentation levels! factor your code!\n"); exit(1); } indents[indent_levels] = indent; return indent.tabs > 0 || indent.spaces > 0; } uint32_t lex_line_indentation(void) { uint32_t indent_level = 0; while (indent_level < indent_levels) { if (!is_indent(peekc())) { return indent_level; } struct indent expected_indent = indents[indent_level]; expect_indent(INDENT_TABS, expected_indent.tabs); expect_indent(INDENT_SPACES, expected_indent.spaces); indent_level++; } if (new_indent()) { return indent_level + 1; } return indent_level; } uint32_t lex_indentation(void) { uint32_t indent_level; while (true) { indent_level = lex_line_indentation(); // We ignore trailing whitespace on empty lines, // but don't know that a line will be empty until we've reached the end. if (!is_newline(peekc())) { break; } nextc(); } if (peekc() == 0) { // EOF closes all indentation-based blocks. indent_level = 0; } indent_levels = indent_level; return indent_levels; }