pass-lang/src/lex/indent.c

167 lines
4.1 KiB
C

///
/// See `docs/syntax.md#indentation-levels` for an explanation of the indent level algorithm.
///
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#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;
}
static void new_indent(void) {
struct indent indent = { 0, 0 };
indent.tabs = count_indents(INDENT_TABS);
indent.spaces = count_indents(INDENT_SPACES);
char c = peekc();
if (c == '\t' && (indent.spaces > 0 || !tabs_allowed())) {
fprintf(stderr, "lexical error: all tabs on a line must precede all spaces\n");
exit(1);
}
if (is_newline(c)) {
return;
}
if (indent_levels == MAX_INDENTS) {
fprintf(stderr, "lexical error: too many indentation levels! factor your code!\n");
exit(1);
}
indents[indent_levels] = indent;
indent_levels++;
}
int32_t lex_indentation(void) {
uint32_t indent_level = 0;
char c = peekc();
while (true) {
while (is_newline(c)) {
nextc();
c = peekc();
}
if (c == 0) {
indent_levels = 0;
return indent_level;
}
if (!is_indent(c)) {
break;
}
indent_level = 0;
while (is_indent(c) && indent_level < indent_levels) {
struct indent indent = indents[indent_level];
expect_indent(INDENT_TABS, indent.tabs);
expect_indent(INDENT_SPACES, indent.spaces);
indent_level++;
c = peekc();
}
if (is_indent(c)) {
new_indent();
c = peekc();
if (!is_newline(c)) {
indent_levels++;
return indent_levels;
}
}
c = peekc();
}
indent_levels = indent_level;
return indent_levels;
}