Indentation-sensitive syntax!
parent
8b251bd1d6
commit
65a3f6a452
9
Makefile
9
Makefile
|
@ -6,7 +6,7 @@ SHELL = /bin/sh
|
||||||
CFLAGS = -std=c99 -pedantic -Wextra -Os
|
CFLAGS = -std=c99 -pedantic -Wextra -Os
|
||||||
LDFLAGS = -lc
|
LDFLAGS = -lc
|
||||||
|
|
||||||
OBJECTS = asm.o io.o ir.o lex.o main.o parse.o x86encode.o
|
OBJECTS = asm.o io.o ir.o lex.o lex/indent.o main.o parse.o x86encode.o
|
||||||
|
|
||||||
.PHONY: passc
|
.PHONY: passc
|
||||||
passc: .bin $(OBJECTS)
|
passc: .bin $(OBJECTS)
|
||||||
|
@ -14,11 +14,16 @@ passc: .bin $(OBJECTS)
|
||||||
|
|
||||||
.PHONY: .bin
|
.PHONY: .bin
|
||||||
.bin:
|
.bin:
|
||||||
@mkdir -p bin/obj
|
@mkdir -p bin/obj bin/obj/lex
|
||||||
|
|
||||||
%.o : src/%.c
|
%.o : src/%.c
|
||||||
$(CC) -c $(CFLAGS) $< -o bin/obj/$@
|
$(CC) -c $(CFLAGS) $< -o bin/obj/$@
|
||||||
|
|
||||||
|
# if you know how to avoid this duplication for subdirectories,
|
||||||
|
# please send a patch or tell me how!
|
||||||
|
lex/indent.o : src/lex/indent.c
|
||||||
|
$(CC) -c $(CFLAGS) $< -o bin/obj/$@
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
@-rm -rf bin
|
@-rm -rf bin
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
The grammar is LL(1).
|
The grammar is LL(1).
|
||||||
|
|
||||||
```ebnf
|
```ebnf
|
||||||
block = open-block, ("pass" | block-body), close-block ;
|
block = open-block, block-body, close-block
|
||||||
block-body = stmt, [{ terminator, stmt }] ;
|
block-body = "stmt, [{ terminator, stmt }] ;
|
||||||
stmt = assignment | expr ;
|
stmt = assignment | expr ;
|
||||||
assignment = var, [":", expr], "=", expr ;
|
assignment = var, [":", expr], "=", expr ;
|
||||||
expr = "if", expr, block, [ "else", block ]
|
expr = "if", expr, block, [ "else", block ]
|
||||||
|
@ -32,6 +32,9 @@ then the lexer is regular. If you use indentation-sensitive syntax, then lexing
|
||||||
is context-sensitive.
|
is context-sensitive.
|
||||||
|
|
||||||
```ebnf
|
```ebnf
|
||||||
|
open-block = "{" | ":", ? indentation-based ?
|
||||||
|
close-block = "}" | ? indentation-based ?
|
||||||
|
terminator = ";" | ? indentation-based ?
|
||||||
unop = "-" | "~" | "!" ;
|
unop = "-" | "~" | "!" ;
|
||||||
(* arithmetic *)
|
(* arithmetic *)
|
||||||
binop = "+" | "-" | "*" | "/" | "%"
|
binop = "+" | "-" | "*" | "/" | "%"
|
||||||
|
@ -45,7 +48,7 @@ binop = "+" | "-" | "*" | "/" | "%"
|
||||||
num = ["-"], { decimal-digit | "," }, ["#", { digit | "," }] ;
|
num = ["-"], { decimal-digit | "," }, ["#", { digit | "," }] ;
|
||||||
string = '"', [{ -('"' | newline }], '"' ;
|
string = '"', [{ -('"' | newline }], '"' ;
|
||||||
label = "'", identifier ;
|
label = "'", identifier ;
|
||||||
identifier = alpha, [{ alphanumeric }] ;
|
identifier = alpha, [{ alphanumeric | "_" }] ;
|
||||||
|
|
||||||
alpha = ? 'A'..'Z' | 'a'..'z' ? ;
|
alpha = ? 'A'..'Z' | 'a'..'z' ? ;
|
||||||
decimal-digit = ? '0'..'9' ? ;
|
decimal-digit = ? '0'..'9' ? ;
|
||||||
|
|
28
src/io.c
28
src/io.c
|
@ -106,39 +106,35 @@ void patch_i32(size_t off, int32_t x) {
|
||||||
patch_u32(off, (uint32_t) x);
|
patch_u32(off, (uint32_t) x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static _Bool init = false;
|
||||||
static char peek_buf;
|
static char peek_buf;
|
||||||
static _Bool peeked;
|
|
||||||
|
|
||||||
static char next_(void) {
|
static char next_(void) {
|
||||||
int c = getc(infile);
|
char c = getc(infile);
|
||||||
if (c == EOF) {
|
if (c == EOF) {
|
||||||
if (ferror(infile)) {
|
if (ferror(infile)) {
|
||||||
fprintf(stderr, "failed to read source file: %s\n", strerror(errno));
|
fprintf(stderr, "failed to read source file: %s\n", strerror(errno));
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
return 0;
|
c = 0;
|
||||||
}
|
}
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
char nextc(void) {
|
char nextc(void) {
|
||||||
if (peeked) {
|
if (!init) {
|
||||||
peeked = false;
|
init = true;
|
||||||
return peek_buf;
|
peek_buf = next_();
|
||||||
}
|
}
|
||||||
return next_();
|
int tmp = peek_buf;
|
||||||
}
|
peek_buf = next_();
|
||||||
|
return peek_buf;
|
||||||
void unnextc(char c) {
|
|
||||||
assert(!peeked);
|
|
||||||
peek_buf = c;
|
|
||||||
peeked = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char peekc(void) {
|
char peekc(void) {
|
||||||
if (!peeked) {
|
if (!init) {
|
||||||
peek_buf = next_();
|
init = true;
|
||||||
peeked = true;
|
return nextc();
|
||||||
}
|
}
|
||||||
return peek_buf;
|
return peek_buf;
|
||||||
}
|
}
|
||||||
|
|
124
src/lex.c
124
src/lex.c
|
@ -1,11 +1,10 @@
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "lex.h"
|
#include "lex.h"
|
||||||
|
#include "lex/indent.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
|
|
||||||
_Bool is_unary(enum operator_ op) {
|
_Bool is_unary(enum operator_ op) {
|
||||||
|
@ -40,10 +39,6 @@ _Bool is_lit(struct token tok) {
|
||||||
return tok.type == TOK_INTEGER || tok.type == TOK_STRING || tok.type == TOK_NAME;
|
return tok.type == TOK_INTEGER || tok.type == TOK_STRING || tok.type == TOK_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
static _Bool is_whitespace(char c) {
|
|
||||||
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
static _Bool is_alpha(char c) {
|
static _Bool is_alpha(char c) {
|
||||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
||||||
}
|
}
|
||||||
|
@ -52,7 +47,11 @@ static _Bool is_digit(char c) {
|
||||||
return c >= '0' && c <= '9';
|
return c >= '0' && c <= '9';
|
||||||
}
|
}
|
||||||
|
|
||||||
static _Bool id_char(char c) {
|
static _Bool is_alphanumeric(char c) {
|
||||||
|
return is_alpha(c) || is_digit(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static _Bool is_id_char(char c) {
|
||||||
return is_alpha(c) || is_digit(c) || c == '_';
|
return is_alpha(c) || is_digit(c) || c == '_';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +76,7 @@ static uint8_t digit_value(uint8_t base, char c) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static _Bool is_extended_digit(uint8_t base, char c) {
|
static _Bool is_extended_digit(uint8_t base, char c) {
|
||||||
if (!is_digit(c) && !is_alpha(c)) {
|
if (!is_alphanumeric(c)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
uint8_t val = digit_value(base, c);
|
uint8_t val = digit_value(base, c);
|
||||||
|
@ -118,19 +117,15 @@ static uint64_t lex_digits(uint8_t base) {
|
||||||
static struct token lex_integer(_Bool sign) {
|
static struct token lex_integer(_Bool sign) {
|
||||||
uint64_t acc = lex_digits(10);
|
uint64_t acc = lex_digits(10);
|
||||||
if (peekc() == '#') {
|
if (peekc() == '#') {
|
||||||
if (acc < 2) {
|
if (acc != 2 || acc != 8 && acc != 10 && acc != 16) {
|
||||||
fprintf(stderr, "lexical error: integer literal base too small\n");
|
fprintf(stderr, "lexical error: illegal integer literal base\n");
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
if (acc > 36) {
|
|
||||||
fprintf(stderr, "lexical error: integer literal base too large\n");
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
nextc();
|
nextc();
|
||||||
acc = lex_digits((uint8_t) acc);
|
acc = lex_digits((uint8_t) acc);
|
||||||
}
|
}
|
||||||
if (sign && acc > INT64_MAX) {
|
if (sign && acc > INT64_MAX) {
|
||||||
fprintf(stderr, "lexical error: integer literal overflow due to sign\n");
|
fprintf(stderr, "lexical error: signed integer literal overflow\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
int64_t val = (int64_t) acc;
|
int64_t val = (int64_t) acc;
|
||||||
|
@ -192,7 +187,7 @@ static char* lex_identifier(void) {
|
||||||
char* buf = str_buf();
|
char* buf = str_buf();
|
||||||
while (true) {
|
while (true) {
|
||||||
char c = peekc();
|
char c = peekc();
|
||||||
if (!is_alpha(c) && !is_digit(c) && c != '_') break;
|
if (!is_id_char(c)) break;
|
||||||
nextc();
|
nextc();
|
||||||
buf[str_index] = c;
|
buf[str_index] = c;
|
||||||
str_index++;
|
str_index++;
|
||||||
|
@ -205,44 +200,91 @@ static char* lex_identifier(void) {
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint32_t indent_level = 0;
|
||||||
|
static uint32_t pending_level = 0;
|
||||||
|
static _Bool level_is_block[MAX_INDENTS] = {true};
|
||||||
|
// going back to a previous indentation level.
|
||||||
|
// if we're going back, then we insert a terminator.
|
||||||
|
static _Bool going_back = false;
|
||||||
|
|
||||||
static struct token lex(void) {
|
static struct token lex(void) {
|
||||||
char c;
|
char c = peekc();
|
||||||
do {
|
if (is_newline(c)) {
|
||||||
c = nextc();
|
indent_level = lex_indentation();
|
||||||
} while (is_whitespace(c));
|
if (indent_level <= pending_level) {
|
||||||
|
going_back = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (indent_level > pending_level) {
|
||||||
|
pending_level++;
|
||||||
|
if (level_is_block[pending_level]) {
|
||||||
|
return simple(TOK_OPEN_BLOCK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (indent_level < pending_level) {
|
||||||
|
_Bool was_block = level_is_block[pending_level];
|
||||||
|
level_is_block[pending_level] = false;
|
||||||
|
pending_level--;
|
||||||
|
if (was_block) {
|
||||||
|
return simple(TOK_CLOSE_BLOCK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (going_back) {
|
||||||
|
going_back = false;
|
||||||
|
if (level_is_block[indent_level]) {
|
||||||
|
return simple(TOK_TERMINATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c = peekc();
|
||||||
|
while (is_indent(c)) {
|
||||||
|
nextc();
|
||||||
|
c = peekc();
|
||||||
|
}
|
||||||
_Bool sign = false;
|
_Bool sign = false;
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 0:
|
case 0:
|
||||||
|
nextc();
|
||||||
return simple(TOK_EOF);
|
return simple(TOK_EOF);
|
||||||
case '"': {
|
case '"': {
|
||||||
|
nextc();
|
||||||
union token_data data;
|
union token_data data;
|
||||||
data.string = lex_string();
|
data.string = lex_string();
|
||||||
struct token tok = { TOK_STRING, data };
|
struct token tok = { TOK_STRING, data };
|
||||||
return tok;
|
return tok;
|
||||||
}
|
}
|
||||||
case '\'': {
|
case '\'': {
|
||||||
|
nextc();
|
||||||
union token_data data;
|
union token_data data;
|
||||||
data.label = lex_identifier();
|
data.label = lex_identifier();
|
||||||
struct token tok = { TOK_LABEL, data };
|
struct token tok = { TOK_LABEL, data };
|
||||||
return tok;
|
return tok;
|
||||||
}
|
}
|
||||||
case '{':
|
case '{':
|
||||||
|
nextc();
|
||||||
return simple(TOK_OPEN_BLOCK);
|
return simple(TOK_OPEN_BLOCK);
|
||||||
case '}':
|
case '}':
|
||||||
|
nextc();
|
||||||
return simple(TOK_CLOSE_BLOCK);
|
return simple(TOK_CLOSE_BLOCK);
|
||||||
case '(':
|
case '(':
|
||||||
|
nextc();
|
||||||
return simple(TOK_OPEN_GROUP);
|
return simple(TOK_OPEN_GROUP);
|
||||||
case ')':
|
case ')':
|
||||||
|
nextc();
|
||||||
return simple(TOK_CLOSE_GROUP);
|
return simple(TOK_CLOSE_GROUP);
|
||||||
case ';':
|
case ';':
|
||||||
|
nextc();
|
||||||
return simple(TOK_TERMINATOR);
|
return simple(TOK_TERMINATOR);
|
||||||
case ',':
|
case ',':
|
||||||
|
nextc();
|
||||||
return simple(TOK_SEPARATOR);
|
return simple(TOK_SEPARATOR);
|
||||||
case '=':
|
case '=':
|
||||||
|
nextc();
|
||||||
return op(OP_EQ);
|
return op(OP_EQ);
|
||||||
case '+':
|
case '+':
|
||||||
|
nextc();
|
||||||
return op(OP_ADD);
|
return op(OP_ADD);
|
||||||
case '-':
|
case '-':
|
||||||
|
nextc();
|
||||||
if (peekc() == '>') {
|
if (peekc() == '>') {
|
||||||
nextc();
|
nextc();
|
||||||
return op(OP_FUN);
|
return op(OP_FUN);
|
||||||
|
@ -252,28 +294,45 @@ static struct token lex(void) {
|
||||||
}
|
}
|
||||||
return op(OP_SUB);
|
return op(OP_SUB);
|
||||||
case '*':
|
case '*':
|
||||||
|
nextc();
|
||||||
return op(OP_MUL);
|
return op(OP_MUL);
|
||||||
case '/':
|
case '/':
|
||||||
|
nextc();
|
||||||
return op(OP_DIV);
|
return op(OP_DIV);
|
||||||
case '%':
|
case '%':
|
||||||
|
nextc();
|
||||||
return op(OP_MOD);
|
return op(OP_MOD);
|
||||||
case '~':
|
case '~':
|
||||||
|
nextc();
|
||||||
return op(OP_INV);
|
return op(OP_INV);
|
||||||
case '&':
|
case '&':
|
||||||
|
nextc();
|
||||||
return op(OP_AND);
|
return op(OP_AND);
|
||||||
case '|':
|
case '|':
|
||||||
|
nextc();
|
||||||
return op(OP_OR);
|
return op(OP_OR);
|
||||||
case '^':
|
case '^':
|
||||||
|
nextc();
|
||||||
return op(OP_XOR);
|
return op(OP_XOR);
|
||||||
case '!':
|
case '!':
|
||||||
|
nextc();
|
||||||
if (peekc() == '=') {
|
if (peekc() == '=') {
|
||||||
nextc();
|
nextc();
|
||||||
return op(OP_NE);
|
return op(OP_NE);
|
||||||
}
|
}
|
||||||
return op(OP_NOT);
|
return op(OP_NOT);
|
||||||
case ':':
|
case ':':
|
||||||
|
nextc();
|
||||||
|
while (is_indent(peekc())) {
|
||||||
|
nextc();
|
||||||
|
}
|
||||||
|
if (is_newline(peekc())) {
|
||||||
|
level_is_block[indent_level + 1] = true;
|
||||||
|
return lex();
|
||||||
|
}
|
||||||
return op(OP_TYPE);
|
return op(OP_TYPE);
|
||||||
case '>':
|
case '>':
|
||||||
|
nextc();
|
||||||
c = peekc();
|
c = peekc();
|
||||||
if (c == '=') {
|
if (c == '=') {
|
||||||
nextc();
|
nextc();
|
||||||
|
@ -289,6 +348,7 @@ static struct token lex(void) {
|
||||||
}
|
}
|
||||||
return op(OP_GT);
|
return op(OP_GT);
|
||||||
case '<':
|
case '<':
|
||||||
|
nextc();
|
||||||
c = peekc();
|
c = peekc();
|
||||||
if (c == '<') {
|
if (c == '<') {
|
||||||
nextc();
|
nextc();
|
||||||
|
@ -300,7 +360,6 @@ static struct token lex(void) {
|
||||||
}
|
}
|
||||||
return op(OP_LT);
|
return op(OP_LT);
|
||||||
}
|
}
|
||||||
unnextc(c);
|
|
||||||
if (is_digit(c)) {
|
if (is_digit(c)) {
|
||||||
return lex_integer(false);
|
return lex_integer(false);
|
||||||
}
|
}
|
||||||
|
@ -310,28 +369,21 @@ static struct token lex(void) {
|
||||||
return tok;
|
return tok;
|
||||||
}
|
}
|
||||||
|
|
||||||
static _Bool peeked = false;
|
static _Bool init = false;
|
||||||
static struct token peek_buf;
|
static struct token peek_buf;
|
||||||
|
|
||||||
struct token next(void) {
|
struct token next(void) {
|
||||||
if (peeked) {
|
if (!init) {
|
||||||
peeked = false;
|
init = true;
|
||||||
return peek_buf;
|
indent_level = lex_indentation();
|
||||||
|
next();
|
||||||
}
|
}
|
||||||
return lex();
|
struct token tmp = peek_buf;
|
||||||
}
|
peek_buf = lex();
|
||||||
|
return tmp;
|
||||||
void unnext(struct token tok) {
|
|
||||||
assert(!peeked);
|
|
||||||
peeked = true;
|
|
||||||
peek_buf = tok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct token peek(void) {
|
struct token peek(void) {
|
||||||
if (!peeked) {
|
|
||||||
peek_buf = lex();
|
|
||||||
peeked = true;
|
|
||||||
}
|
|
||||||
return peek_buf;
|
return peek_buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,6 @@ _Bool is_binary(enum operator_ op);
|
||||||
_Bool is_lit(struct token tok);
|
_Bool is_lit(struct token tok);
|
||||||
|
|
||||||
struct token next(void);
|
struct token next(void);
|
||||||
void unnext(struct token tok);
|
|
||||||
struct token peek(void);
|
struct token peek(void);
|
||||||
|
|
||||||
void print_token(struct token tok);
|
void print_token(struct token tok);
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
///
|
||||||
|
/// 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;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
#ifndef LEX_INDENT_H
|
||||||
|
#define LEX_INDENT_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define MAX_INDENTS 32
|
||||||
|
|
||||||
|
_Bool is_newline(char c);
|
||||||
|
_Bool is_indent(char c);
|
||||||
|
|
||||||
|
int32_t lex_indentation(void);
|
||||||
|
|
||||||
|
#endif
|
77
src/parse.c
77
src/parse.c
|
@ -9,8 +9,10 @@
|
||||||
#include "parse.h"
|
#include "parse.h"
|
||||||
|
|
||||||
enum state {
|
enum state {
|
||||||
ST_BLOCK_OPEN,
|
|
||||||
ST_BLOCK,
|
ST_BLOCK,
|
||||||
|
ST_BLOCK_BODY,
|
||||||
|
ST_BLOCK_CONT,
|
||||||
|
ST_BLOCK_CLOSE,
|
||||||
ST_ASSIGN,
|
ST_ASSIGN,
|
||||||
ST_EXPR,
|
ST_EXPR,
|
||||||
ST_EXPR_CONT,
|
ST_EXPR_CONT,
|
||||||
|
@ -18,15 +20,18 @@ enum state {
|
||||||
ST_IF_ELSE,
|
ST_IF_ELSE,
|
||||||
ST_LOOP_VARS,
|
ST_LOOP_VARS,
|
||||||
ST_LOOP_VARS_CONT,
|
ST_LOOP_VARS_CONT,
|
||||||
ST_LABEL,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const char* state_name(enum state st) {
|
const char* state_name(enum state st) {
|
||||||
switch (st) {
|
switch (st) {
|
||||||
case ST_BLOCK_OPEN:
|
|
||||||
return "{";
|
|
||||||
case ST_BLOCK:
|
case ST_BLOCK:
|
||||||
|
return "{";
|
||||||
|
case ST_BLOCK_BODY:
|
||||||
|
return "B";
|
||||||
|
case ST_BLOCK_CONT:
|
||||||
return ";";
|
return ";";
|
||||||
|
case ST_BLOCK_CLOSE:
|
||||||
|
return "}";
|
||||||
case ST_ASSIGN:
|
case ST_ASSIGN:
|
||||||
return "=";
|
return "=";
|
||||||
case ST_EXPR:
|
case ST_EXPR:
|
||||||
|
@ -41,8 +46,6 @@ const char* state_name(enum state st) {
|
||||||
return "v";
|
return "v";
|
||||||
case ST_LOOP_VARS_CONT:
|
case ST_LOOP_VARS_CONT:
|
||||||
return ",";
|
return ",";
|
||||||
case ST_LABEL:
|
|
||||||
return "'";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,79 +91,86 @@ static _Bool is_expr(struct token tok) {
|
||||||
#define syntax_error(msg) fprintf(stderr, "syntax error: %s\n", msg); exit(1)
|
#define syntax_error(msg) fprintf(stderr, "syntax error: %s\n", msg); exit(1)
|
||||||
|
|
||||||
void parse(void) {
|
void parse(void) {
|
||||||
|
sp = 0;
|
||||||
// TODO: add support for the top-level instead of this block hack
|
// TODO: add support for the top-level instead of this block hack
|
||||||
push(ST_BLOCK);
|
push(ST_BLOCK_BODY);
|
||||||
struct token tok = next();
|
struct token tok = next();
|
||||||
struct token nxt;
|
struct token nxt = peek();
|
||||||
while (sp > 0) {
|
while (sp > 0) {
|
||||||
nxt = peek();
|
|
||||||
debug_print(tok, nxt);
|
debug_print(tok, nxt);
|
||||||
// FIXME: stack underflow because we're faking the top-level with blocks
|
// FIXME: stack underflow because we're faking the top-level with blocks
|
||||||
switch (pop()) {
|
switch (pop()) {
|
||||||
case ST_BLOCK_OPEN:
|
|
||||||
if (tok.type != TOK_OPEN_BLOCK) {
|
|
||||||
syntax_error("expected open block (`{`)");
|
|
||||||
}
|
|
||||||
push(ST_BLOCK);
|
|
||||||
break;
|
|
||||||
case ST_BLOCK:
|
case ST_BLOCK:
|
||||||
if (tok.type == TOK_CLOSE_BLOCK) {
|
if (tok.type == TOK_OPEN_BLOCK) {
|
||||||
break;
|
push(ST_BLOCK_CLOSE);
|
||||||
}
|
push(ST_BLOCK_BODY);
|
||||||
if (tok.type == TOK_TERMINATOR) {
|
|
||||||
push(ST_BLOCK);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
syntax_error("expected beginning of block");
|
||||||
|
break;
|
||||||
|
case ST_BLOCK_BODY:
|
||||||
if (is_assignment(tok, nxt)) {
|
if (is_assignment(tok, nxt)) {
|
||||||
push(ST_BLOCK);
|
push(ST_BLOCK_CONT);
|
||||||
push(ST_ASSIGN);
|
push(ST_ASSIGN);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (is_expr(tok)) {
|
if (is_expr(tok)) {
|
||||||
push(ST_BLOCK);
|
push(ST_BLOCK_CONT);
|
||||||
push(ST_EXPR);
|
push(ST_EXPR);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
break;
|
continue;
|
||||||
|
case ST_BLOCK_CONT:
|
||||||
|
if (tok.type == TOK_TERMINATOR) {
|
||||||
|
push(ST_BLOCK_BODY);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
case ST_BLOCK_CLOSE:
|
||||||
|
if (tok.type == TOK_CLOSE_BLOCK) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
syntax_error("expected end of block");
|
||||||
case ST_ASSIGN:
|
case ST_ASSIGN:
|
||||||
assert(tok.type == TOK_OPERATOR || tok.data.op == OP_EQ);
|
assert(tok.type == TOK_OPERATOR || tok.data.op == OP_EQ);
|
||||||
push(ST_EXPR);
|
push(ST_EXPR);
|
||||||
break;
|
break;
|
||||||
case ST_EXPR:
|
case ST_EXPR:
|
||||||
push(ST_EXPR_CONT);
|
|
||||||
if (tok.type == TOK_STRING) {
|
if (tok.type == TOK_STRING) {
|
||||||
|
push(ST_EXPR_CONT);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (tok.type == TOK_INTEGER) {
|
if (tok.type == TOK_INTEGER) {
|
||||||
|
push(ST_EXPR_CONT);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (tok.type == TOK_NAME) {
|
if (tok.type == TOK_NAME) {
|
||||||
char* name = tok.data.name;
|
char* name = tok.data.name;
|
||||||
if (strcmp(name, "if") == 0) {
|
if (strcmp(name, "if") == 0) {
|
||||||
push(ST_IF_ELSE);
|
push(ST_IF_ELSE);
|
||||||
push(ST_BLOCK_OPEN);
|
push(ST_BLOCK);
|
||||||
push(ST_EXPR);
|
push(ST_EXPR);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (strcmp(name, "loop") == 0) {
|
if (strcmp(name, "loop") == 0) {
|
||||||
push(ST_BLOCK_OPEN);
|
push(ST_BLOCK);
|
||||||
push(ST_LOOP_VARS);
|
push(ST_LOOP_VARS);
|
||||||
if (nxt.type == TOK_LABEL) {
|
if (nxt.type == TOK_LABEL) {
|
||||||
push(ST_LABEL);
|
next();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (strcmp(name, "next") == 0) {
|
if (strcmp(name, "next") == 0) {
|
||||||
push(ST_LOOP_VARS);
|
push(ST_LOOP_VARS);
|
||||||
if (nxt.type == TOK_LABEL) {
|
if (nxt.type == TOK_LABEL) {
|
||||||
push(ST_LABEL);
|
next();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (strcmp(name, "exit") == 0) {
|
if (strcmp(name, "exit") == 0) {
|
||||||
push(ST_EXPR);
|
push(ST_EXPR);
|
||||||
if (nxt.type == TOK_LABEL) {
|
if (nxt.type == TOK_LABEL) {
|
||||||
push(ST_LABEL);
|
next();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -168,14 +178,17 @@ void parse(void) {
|
||||||
push(ST_EXPR);
|
push(ST_EXPR);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
push(ST_EXPR_CONT);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (tok.type == TOK_OPEN_GROUP) {
|
if (tok.type == TOK_OPEN_GROUP) {
|
||||||
|
push(ST_EXPR_CONT);
|
||||||
push(ST_GROUP);
|
push(ST_GROUP);
|
||||||
push(ST_EXPR);
|
push(ST_EXPR);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (tok.type == TOK_OPERATOR && is_unary(tok.data.op)) {
|
if (tok.type == TOK_OPERATOR && is_unary(tok.data.op)) {
|
||||||
|
push(ST_EXPR_CONT);
|
||||||
push(ST_EXPR);
|
push(ST_EXPR);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -197,7 +210,7 @@ void parse(void) {
|
||||||
syntax_error("mismatched parentheses");
|
syntax_error("mismatched parentheses");
|
||||||
case ST_IF_ELSE:
|
case ST_IF_ELSE:
|
||||||
if (tok.type == TOK_NAME && strcmp(tok.data.name, "else") == 0) {
|
if (tok.type == TOK_NAME && strcmp(tok.data.name, "else") == 0) {
|
||||||
push(ST_BLOCK_OPEN);
|
push(ST_BLOCK);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
@ -218,11 +231,9 @@ void parse(void) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
case ST_LABEL:
|
|
||||||
assert(tok.type == TOK_LABEL);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
tok = next();
|
tok = next();
|
||||||
|
nxt = peek();
|
||||||
}
|
}
|
||||||
if (tok.type != TOK_EOF) {
|
if (tok.type != TOK_EOF) {
|
||||||
fprintf(stderr, "syntax error: finished parsing before end of file\n");
|
fprintf(stderr, "syntax error: finished parsing before end of file\n");
|
||||||
|
|
Loading…
Reference in New Issue