mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 08:54:25 +00:00
The compiler is written in Finch, and nothing else: src/*.fch, built from a seed
boot/ is src/ now. The C++ compiler and CMake are gone from this branch (they are on before-bootstrap). bootstrap/build.sh builds the compiler from bootstrap/<system>.ll -- the compiler's own LLVM IR -- then builds it with itself and checks the two come out identical. bootstrap/build.sh seed writes the seed anew. The driver is complete: the runtime is compiled once and kept in ~/.cache/finch; a program's own C files, .o and .a files and frameworks link along; a linker failure is read and answered with link "lib" or the library that is missing. The old tests/fail diagnostics are all in again (a missing import, a str compared with null, 'write just the library's name', a map key that is a float, non-ASCII names, ...). tests/run.sh is the one test runner: programs, refusals, -g builds and the shared library. 106 passed.
This commit is contained in:
parent
750eca3e85
commit
b9378050ab
39 changed files with 80403 additions and 7251 deletions
223
src/lexer.fch
Normal file
223
src/lexer.fch
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
// The Finch lexer, written in Finch.
|
||||
// A token's kind is a word: "ident", "int", "float", "string", "char", "eof",
|
||||
// or the keyword / operator itself ("fn", "{", "+=", ...).
|
||||
|
||||
struct Token {
|
||||
str kind
|
||||
str text
|
||||
int line
|
||||
int col
|
||||
bool nl // a newline came before this token
|
||||
}
|
||||
|
||||
fn isAlpha(char c) -> bool {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'
|
||||
}
|
||||
|
||||
fn isDigit(char c) -> bool {
|
||||
return c >= '0' && c <= '9'
|
||||
}
|
||||
|
||||
fn isHex(char c) -> bool {
|
||||
return isDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
|
||||
}
|
||||
|
||||
fn isKeyword(str w) -> bool {
|
||||
keywords := ["fn", "return", "if", "else", "while", "for", "in", "break", "continue", "true", "false",
|
||||
"null", "import", "link", "struct", "defer", "or", "try", "enum", "match", "export"]
|
||||
return keywords.contains(w)
|
||||
}
|
||||
|
||||
struct Lexer {
|
||||
str src
|
||||
str path
|
||||
int i
|
||||
int line
|
||||
int col
|
||||
bool nl
|
||||
}
|
||||
|
||||
fn fail(ptr[Lexer] lx, int line, int col, str msg) {
|
||||
print(lx.path + ":" + str(line) + ":" + str(col) + ": error: " + msg)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
fn peek(ptr[Lexer] lx, int off) -> char {
|
||||
if lx.i + off < lx.src.len {
|
||||
return lx.src[lx.i + off]
|
||||
}
|
||||
return char(0)
|
||||
}
|
||||
|
||||
fn advance(ptr[Lexer] lx) {
|
||||
c := lx.src[lx.i]
|
||||
if c == '\n' {
|
||||
lx.line += 1
|
||||
lx.col = 1
|
||||
lx.nl = true
|
||||
} else if int(c) & 0xC0 != 0x80 {
|
||||
lx.col += 1
|
||||
}
|
||||
lx.i += 1
|
||||
}
|
||||
|
||||
// operators, longest first
|
||||
fn operator(ptr[Lexer] lx) -> str {
|
||||
twos := ["->", "<<", ">>", "<=", ">=", "==", "!=", "&&", "||", "..", ":=", "+=", "-=", "*=", "/=", "%="]
|
||||
if lx.i + 1 < lx.src.len {
|
||||
two := lx.src.sub(lx.i, lx.i + 2)
|
||||
if twos.contains(two) {
|
||||
return two
|
||||
}
|
||||
}
|
||||
ones := "(){}[],.:+-*/%&|^~<>=!"
|
||||
c := lx.src.sub(lx.i, lx.i + 1)
|
||||
if ones.contains(c) {
|
||||
return c
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn escape(ptr[Lexer] lx, char e) -> char {
|
||||
if e == 'n' {
|
||||
return '\n'
|
||||
}
|
||||
if e == 't' {
|
||||
return '\t'
|
||||
}
|
||||
if e == 'r' {
|
||||
return '\r'
|
||||
}
|
||||
if e == '0' {
|
||||
return char(0)
|
||||
}
|
||||
if e == '\\' || e == '"' || e == '\'' {
|
||||
return e
|
||||
}
|
||||
fail(lx, lx.line, lx.col - 1, "unknown escape \\" + str(e))
|
||||
return e
|
||||
}
|
||||
|
||||
fn lex(str src, str path) -> []Token {
|
||||
lexer := Lexer(src: src, path: path, i: 0, line: 1, col: 1, nl: true)
|
||||
lx := addr(lexer)
|
||||
[]Token out
|
||||
while lx.i < lx.src.len {
|
||||
c := lx.src[lx.i]
|
||||
if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
|
||||
advance(lx)
|
||||
continue
|
||||
}
|
||||
if c == '/' && peek(lx, 1) == '/' {
|
||||
while lx.i < lx.src.len && lx.src[lx.i] != '\n' {
|
||||
advance(lx)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if c == '/' && peek(lx, 1) == '*' {
|
||||
line := lx.line
|
||||
col := lx.col
|
||||
advance(lx)
|
||||
advance(lx)
|
||||
while lx.i < lx.src.len && !(lx.src[lx.i] == '*' && peek(lx, 1) == '/') {
|
||||
advance(lx)
|
||||
}
|
||||
if lx.i >= lx.src.len {
|
||||
fail(lx, line, col, "comment is never closed (missing */)")
|
||||
}
|
||||
advance(lx)
|
||||
advance(lx)
|
||||
continue
|
||||
}
|
||||
|
||||
t := Token(kind: "", text: "", line: lx.line, col: lx.col, nl: lx.nl)
|
||||
lx.nl = false
|
||||
start := lx.i
|
||||
if isAlpha(c) {
|
||||
while lx.i < lx.src.len && (isAlpha(lx.src[lx.i]) || isDigit(lx.src[lx.i])) {
|
||||
advance(lx)
|
||||
}
|
||||
t.text = lx.src.sub(start, lx.i)
|
||||
t.kind = "ident"
|
||||
if isKeyword(t.text) {
|
||||
t.kind = t.text
|
||||
}
|
||||
} else if isDigit(c) {
|
||||
t.kind = "int"
|
||||
if c == '0' && (peek(lx, 1) == 'x' || peek(lx, 1) == 'X') {
|
||||
advance(lx)
|
||||
advance(lx)
|
||||
while lx.i < lx.src.len && (isHex(lx.src[lx.i]) || lx.src[lx.i] == '_') {
|
||||
advance(lx)
|
||||
}
|
||||
} else {
|
||||
while lx.i < lx.src.len && (isDigit(lx.src[lx.i]) || lx.src[lx.i] == '_') {
|
||||
advance(lx)
|
||||
}
|
||||
if peek(lx, 0) == '.' && isDigit(peek(lx, 1)) {
|
||||
t.kind = "float"
|
||||
advance(lx)
|
||||
while lx.i < lx.src.len && (isDigit(lx.src[lx.i]) || lx.src[lx.i] == '_') {
|
||||
advance(lx)
|
||||
}
|
||||
}
|
||||
}
|
||||
t.text = lx.src.sub(start, lx.i).replace("_", "")
|
||||
} else if c == '"' || c == '\'' {
|
||||
quote := c
|
||||
t.kind = "string"
|
||||
if quote == '\'' {
|
||||
t.kind = "char"
|
||||
}
|
||||
advance(lx)
|
||||
[]str parts
|
||||
while peek(lx, 0) != quote {
|
||||
if peek(lx, 0) == char(0) || peek(lx, 0) == '\n' {
|
||||
if quote == '"' {
|
||||
fail(lx, t.line, t.col, "string is never closed (missing \")")
|
||||
}
|
||||
fail(lx, t.line, t.col, "char is never closed (missing ')")
|
||||
}
|
||||
ch := peek(lx, 0)
|
||||
advance(lx)
|
||||
if ch == '\\' {
|
||||
e := peek(lx, 0)
|
||||
advance(lx)
|
||||
ch = escape(lx, e)
|
||||
}
|
||||
parts.push(str(ch))
|
||||
}
|
||||
advance(lx)
|
||||
t.text = parts.join("")
|
||||
if quote == '\'' && t.text.len != 1 {
|
||||
fail(lx, t.line, t.col, "a char holds exactly one character, like 'a'")
|
||||
}
|
||||
} else {
|
||||
op := operator(lx)
|
||||
if op.len == 0 {
|
||||
// show the whole UTF-8 character, not just its first byte
|
||||
n := 1
|
||||
if (int(c) & 0xE0) == 0xC0 {
|
||||
n = 2
|
||||
} else if (int(c) & 0xF0) == 0xE0 {
|
||||
n = 3
|
||||
} else if (int(c) & 0xF8) == 0xF0 {
|
||||
n = 4
|
||||
}
|
||||
hint := ""
|
||||
if int(c) >= 0x80 {
|
||||
hint = " (names use only a-z, A-Z, 0-9 and _; other letters only go inside \"text\")"
|
||||
}
|
||||
fail(lx, t.line, t.col, "unexpected character '" + lx.src.sub(lx.i, lx.i + n) + "'" + hint)
|
||||
}
|
||||
for k in 0..op.len {
|
||||
advance(lx)
|
||||
}
|
||||
t.kind = op
|
||||
t.text = op
|
||||
}
|
||||
out.push(t)
|
||||
}
|
||||
out.push(Token(kind: "eof", text: "", line: lx.line, col: lx.col, nl: true))
|
||||
return out
|
||||
}
|
||||
Loading…
Reference in a new issue