Data commit

This commit is contained in:
Ingy döt Net 2023-07-01 11:58:00 -04:00
parent 7387c8f97b
commit cb5bb5e222
199093 changed files with 3378972 additions and 0 deletions

View file

@ -0,0 +1,191 @@
import re, strformat, strutils
type
TokenKind = enum
tkUnknown = "UNKNOWN_TOKEN",
tkMul = "Op_multiply",
tkDiv = "Op_divide",
tkMod = "Op_mod",
tkAdd = "Op_add",
tkSub = "Op_subtract",
tkNeg = "Op_negate",
tkLt = "Op_less",
tkLte = "Op_lessequal",
tkGt = "Op_greater",
tkGte = "Op_greaterequal",
tkEq = "Op_equal",
tkNeq = "Op_notequal",
tkNot = "Op_not",
tkAsgn = "Op_assign",
tkAnd = "Op_and",
tkOr = "Op_or",
tkLpar = "LeftParen",
tkRpar = "RightParen",
tkLbra = "LeftBrace",
tkRbra = "RightBrace",
tkSmc = "Semicolon",
tkCom = "Comma",
tkIf = "Keyword_if",
tkElse = "Keyword_else",
tkWhile = "Keyword_while",
tkPrint = "Keyword_print",
tkPutc = "Keyword_putc",
tkId = "Identifier",
tkInt = "Integer",
tkChar = "Integer",
tkStr = "String",
tkEof = "End_of_input"
Token = object
kind: TokenKind
value: string
TokenAnn = object
## Annotated token with messages for compiler
token: Token
line, column: int
proc getSymbols(table: openArray[(char, TokenKind)]): seq[char] =
result = newSeq[char]()
for ch, tokenKind in items(table):
result.add ch
const
tkSymbols = { # single-char tokens
'*': tkMul,
'%': tkMod,
'+': tkAdd,
'-': tkSub,
'(': tkLpar,
')': tkRpar,
'{': tkLbra,
'}': tkRbra,
';': tkSmc,
',': tkCom,
'/': tkDiv, # the comment case /* ... */ is handled in `stripUnimportant`
}
symbols = getSymbols(tkSymbols)
proc findTokenKind(table: openArray[(char, TokenKind)]; needle: char):
TokenKind =
for ch, tokenKind in items(table):
if ch == needle: return tokenKind
tkUnknown
proc stripComment(text: var string, lineNo, colNo: var int) =
var matches: array[1, string]
if match(text, re"\A(/\*[\s\S]*?\*/)", matches):
text = text[matches[0].len..^1]
for s in matches[0]:
if s == '\n':
inc lineNo
colNo = 1
else:
inc colNo
proc stripUnimportant(text: var string; lineNo, colNo: var int) =
while true:
if text.len == 0: return
elif text[0] == '\n':
inc lineNo
colNo = 1
text = text[1..^1]
elif text[0] == ' ':
inc colNo
text = text[1..^1]
elif text.len >= 2 and text[0] == '/' and text[1] == '*':
stripComment(text, lineNo, colNo)
else: return
proc lookAhead(ch1, ch2: char, tk1, tk2: TokenKind): (TokenKind, int) =
if ch1 == ch2: (tk1, 2)
else: (tk2, 1)
proc consumeToken(text: var string; tkl: var int): Token =
## Return token removing it from the `text` and write its length to
## `tkl`. If the token can not be defined, return `tkUnknown` as a
## token, shrink text by 1 and write 1 to its length.
var
matches: array[1, string]
tKind: TokenKind
val: string
if text.len == 0:
(tKind, tkl) = (tkEof, 0)
# Simple characters
elif text[0] in symbols: (tKind, tkl) = (tkSymbols.findTokenKind(text[0]), 1)
elif text[0] == '<': (tKind, tkl) = lookAhead(text[1], '=', tkLte, tkLt)
elif text[0] == '>': (tKind, tkl) = lookAhead(text[1], '=', tkGte, tkGt)
elif text[0] == '=': (tKind, tkl) = lookAhead(text[1], '=', tkEq, tkAsgn)
elif text[0] == '!': (tKind, tkl) = lookAhead(text[1], '=', tkNeq, tkNot)
elif text[0] == '&': (tKind, tkl) = lookAhead(text[1], '&', tkAnd, tkUnknown)
elif text[0] == '|': (tKind, tkl) = lookAhead(text[1], '|', tkOr, tkUnknown)
# Keywords
elif match(text, re"\Aif\b"): (tKind, tkl) = (tkIf, 2)
elif match(text, re"\Aelse\b"): (tKind, tkl) = (tkElse, 4)
elif match(text, re"\Awhile\b"): (tKind, tkl) = (tkWhile, 5)
elif match(text, re"\Aprint\b"): (tKind, tkl) = (tkPrint, 5)
elif match(text, re"\Aputc\b"): (tKind, tkl) = (tkPutc, 4)
# Literals and identifiers
elif match(text, re"\A([0-9]+)", matches):
(tKind, tkl) = (tkInt, matches[0].len)
val = matches[0]
elif match(text, re"\A([_a-zA-Z][_a-zA-Z0-9]*)", matches):
(tKind, tkl) = (tkId, matches[0].len)
val = matches[0]
elif match(text, re"\A('(?:[^'\n]|\\\\|\\n)')", matches):
(tKind, tkl) = (tkChar, matches[0].len)
val = case matches[0]
of r"' '": $ord(' ')
of r"'\n'": $ord('\n')
of r"'\\'": $ord('\\')
else: $ord(matches[0][1]) # "'a'"[1] == 'a'
elif match(text, re"\A(""[^""\n]*"")", matches):
(tKind, tkl) = (tkStr, matches[0].len)
val = matches[0]
else: (tKind, tkl) = (tkUnknown, 1)
text = text[tkl..^1]
Token(kind: tKind, value: val)
proc tokenize*(text: string): seq[TokenAnn] =
result = newSeq[TokenAnn]()
var
lineNo, colNo: int = 1
text = text
token: Token
tokenLength: int
while text.len > 0:
stripUnimportant(text, lineNo, colNo)
token = consumeToken(text, tokenLength)
result.add TokenAnn(token: token, line: lineNo, column: colNo)
inc colNo, tokenLength
proc output*(s: seq[TokenAnn]): string =
var
tokenKind: TokenKind
value: string
line, column: int
for tokenAnn in items(s):
line = tokenAnn.line
column = tokenAnn.column
tokenKind = tokenAnn.token.kind
value = tokenAnn.token.value
result.add(
fmt"{line:>5}{column:>7} {tokenKind:<15}{value}"
.strip(leading = false) & "\n")
when isMainModule:
import os
let input = if paramCount() > 0: readFile paramStr(1)
else: readAll stdin
echo input.tokenize.output

View file

@ -0,0 +1,310 @@
import lexbase, streams
from strutils import Whitespace
type
TokenKind = enum
tkInvalid = "Invalid",
tkOpMultiply = "Op_multiply",
tkOpDivide = "Op_divide",
tkOpMod = "Op_mod",
tkOpAdd = "Op_add",
tkOpSubtract = "Op_subtract",
tkOpLess = "Op_less",
tkOpLessEqual = "Op_lessequal",
tkOpGreater = "Op_greater",
tkOpGreaterEqual = "Op_greaterequal",
tkOpEqual = "Op_equal",
tkOpNotEqual = "Op_notequal",
tkOpNot = "Op_not",
tkOpAssign = "Op_assign",
tkOpAnd = "Op_and",
tkOpOr = "Op_or",
tkLeftParen = "LeftParen",
tkRightParen = "RightParen",
tkLeftBrace = "LeftBrace",
tkRightBrace = "RightBrace",
tkSemicolon = "Semicolon",
tkComma = "Comma",
tkKeywordIf = "Keyword_if",
tkKeywordElse = "Keyword_else",
tkKeywordWhile = "Keyword_while",
tkKeywordPrint = "Keyword_print",
tkKeywordPutc = "Keyword_putc",
tkIdentifier = "Identifier",
tkInteger = "Integer",
tkString = "String",
tkEndOfInput = "End_of_input"
Lexer = object of BaseLexer
kind: TokenKind
token, error: string
startPos: int
template setError(l: var Lexer; err: string): untyped =
l.kind = tkInvalid
if l.error.len == 0:
l.error = err
proc hasError(l: Lexer): bool {.inline.} =
l.error.len > 0
proc open(l: var Lexer; input: Stream) {.inline.} =
lexbase.open(l, input)
l.startPos = 0
l.kind = tkInvalid
l.token = ""
l.error = ""
proc handleNewLine(l: var Lexer) =
case l.buf[l.bufpos]
of '\c': l.bufpos = l.handleCR l.bufpos
of '\n': l.bufpos = l.handleLF l.bufpos
else: discard
proc skip(l: var Lexer) =
while true:
case l.buf[l.bufpos]
of Whitespace:
if l.buf[l.bufpos] notin NewLines:
inc l.bufpos
else:
handleNewLine l
of '/':
if l.buf[l.bufpos + 1] == '*':
inc l.bufpos, 2
while true:
case l.buf[l.bufpos]
of '*':
if l.buf[l.bufpos + 1] == '/':
inc l.bufpos, 2
break
else: inc l.bufpos
of NewLines:
handleNewLine l
of EndOfFile:
setError l, "EOF reached in comment"
return
else:
inc l.bufpos
else: break
else: break
proc handleSpecial(l: var Lexer): char =
assert l.buf[l.bufpos] == '\\'
inc l.bufpos
case l.buf[l.bufpos]
of 'n':
l.token.add "\\n"
result = '\n'
inc l.bufpos
of '\\':
l.token.add "\\\\"
result = '\\'
inc l.bufpos
else:
setError l, "Unknown escape sequence: '\\" & l.buf[l.bufpos] & "'"
result = '\0'
proc handleChar(l: var Lexer) =
assert l.buf[l.bufpos] == '\''
l.startPos = l.getColNumber l.bufpos
l.kind = tkInvalid
inc l.bufpos
if l.buf[l.bufpos] == '\\':
l.token = $ord(handleSpecial l)
if hasError l: return
elif l.buf[l.bufpos] == '\'':
setError l, "Empty character constant"
return
else:
l.token = $ord(l.buf[l.bufpos])
inc l.bufpos
if l.buf[l.bufpos] == '\'':
l.kind = tkInteger
inc l.bufpos
else:
setError l, "Multi-character constant"
proc handleString(l: var Lexer) =
assert l.buf[l.bufpos] == '"'
l.startPos = l.getColNumber l.bufpos
l.token = "\""
inc l.bufpos
while true:
case l.buf[l.bufpos]
of '\\':
discard handleSpecial l
if hasError l: return
of '"':
l.kind = tkString
add l.token, '"'
inc l.bufpos
break
of NewLines:
setError l, "EOL reached before end-of-string"
return
of EndOfFile:
setError l, "EOF reached before end-of-string"
return
else:
add l.token, l.buf[l.bufpos]
inc l.bufpos
proc handleNumber(l: var Lexer) =
assert l.buf[l.bufpos] in {'0'..'9'}
l.startPos = l.getColNumber l.bufpos
l.token = "0"
while l.buf[l.bufpos] == '0': inc l.bufpos
while true:
case l.buf[l.bufpos]
of '0'..'9':
if l.token == "0":
setLen l.token, 0
add l.token, l.buf[l.bufpos]
inc l.bufpos
of 'a'..'z', 'A'..'Z', '_':
setError l, "Invalid number"
return
else:
l.kind = tkInteger
break
proc handleIdent(l: var Lexer) =
assert l.buf[l.bufpos] in {'a'..'z'}
l.startPos = l.getColNumber l.bufpos
setLen l.token, 0
while true:
if l.buf[l.bufpos] in {'a'..'z', 'A'..'Z', '0'..'9', '_'}:
add l.token, l.buf[l.bufpos]
inc l.bufpos
else:
break
l.kind = case l.token
of "if": tkKeywordIf
of "else": tkKeywordElse
of "while": tkKeywordWhile
of "print": tkKeywordPrint
of "putc": tkKeywordPutc
else: tkIdentifier
proc getToken(l: var Lexer): TokenKind =
l.kind = tkInvalid
setLen l.token, 0
skip l
case l.buf[l.bufpos]
of '*':
l.kind = tkOpMultiply
l.startPos = l.getColNumber l.bufpos
inc l.bufpos
of '/':
l.kind = tkOpDivide
l.startPos = l.getColNumber l.bufpos
inc l.bufpos
of '%':
l.kind = tkOpMod
l.startPos = l.getColNumber l.bufpos
inc l.bufpos
of '+':
l.kind = tkOpAdd
l.startPos = l.getColNumber l.bufpos
inc l.bufpos
of '-':
l.kind = tkOpSubtract
l.startPos = l.getColNumber l.bufpos
inc l.bufpos
of '<':
l.kind = tkOpLess
l.startPos = l.getColNumber l.bufpos
inc l.bufpos
if l.buf[l.bufpos] == '=':
l.kind = tkOpLessEqual
inc l.bufpos
of '>':
l.kind = tkOpGreater
l.startPos = l.getColNumber l.bufpos
inc l.bufpos
if l.buf[l.bufpos] == '=':
l.kind = tkOpGreaterEqual
inc l.bufpos
of '=':
l.kind = tkOpAssign
l.startPos = l.getColNumber l.bufpos
inc l.bufpos
if l.buf[l.bufpos] == '=':
l.kind = tkOpEqual
inc l.bufpos
of '!':
l.kind = tkOpNot
l.startPos = l.getColNumber l.bufpos
inc l.bufpos
if l.buf[l.bufpos] == '=':
l.kind = tkOpNotEqual
inc l.bufpos
of '&':
if l.buf[l.bufpos + 1] == '&':
l.kind = tkOpAnd
l.startPos = l.getColNumber l.bufpos
inc l.bufpos, 2
else:
setError l, "Unrecognized character"
of '|':
if l.buf[l.bufpos + 1] == '|':
l.kind = tkOpOr
l.startPos = l.getColNumber l.bufpos
inc l.bufpos, 2
else:
setError l, "Unrecognized character"
of '(':
l.kind = tkLeftParen
l.startPos = l.getColNumber l.bufpos
inc l.bufpos
of ')':
l.kind = tkRightParen
l.startPos = l.getColNumber l.bufpos
inc l.bufpos
of '{':
l.kind = tkLeftBrace
l.startPos = l.getColNumber l.bufpos
inc l.bufpos
of '}':
l.kind = tkRightBrace
l.startPos = l.getColNumber l.bufpos
inc l.bufpos
of ';':
l.kind = tkSemicolon
l.startPos = l.getColNumber l.bufpos
inc l.bufpos
of ',':
l.kind = tkComma
l.startPos = l.getColNumber l.bufpos
inc l.bufpos
of '\'': handleChar l
of '"': handleString l
of '0'..'9': handleNumber l
of 'a'..'z', 'A'..'Z': handleIdent l
of EndOfFile:
l.startPos = l.getColNumber l.bufpos
l.kind = tkEndOfInput
else:
setError l, "Unrecognized character"
result = l.kind
when isMainModule:
import os, strformat
proc main() =
var l: Lexer
if paramCount() < 1:
open l, newFileStream stdin
else:
open l, newFileStream paramStr(1)
while l.getToken notin {tkInvalid}:
stdout.write &"{l.lineNumber:5} {l.startPos + 1:5} {l.kind:<14}"
if l.kind in {tkIdentifier, tkInteger, tkString}:
stdout.write &" {l.token}"
stdout.write '\n'
if l.kind == tkEndOfInput:
break
if hasError l:
echo &"({l.lineNumber},{l.getColNumber l.bufpos + 1}) {l.error}"
main()

View file

@ -0,0 +1,221 @@
import strutils
type
TokenKind* = enum
tokMult = "Op_multiply", tokDiv = "Op_divide", tokMod = "Op_mod",
tokAdd = "Op_add", tokSub = "Op_subtract", tokLess = "Op_less",
tokLessEq = "Op_lessequal", tokGreater = "Op_greater",
tokGreaterEq = "Op_greaterequal", tokEq = "Op_equal",
tokNotEq = "Op_notequal", tokNot = "Op_not", tokAssign = "Op_assign",
tokAnd = "Op_and", tokOr = "Op_or"
tokLPar = "LeftParen", tokRPar = "RightParen"
tokLBrace = "LeftBrace", tokRBrace = "RightBrace"
tokSemi = "Semicolon", tokComma = "Comma"
tokIf = "Keyword_if", tokElse = "Keyword_else", tokWhile = "Keyword_while",
tokPrint = "Keyword_print", tokPutc = "Keyword_putc"
tokIdent = "Identifier", tokInt = "Integer", tokChar = "Integer",
tokString = "String"
tokEnd = "End_of_input"
Token* = object
ln*, col*: int
case kind*: TokenKind
of tokIdent: ident*: string
of tokInt: intVal*: int
of tokChar: charVal*: char
of tokString: stringVal*: string
else: discard
Lexer* = object
input: string
pos: int
ln, col: int
LexicalError* = object of CatchableError
ln*, col*: int
proc error(lexer: var Lexer, message: string) =
var err = newException(LexicalError, message)
err.ln = lexer.ln
err.col = lexer.col
template current: char =
if lexer.pos < lexer.input.len: lexer.input[lexer.pos]
else: '\x00'
template get(n: int): string =
if lexer.pos < lexer.input.len:
lexer.input[min(lexer.pos, lexer.input.len)..
min(lexer.pos + n - 1, lexer.input.len)]
else: ""
template next() =
inc(lexer.pos); inc(lexer.col)
if current() == '\n':
inc(lexer.ln)
lexer.col = 0
elif current() == '\r':
lexer.col = 0
proc skip(lexer: var Lexer) =
while true:
if current() in Whitespace:
while current() in Whitespace:
next()
continue
elif get(2) == "/*":
next(); next()
while get(2) != "*/":
if current() == '\x00':
lexer.error("Unterminated comment")
next()
next(); next()
continue
else: discard
break
proc charOrEscape(lexer: var Lexer): char =
if current() != '\\':
result = current()
next()
else:
next()
case current()
of 'n': result = '\n'
of '\\': result = '\\'
else: lexer.error("Unknown escape sequence '\\" & current() & "'")
next()
proc next*(lexer: var Lexer): Token =
let
ln = lexer.ln
col = lexer.col
case current()
of '*': result = Token(kind: tokMult); next()
of '/': result = Token(kind: tokDiv); next()
of '%': result = Token(kind: tokMod); next()
of '+': result = Token(kind: tokAdd); next()
of '-': result = Token(kind: tokSub); next()
of '<':
next()
if current() == '=': result = Token(kind: tokLessEq)
else: result = Token(kind: tokLess)
of '>':
next()
if current() == '=':
result = Token(kind: tokGreaterEq)
next()
else:
result = Token(kind: tokGreater)
of '=':
next()
if current() == '=':
result = Token(kind: tokEq)
next()
else:
result = Token(kind: tokAssign)
of '!':
next()
if current() == '=':
result = Token(kind: tokNotEq)
next()
else:
result = Token(kind: tokNot)
of '&':
next()
if current() == '&':
result = Token(kind: tokAnd)
next()
else:
lexer.error("'&&' expected")
of '|':
next()
if current() == '|':
result = Token(kind: tokOr)
next()
else:
lexer.error("'||' expected")
of '(': result = Token(kind: tokLPar); next()
of ')': result = Token(kind: tokRPar); next()
of '{': result = Token(kind: tokLBrace); next()
of '}': result = Token(kind: tokRBrace); next()
of ';': result = Token(kind: tokSemi); next()
of ',': result = Token(kind: tokComma); next()
of '\'':
next()
if current() == '\'': lexer.error("Empty character literal")
let ch = lexer.charOrEscape()
if current() != '\'':
lexer.error("Character literal must contain a single character or " &
"escape sequence")
result = Token(kind: tokChar, charVal: ch)
next()
of '0'..'9':
var number = ""
while current() in Digits:
number.add(current())
next()
if current() in IdentStartChars:
lexer.error("Integer literal ends in non-digit characters")
result = Token(kind: tokInt, intVal: parseInt(number))
of '"':
next()
var str = ""
while current() notin {'"', '\x00', '\n'}:
str.add(lexer.charOrEscape())
if current() == '\x00':
lexer.error("Unterminated string literal")
elif current() == '\n':
lexer.error("Line feed in string literal")
else:
next()
result = Token(kind: tokString, stringVal: str)
of IdentStartChars:
var ident = $current()
next()
while current() in IdentChars:
ident.add(current())
next()
case ident
of "if": result = Token(kind: tokIf)
of "else": result = Token(kind: tokElse)
of "while": result = Token(kind: tokWhile)
of "print": result = Token(kind: tokPrint)
of "putc": result = Token(kind: tokPutc)
else: result = Token(kind: tokIdent, ident: ident)
of '\x00':
result = Token(kind: tokEnd)
else:
lexer.error("Unexpected character: '" & current() & "'")
result.ln = ln
result.col = col
lexer.skip()
proc peek*(lexer: var Lexer): Token =
discard
proc initLexer*(input: string): Lexer =
result = Lexer(input: input, pos: 0, ln: 1, col: 1)
result.skip()
when isMainModule:
let code = readAll(stdin)
var
lexer = initLexer(code)
token: Token
while true:
token = lexer.next()
stdout.write(token.ln, ' ', token.col, ' ', token.kind)
case token.kind
of tokInt: stdout.write(' ', token.intVal)
of tokChar: stdout.write(' ', token.charVal.ord)
of tokString: stdout.write(" \"", token.stringVal
.replace("\\", "\\\\")
.replace("\n", "\\n"), '"')
of tokIdent: stdout.write(' ', token.ident)
else: discard
stdout.write('\n')
if token.kind == tokEnd:
break