feat(lexer): add token definitions and tokenizer
This commit is contained in:
1 parent
7c8ef00d77
commit
682fd8146b
1 file changed
+139
@@ -0,0 +1,139 @@
|
|||||||
|
module Fml.Core.Lexer
|
||||||
|
|
||||||
|
open System
|
||||||
|
|
||||||
|
type Token =
|
||||||
|
| TkInt of int
|
||||||
|
| TkBool of bool
|
||||||
|
| TkStr of string
|
||||||
|
| TkIdent of string
|
||||||
|
| TkTyVar of string
|
||||||
|
| TkLet | TkRec | TkIn | TkFun | TkIf | TkThen | TkElse | TkMatch | TkWith
|
||||||
|
| TkType | TkOf | TkKwAnd | TkModule | TkStruct | TkEnd | TkOpen
|
||||||
|
| TkArrow | TkEquals | TkNotEq | TkLt | TkLe | TkGt | TkGe
|
||||||
|
| TkPlus | TkMinus | TkStar | TkSlash | TkMod | TkCaret
|
||||||
|
| TkAnd | TkOr
|
||||||
|
| TkLParen | TkRParen | TkLBracket | TkRBracket | TkLBrace | TkRBrace
|
||||||
|
| TkComma | TkSemi | TkDoubleSemi | TkCons | TkBar | TkUnderscore
|
||||||
|
| TkDot | TkColon
|
||||||
|
| TkEOF
|
||||||
|
|
||||||
|
exception LexError of string
|
||||||
|
|
||||||
|
let tokenize (src: string) : Token list =
|
||||||
|
let n = src.Length
|
||||||
|
let tokens = ResizeArray<Token>()
|
||||||
|
let mutable i = 0
|
||||||
|
let peek k = if i + k < n then src.[i + k] else '\000'
|
||||||
|
let isIdentStart c = Char.IsLetter c || c = '_'
|
||||||
|
let isIdentCont c = Char.IsLetterOrDigit c || c = '_' || c = '\''
|
||||||
|
while i < n do
|
||||||
|
let c = src.[i]
|
||||||
|
if c = ' ' || c = '\t' || c = '\r' || c = '\n' then
|
||||||
|
i <- i + 1
|
||||||
|
elif c = '(' && peek 1 = '*' then
|
||||||
|
let mutable depth = 1
|
||||||
|
i <- i + 2
|
||||||
|
while i < n && depth > 0 do
|
||||||
|
if i + 1 < n && src.[i] = '(' && src.[i + 1] = '*' then depth <- depth + 1; i <- i + 2
|
||||||
|
elif i + 1 < n && src.[i] = '*' && src.[i + 1] = ')' then depth <- depth - 1; i <- i + 2
|
||||||
|
else i <- i + 1
|
||||||
|
if depth > 0 then raise (LexError "unterminated block comment")
|
||||||
|
elif c = '"' then
|
||||||
|
let sb = Text.StringBuilder()
|
||||||
|
i <- i + 1
|
||||||
|
let mutable closed = false
|
||||||
|
while i < n && not closed do
|
||||||
|
let ch = src.[i]
|
||||||
|
if ch = '"' then
|
||||||
|
closed <- true
|
||||||
|
i <- i + 1
|
||||||
|
elif ch = '\\' then
|
||||||
|
let e = if i + 1 < n then src.[i + 1] else '\000'
|
||||||
|
let r =
|
||||||
|
match e with
|
||||||
|
| 'n' -> '\n'
|
||||||
|
| 't' -> '\t'
|
||||||
|
| 'r' -> '\r'
|
||||||
|
| '\\' -> '\\'
|
||||||
|
| '"' -> '"'
|
||||||
|
| _ -> raise (LexError (sprintf "invalid escape sequence in string"))
|
||||||
|
sb.Append(r) |> ignore
|
||||||
|
i <- i + 2
|
||||||
|
else
|
||||||
|
sb.Append(ch) |> ignore
|
||||||
|
i <- i + 1
|
||||||
|
if not closed then raise (LexError "unterminated string literal")
|
||||||
|
tokens.Add(TkStr(sb.ToString()))
|
||||||
|
elif c = '\'' && isIdentStart (peek 1) then
|
||||||
|
i <- i + 1
|
||||||
|
let start = i
|
||||||
|
while i < n && isIdentCont src.[i] do i <- i + 1
|
||||||
|
tokens.Add(TkTyVar(src.Substring(start, i - start)))
|
||||||
|
elif Char.IsDigit c then
|
||||||
|
let start = i
|
||||||
|
while i < n && Char.IsDigit src.[i] do i <- i + 1
|
||||||
|
tokens.Add(TkInt(Int32.Parse(src.Substring(start, i - start))))
|
||||||
|
elif isIdentStart c then
|
||||||
|
let start = i
|
||||||
|
while i < n && isIdentCont src.[i] do i <- i + 1
|
||||||
|
let word = src.Substring(start, i - start)
|
||||||
|
let tok =
|
||||||
|
match word with
|
||||||
|
| "let" -> TkLet
|
||||||
|
| "rec" -> TkRec
|
||||||
|
| "in" -> TkIn
|
||||||
|
| "fun" -> TkFun
|
||||||
|
| "if" -> TkIf
|
||||||
|
| "then" -> TkThen
|
||||||
|
| "else" -> TkElse
|
||||||
|
| "match" -> TkMatch
|
||||||
|
| "with" -> TkWith
|
||||||
|
| "type" -> TkType
|
||||||
|
| "of" -> TkOf
|
||||||
|
| "and" -> TkKwAnd
|
||||||
|
| "module" -> TkModule
|
||||||
|
| "struct" -> TkStruct
|
||||||
|
| "end" -> TkEnd
|
||||||
|
| "open" -> TkOpen
|
||||||
|
| "mod" -> TkMod
|
||||||
|
| "true" -> TkBool true
|
||||||
|
| "false" -> TkBool false
|
||||||
|
| "_" -> TkUnderscore
|
||||||
|
| _ -> TkIdent word
|
||||||
|
tokens.Add tok
|
||||||
|
else
|
||||||
|
let two = if i + 1 < n then src.Substring(i, 2) else ""
|
||||||
|
match two with
|
||||||
|
| "->" -> tokens.Add TkArrow; i <- i + 2
|
||||||
|
| "<>" -> tokens.Add TkNotEq; i <- i + 2
|
||||||
|
| "<=" -> tokens.Add TkLe; i <- i + 2
|
||||||
|
| ">=" -> tokens.Add TkGe; i <- i + 2
|
||||||
|
| "::" -> tokens.Add TkCons; i <- i + 2
|
||||||
|
| "&&" -> tokens.Add TkAnd; i <- i + 2
|
||||||
|
| "||" -> tokens.Add TkOr; i <- i + 2
|
||||||
|
| ";;" -> tokens.Add TkDoubleSemi; i <- i + 2
|
||||||
|
| _ ->
|
||||||
|
match c with
|
||||||
|
| '=' -> tokens.Add TkEquals; i <- i + 1
|
||||||
|
| '<' -> tokens.Add TkLt; i <- i + 1
|
||||||
|
| '>' -> tokens.Add TkGt; i <- i + 1
|
||||||
|
| '+' -> tokens.Add TkPlus; i <- i + 1
|
||||||
|
| '-' -> tokens.Add TkMinus; i <- i + 1
|
||||||
|
| '*' -> tokens.Add TkStar; i <- i + 1
|
||||||
|
| '/' -> tokens.Add TkSlash; i <- i + 1
|
||||||
|
| '^' -> tokens.Add TkCaret; i <- i + 1
|
||||||
|
| '(' -> tokens.Add TkLParen; i <- i + 1
|
||||||
|
| ')' -> tokens.Add TkRParen; i <- i + 1
|
||||||
|
| '[' -> tokens.Add TkLBracket; i <- i + 1
|
||||||
|
| ']' -> tokens.Add TkRBracket; i <- i + 1
|
||||||
|
| '{' -> tokens.Add TkLBrace; i <- i + 1
|
||||||
|
| '}' -> tokens.Add TkRBrace; i <- i + 1
|
||||||
|
| ',' -> tokens.Add TkComma; i <- i + 1
|
||||||
|
| ';' -> tokens.Add TkSemi; i <- i + 1
|
||||||
|
| '|' -> tokens.Add TkBar; i <- i + 1
|
||||||
|
| '.' -> tokens.Add TkDot; i <- i + 1
|
||||||
|
| ':' -> tokens.Add TkColon; i <- i + 1
|
||||||
|
| _ -> raise (LexError (sprintf "unexpected character '%c'" c))
|
||||||
|
tokens.Add TkEOF
|
||||||
|
List.ofSeq tokens
|
||||||
Reference in new issue
Block a user