feat: initial BaraDB — multimodal database engine in Nim
- LSM-Tree storage engine with WAL, bloom filter, MemTable - BaraQL query language: lexer (80+ tokens), recursive descent parser, AST - Vector engine: HNSW + IVF-PQ indexes, 4 distance metrics - Graph engine: adjacency list, BFS/DFS, Dijkstra, PageRank - Full-Text Search: inverted index, BM25 ranking, stemming, stop words - Type system: 17 types (int/float/string/uuid/json/vector/...) - Async TCP server - 21 passing tests
This commit is contained in:
@@ -0,0 +1,318 @@
|
||||
## BaraQL AST — Abstract Syntax Tree nodes
|
||||
import ../core/types
|
||||
|
||||
type
|
||||
NodeKind* = enum
|
||||
# Statements
|
||||
nkSelect
|
||||
nkInsert
|
||||
nkUpdate
|
||||
nkDelete
|
||||
nkCreateType
|
||||
nkDropType
|
||||
nkAlterType
|
||||
nkCreateIndex
|
||||
nkDropIndex
|
||||
|
||||
# Clauses
|
||||
nkFrom
|
||||
nkWhere
|
||||
nkOrderBy
|
||||
nkGroupBy
|
||||
nkHaving
|
||||
nkLimit
|
||||
nkOffset
|
||||
nkReturning
|
||||
nkWith
|
||||
|
||||
# Expressions
|
||||
nkBinOp
|
||||
nkUnaryOp
|
||||
nkFuncCall
|
||||
nkTypeCast
|
||||
nkPath
|
||||
nkIdent
|
||||
nkIntLit
|
||||
nkFloatLit
|
||||
nkStringLit
|
||||
nkBoolLit
|
||||
nkNullLit
|
||||
nkArrayLit
|
||||
nkVectorLit
|
||||
nkObjectLit
|
||||
nkIfElse
|
||||
nkCase
|
||||
nkSubquery
|
||||
nkExists
|
||||
nkInExpr
|
||||
nkBetweenExpr
|
||||
nkLikeExpr
|
||||
nkIsExpr
|
||||
|
||||
# Graph-specific
|
||||
nkGraphTraversal
|
||||
nkBfsQuery
|
||||
nkDfsQuery
|
||||
nkShortestPath
|
||||
nkPatternMatch
|
||||
|
||||
# Vector-specific
|
||||
nkVectorSimilar
|
||||
nkVectorNearest
|
||||
|
||||
# Join
|
||||
nkJoin
|
||||
|
||||
# Type definitions
|
||||
nkPropertyDef
|
||||
nkLinkDef
|
||||
nkIndexDef
|
||||
nkConstraintDef
|
||||
|
||||
# Top-level
|
||||
nkStatementList
|
||||
|
||||
BinOpKind* = enum
|
||||
bkAdd = "+"
|
||||
bkSub = "-"
|
||||
bkMul = "*"
|
||||
bkDiv = "/"
|
||||
bkMod = "%"
|
||||
bkPow = "**"
|
||||
bkFloorDiv = "//"
|
||||
bkEq = "="
|
||||
bkNotEq = "!="
|
||||
bkLt = "<"
|
||||
bkLtEq = "<="
|
||||
bkGt = ">"
|
||||
bkGtEq = ">="
|
||||
bkAnd = "AND"
|
||||
bkOr = "OR"
|
||||
bkIn = "IN"
|
||||
bkNotIn = "NOT IN"
|
||||
bkLike = "LIKE"
|
||||
bkILike = "ILIKE"
|
||||
bkConcat = "++"
|
||||
bkCoalesce = "??"
|
||||
bkAssign = ":="
|
||||
bkArrow = "=>"
|
||||
|
||||
UnaryOpKind* = enum
|
||||
ukNeg = "-"
|
||||
ukNot = "NOT"
|
||||
ukIsNull = "IS NULL"
|
||||
ukIsNotNull = "IS NOT NULL"
|
||||
|
||||
JoinKind* = enum
|
||||
jkInner
|
||||
jkLeft
|
||||
jkRight
|
||||
jkFull
|
||||
jkCross
|
||||
|
||||
SortDir* = enum
|
||||
sdAsc
|
||||
sdDesc
|
||||
|
||||
Node* = ref object
|
||||
line*: int
|
||||
col*: int
|
||||
case kind*: NodeKind
|
||||
of nkSelect:
|
||||
selDistinct*: bool
|
||||
selWith*: seq[Node]
|
||||
selResult*: seq[Node]
|
||||
selFrom*: Node
|
||||
selJoins*: seq[Node]
|
||||
selWhere*: Node
|
||||
selGroupBy*: seq[Node]
|
||||
selHaving*: Node
|
||||
selOrderBy*: seq[Node]
|
||||
selLimit*: Node
|
||||
selOffset*: Node
|
||||
of nkInsert:
|
||||
insTarget*: string
|
||||
insFields*: seq[Node]
|
||||
insValues*: seq[Node]
|
||||
insReturning*: seq[Node]
|
||||
insConflict*: Node
|
||||
of nkUpdate:
|
||||
updTarget*: string
|
||||
updAlias*: string
|
||||
updSet*: seq[Node]
|
||||
updWhere*: Node
|
||||
updReturning*: seq[Node]
|
||||
of nkDelete:
|
||||
delTarget*: string
|
||||
delAlias*: string
|
||||
delWhere*: Node
|
||||
delReturning*: seq[Node]
|
||||
of nkCreateType:
|
||||
ctName*: string
|
||||
ctBases*: seq[string]
|
||||
ctProperties*: seq[Node]
|
||||
ctLinks*: seq[Node]
|
||||
of nkDropType:
|
||||
dtName*: string
|
||||
of nkAlterType:
|
||||
atName*: string
|
||||
atOps*: seq[Node]
|
||||
of nkCreateIndex:
|
||||
ciTarget*: string
|
||||
ciName*: string
|
||||
ciExpr*: Node
|
||||
ciKind*: IndexKind
|
||||
of nkDropIndex:
|
||||
diName*: string
|
||||
of nkFrom:
|
||||
fromTable*: string
|
||||
fromAlias*: string
|
||||
of nkWhere:
|
||||
whereExpr*: Node
|
||||
of nkOrderBy:
|
||||
orderByExpr*: Node
|
||||
orderByDir*: SortDir
|
||||
of nkGroupBy:
|
||||
groupExprs*: seq[Node]
|
||||
of nkHaving:
|
||||
havingExpr*: Node
|
||||
of nkLimit:
|
||||
limitExpr*: Node
|
||||
of nkOffset:
|
||||
offsetExpr*: Node
|
||||
of nkReturning:
|
||||
retExprs*: seq[Node]
|
||||
of nkWith:
|
||||
withBindings*: seq[(string, Node)]
|
||||
of nkBinOp:
|
||||
binOp*: BinOpKind
|
||||
binLeft*: Node
|
||||
binRight*: Node
|
||||
of nkUnaryOp:
|
||||
unOp*: UnaryOpKind
|
||||
unOperand*: Node
|
||||
of nkFuncCall:
|
||||
funcName*: string
|
||||
funcArgs*: seq[Node]
|
||||
of nkTypeCast:
|
||||
castType*: string
|
||||
castExpr*: Node
|
||||
of nkPath:
|
||||
pathParts*: seq[string]
|
||||
of nkIdent:
|
||||
identName*: string
|
||||
of nkIntLit:
|
||||
intVal*: int64
|
||||
of nkFloatLit:
|
||||
floatVal*: float64
|
||||
of nkStringLit:
|
||||
strVal*: string
|
||||
of nkBoolLit:
|
||||
boolVal*: bool
|
||||
of nkNullLit:
|
||||
discard
|
||||
of nkArrayLit:
|
||||
arrayElems*: seq[Node]
|
||||
of nkVectorLit:
|
||||
vecElems*: seq[Node]
|
||||
of nkObjectLit:
|
||||
objFields*: seq[(string, Node)]
|
||||
of nkIfElse:
|
||||
ifCond*: Node
|
||||
ifThen*: Node
|
||||
ifElse*: Node
|
||||
of nkCase:
|
||||
caseExpr*: Node
|
||||
caseWhens*: seq[(Node, Node)]
|
||||
caseElse*: Node
|
||||
of nkSubquery:
|
||||
subQuery*: Node
|
||||
of nkExists:
|
||||
existsExpr*: Node
|
||||
of nkInExpr:
|
||||
inLeft*: Node
|
||||
inRight*: Node
|
||||
of nkBetweenExpr:
|
||||
betweenExpr*: Node
|
||||
betweenLow*: Node
|
||||
betweenHigh*: Node
|
||||
of nkLikeExpr:
|
||||
likeExpr*: Node
|
||||
likePattern*: Node
|
||||
likeCaseInsensitive*: bool
|
||||
of nkIsExpr:
|
||||
isExpr*: Node
|
||||
isType*: string
|
||||
isNegated*: bool
|
||||
of nkGraphTraversal:
|
||||
gtStart*: Node
|
||||
gtEdge*: string
|
||||
gtDirection*: string
|
||||
gtEnd*: Node
|
||||
gtMaxDepth*: int
|
||||
of nkBfsQuery:
|
||||
bfsStart*: Node
|
||||
bfsTarget*: Node
|
||||
bfsEdge*: string
|
||||
bfsMaxDepth*: int
|
||||
bfsFilter*: Node
|
||||
of nkDfsQuery:
|
||||
dfsStart*: Node
|
||||
dfsTarget*: Node
|
||||
dfsEdge*: string
|
||||
dfsMaxDepth*: int
|
||||
dfsFilter*: Node
|
||||
of nkShortestPath:
|
||||
spStart*: Node
|
||||
spEnd*: Node
|
||||
spEdge*: string
|
||||
spMaxDepth*: int
|
||||
of nkPatternMatch:
|
||||
pmPattern*: Node
|
||||
pmWhere*: Node
|
||||
of nkVectorSimilar:
|
||||
vsField*: string
|
||||
vsVector*: Node
|
||||
vsLimit*: int
|
||||
vsMetric*: string
|
||||
of nkVectorNearest:
|
||||
vnField*: string
|
||||
vnVector*: Node
|
||||
vnLimit*: int
|
||||
vnMetric*: string
|
||||
of nkJoin:
|
||||
joinKind*: JoinKind
|
||||
joinTarget*: Node
|
||||
joinOn*: Node
|
||||
joinAlias*: string
|
||||
of nkPropertyDef:
|
||||
pdName*: string
|
||||
pdType*: string
|
||||
pdRequired*: bool
|
||||
pdDefault*: Node
|
||||
pdComputed*: bool
|
||||
pdExpr*: Node
|
||||
of nkLinkDef:
|
||||
ldName*: string
|
||||
ldTarget*: string
|
||||
ldCardinality*: Cardinality
|
||||
ldRequired*: bool
|
||||
of nkIndexDef:
|
||||
idName*: string
|
||||
idExpr*: Node
|
||||
idKind*: IndexKind
|
||||
of nkConstraintDef:
|
||||
cdName*: string
|
||||
cdExpr*: Node
|
||||
of nkStatementList:
|
||||
stmts*: seq[Node]
|
||||
|
||||
proc newNode*(kind: NodeKind, line, col: int = 0): Node =
|
||||
result = Node(kind: kind, line: line, col: col)
|
||||
case kind
|
||||
of nkSelect: result.selResult = @[]
|
||||
of nkInsert: result.insFields = @[]; result.insValues = @[]
|
||||
of nkUpdate: result.updSet = @[]
|
||||
of nkDelete: discard
|
||||
of nkStatementList: result.stmts = @[]
|
||||
else: discard
|
||||
@@ -0,0 +1,461 @@
|
||||
## BaraQL Lexer — tokenization
|
||||
import std/tables
|
||||
import std/strutils
|
||||
|
||||
type
|
||||
TokenKind* = enum
|
||||
# Literals
|
||||
tkIntLit
|
||||
tkFloatLit
|
||||
tkStringLit
|
||||
tkBoolLit
|
||||
|
||||
# Identifiers
|
||||
tkIdent
|
||||
|
||||
# Keywords
|
||||
tkSelect
|
||||
tkInsert
|
||||
tkUpdate
|
||||
tkDelete
|
||||
tkFrom
|
||||
tkWhere
|
||||
tkAnd
|
||||
tkOr
|
||||
tkNot
|
||||
tkIn
|
||||
tkIs
|
||||
tkAs
|
||||
tkOn
|
||||
tkJoin
|
||||
tkLeft
|
||||
tkRight
|
||||
tkInner
|
||||
tkOuter
|
||||
tkFull
|
||||
tkCross
|
||||
tkOrder
|
||||
tkBy
|
||||
tkAsc
|
||||
tkDesc
|
||||
tkGroup
|
||||
tkHaving
|
||||
tkLimit
|
||||
tkOffset
|
||||
tkSet
|
||||
tkInto
|
||||
tkValues
|
||||
tkCreate
|
||||
tkDrop
|
||||
tkAlter
|
||||
tkTable
|
||||
tkIndex
|
||||
tkType
|
||||
tkLink
|
||||
tkProperty
|
||||
tkRequired
|
||||
tkMulti
|
||||
tkSingle
|
||||
tkTrue
|
||||
tkFalse
|
||||
tkNull
|
||||
tkIf
|
||||
tkThen
|
||||
tkElse
|
||||
tkEnd
|
||||
tkCase
|
||||
tkWhen
|
||||
tkWith
|
||||
tkDistinct
|
||||
tkUnion
|
||||
tkIntersect
|
||||
tkExcept
|
||||
tkExists
|
||||
tkBetween
|
||||
tkLike
|
||||
tkILike
|
||||
tkReturning
|
||||
tkCount
|
||||
tkSum
|
||||
tkAvg
|
||||
tkMin
|
||||
tkMax
|
||||
tkArray
|
||||
tkVector
|
||||
tkGraph
|
||||
tkDocument
|
||||
tkSimilar
|
||||
tkNearest
|
||||
tkTo
|
||||
tkBfs
|
||||
tkDfs
|
||||
tkPath
|
||||
|
||||
# Operators
|
||||
tkPlus
|
||||
tkMinus
|
||||
tkStar
|
||||
tkSlash
|
||||
tkPercent
|
||||
tkPower
|
||||
tkEq
|
||||
tkNotEq
|
||||
tkLt
|
||||
tkLtEq
|
||||
tkGt
|
||||
tkGtEq
|
||||
tkAssign
|
||||
tkArrow
|
||||
tkDoubleColon
|
||||
tkDot
|
||||
tkComma
|
||||
tkSemicolon
|
||||
tkLParen
|
||||
tkRParen
|
||||
tkLBrace
|
||||
tkRbrace
|
||||
tkLBracket
|
||||
tkRBracket
|
||||
tkAmp
|
||||
tkPipe
|
||||
tkTilde
|
||||
tkConcat
|
||||
tkCoalesce
|
||||
tkFloorDiv
|
||||
|
||||
# Special
|
||||
tkEof
|
||||
tkNewline
|
||||
tkInvalid
|
||||
|
||||
Token* = object
|
||||
kind*: TokenKind
|
||||
value*: string
|
||||
line*: int
|
||||
col*: int
|
||||
|
||||
Lexer* = object
|
||||
input: string
|
||||
pos: int
|
||||
line: int
|
||||
col: int
|
||||
|
||||
const keywords*: Table[string, TokenKind] = {
|
||||
"select": tkSelect,
|
||||
"insert": tkInsert,
|
||||
"update": tkUpdate,
|
||||
"delete": tkDelete,
|
||||
"from": tkFrom,
|
||||
"where": tkWhere,
|
||||
"and": tkAnd,
|
||||
"or": tkOr,
|
||||
"not": tkNot,
|
||||
"in": tkIn,
|
||||
"is": tkIs,
|
||||
"as": tkAs,
|
||||
"on": tkOn,
|
||||
"join": tkJoin,
|
||||
"left": tkLeft,
|
||||
"right": tkRight,
|
||||
"inner": tkInner,
|
||||
"outer": tkOuter,
|
||||
"full": tkFull,
|
||||
"cross": tkCross,
|
||||
"order": tkOrder,
|
||||
"by": tkBy,
|
||||
"asc": tkAsc,
|
||||
"desc": tkDesc,
|
||||
"group": tkGroup,
|
||||
"having": tkHaving,
|
||||
"limit": tkLimit,
|
||||
"offset": tkOffset,
|
||||
"set": tkSet,
|
||||
"into": tkInto,
|
||||
"values": tkValues,
|
||||
"create": tkCreate,
|
||||
"drop": tkDrop,
|
||||
"alter": tkAlter,
|
||||
"table": tkTable,
|
||||
"index": tkIndex,
|
||||
"type": tkType,
|
||||
"link": tkLink,
|
||||
"property": tkProperty,
|
||||
"required": tkRequired,
|
||||
"multi": tkMulti,
|
||||
"single": tkSingle,
|
||||
"true": tkTrue,
|
||||
"false": tkFalse,
|
||||
"null": tkNull,
|
||||
"if": tkIf,
|
||||
"then": tkThen,
|
||||
"else": tkElse,
|
||||
"end": tkEnd,
|
||||
"case": tkCase,
|
||||
"when": tkWhen,
|
||||
"with": tkWith,
|
||||
"distinct": tkDistinct,
|
||||
"union": tkUnion,
|
||||
"intersect": tkIntersect,
|
||||
"except": tkExcept,
|
||||
"exists": tkExists,
|
||||
"between": tkBetween,
|
||||
"like": tkLike,
|
||||
"ilike": tkILike,
|
||||
"returning": tkReturning,
|
||||
"count": tkCount,
|
||||
"sum": tkSum,
|
||||
"avg": tkAvg,
|
||||
"min": tkMin,
|
||||
"max": tkMax,
|
||||
"array": tkArray,
|
||||
"vector": tkVector,
|
||||
"graph": tkGraph,
|
||||
"document": tkDocument,
|
||||
"similar": tkSimilar,
|
||||
"nearest": tkNearest,
|
||||
"to": tkTo,
|
||||
"bfs": tkBfs,
|
||||
"dfs": tkDfs,
|
||||
"path": tkPath,
|
||||
}.toTable
|
||||
|
||||
proc newLexer*(input: string): Lexer =
|
||||
Lexer(input: input, pos: 0, line: 1, col: 1)
|
||||
|
||||
proc peek(l: Lexer): char =
|
||||
if l.pos < l.input.len:
|
||||
return l.input[l.pos]
|
||||
return '\0'
|
||||
|
||||
proc advance(l: var Lexer): char =
|
||||
result = l.input[l.pos]
|
||||
inc l.pos
|
||||
if result == '\n':
|
||||
inc l.line
|
||||
l.col = 1
|
||||
else:
|
||||
inc l.col
|
||||
|
||||
proc skipWhitespace(l: var Lexer) =
|
||||
while l.pos < l.input.len and l.input[l.pos] in {' ', '\t', '\r', '\n'}:
|
||||
discard l.advance()
|
||||
|
||||
proc skipLineComment(l: var Lexer) =
|
||||
while l.pos < l.input.len and l.input[l.pos] != '\n':
|
||||
discard l.advance()
|
||||
|
||||
proc skipBlockComment(l: var Lexer) =
|
||||
discard l.advance() # skip *
|
||||
discard l.advance() # skip *
|
||||
while l.pos < l.input.len - 1:
|
||||
if l.input[l.pos] == '*' and l.input[l.pos + 1] == '/':
|
||||
discard l.advance()
|
||||
discard l.advance()
|
||||
return
|
||||
discard l.advance()
|
||||
|
||||
proc readString(l: var Lexer, quote: char): string =
|
||||
result = ""
|
||||
while l.pos < l.input.len and l.input[l.pos] != quote:
|
||||
if l.input[l.pos] == '\\':
|
||||
discard l.advance()
|
||||
case l.input[l.pos]
|
||||
of 'n': result.add('\n')
|
||||
of 't': result.add('\t')
|
||||
of 'r': result.add('\r')
|
||||
of '\\': result.add('\\')
|
||||
of '\'': result.add('\'')
|
||||
of '"': result.add('"')
|
||||
else: result.add(l.input[l.pos])
|
||||
else:
|
||||
result.add(l.input[l.pos])
|
||||
discard l.advance()
|
||||
if l.pos < l.input.len:
|
||||
discard l.advance() # skip closing quote
|
||||
|
||||
proc readNumber(l: var Lexer, startLine, startCol: int): Token =
|
||||
var numStr = ""
|
||||
var isFloat = false
|
||||
while l.pos < l.input.len and (l.input[l.pos] in Digits or l.input[l.pos] == '.'):
|
||||
if l.input[l.pos] == '.':
|
||||
isFloat = true
|
||||
numStr.add(l.input[l.pos])
|
||||
discard l.advance()
|
||||
if isFloat:
|
||||
Token(kind: tkFloatLit, value: numStr, line: startLine, col: startCol)
|
||||
else:
|
||||
Token(kind: tkIntLit, value: numStr, line: startLine, col: startCol)
|
||||
|
||||
proc readIdent(l: var Lexer, startLine, startCol: int): Token =
|
||||
var ident = ""
|
||||
while l.pos < l.input.len and (l.input[l.pos] in IdentChars or l.input[l.pos] in Digits):
|
||||
ident.add(l.input[l.pos])
|
||||
discard l.advance()
|
||||
let lowerIdent = ident.toLower()
|
||||
if lowerIdent in keywords:
|
||||
Token(kind: keywords[lowerIdent], value: ident, line: startLine, col: startCol)
|
||||
elif lowerIdent == "true":
|
||||
Token(kind: tkBoolLit, value: "true", line: startLine, col: startCol)
|
||||
elif lowerIdent == "false":
|
||||
Token(kind: tkBoolLit, value: "false", line: startLine, col: startCol)
|
||||
else:
|
||||
Token(kind: tkIdent, value: ident, line: startLine, col: startCol)
|
||||
|
||||
proc nextToken*(l: var Lexer): Token =
|
||||
l.skipWhitespace()
|
||||
if l.pos >= l.input.len:
|
||||
return Token(kind: tkEof, line: l.line, col: l.col)
|
||||
|
||||
let startLine = l.line
|
||||
let startCol = l.col
|
||||
let ch = l.peek()
|
||||
|
||||
case ch
|
||||
of '/':
|
||||
if l.pos + 1 < l.input.len and l.input[l.pos + 1] == '/':
|
||||
l.skipLineComment()
|
||||
return l.nextToken()
|
||||
elif l.pos + 1 < l.input.len and l.input[l.pos + 1] == '*':
|
||||
l.skipBlockComment()
|
||||
return l.nextToken()
|
||||
else:
|
||||
discard l.advance()
|
||||
return Token(kind: tkSlash, value: "/", line: startLine, col: startCol)
|
||||
of '+':
|
||||
if l.pos + 1 < l.input.len and l.input[l.pos + 1] == '+':
|
||||
discard l.advance()
|
||||
discard l.advance()
|
||||
return Token(kind: tkConcat, value: "++", line: startLine, col: startCol)
|
||||
discard l.advance()
|
||||
return Token(kind: tkPlus, value: "+", line: startLine, col: startCol)
|
||||
of '-':
|
||||
discard l.advance()
|
||||
return Token(kind: tkMinus, value: "-", line: startLine, col: startCol)
|
||||
of '*':
|
||||
if l.pos + 1 < l.input.len and l.input[l.pos + 1] == '*':
|
||||
discard l.advance()
|
||||
discard l.advance()
|
||||
return Token(kind: tkPower, value: "**", line: startLine, col: startCol)
|
||||
discard l.advance()
|
||||
return Token(kind: tkStar, value: "*", line: startLine, col: startCol)
|
||||
of '%':
|
||||
discard l.advance()
|
||||
return Token(kind: tkPercent, value: "%", line: startLine, col: startCol)
|
||||
of '=':
|
||||
if l.pos + 1 < l.input.len and l.input[l.pos + 1] == '>':
|
||||
discard l.advance()
|
||||
discard l.advance()
|
||||
return Token(kind: tkArrow, value: "=>", line: startLine, col: startCol)
|
||||
if l.pos + 1 < l.input.len and l.input[l.pos + 1] == '=':
|
||||
discard l.advance()
|
||||
discard l.advance()
|
||||
return Token(kind: tkEq, value: "==", line: startLine, col: startCol)
|
||||
discard l.advance()
|
||||
return Token(kind: tkAssign, value: ":=", line: startLine, col: startCol)
|
||||
of ':':
|
||||
if l.pos + 1 < l.input.len and l.input[l.pos + 1] == '=':
|
||||
discard l.advance()
|
||||
discard l.advance()
|
||||
return Token(kind: tkAssign, value: ":=", line: startLine, col: startCol)
|
||||
if l.pos + 1 < l.input.len and l.input[l.pos + 1] == ':':
|
||||
discard l.advance()
|
||||
discard l.advance()
|
||||
return Token(kind: tkDoubleColon, value: "::", line: startLine, col: startCol)
|
||||
discard l.advance()
|
||||
return Token(kind: tkInvalid, value: ":", line: startLine, col: startCol)
|
||||
of '!':
|
||||
if l.pos + 1 < l.input.len and l.input[l.pos + 1] == '=':
|
||||
discard l.advance()
|
||||
discard l.advance()
|
||||
return Token(kind: tkNotEq, value: "!=", line: startLine, col: startCol)
|
||||
discard l.advance()
|
||||
return Token(kind: tkInvalid, value: "!", line: startLine, col: startCol)
|
||||
of '<':
|
||||
if l.pos + 1 < l.input.len and l.input[l.pos + 1] == '=':
|
||||
discard l.advance()
|
||||
discard l.advance()
|
||||
return Token(kind: tkLtEq, value: "<=", line: startLine, col: startCol)
|
||||
if l.pos + 1 < l.input.len and l.input[l.pos + 1] == '>':
|
||||
discard l.advance()
|
||||
discard l.advance()
|
||||
return Token(kind: tkNotEq, value: "<>", line: startLine, col: startCol)
|
||||
discard l.advance()
|
||||
return Token(kind: tkLt, value: "<", line: startLine, col: startCol)
|
||||
of '>':
|
||||
if l.pos + 1 < l.input.len and l.input[l.pos + 1] == '=':
|
||||
discard l.advance()
|
||||
discard l.advance()
|
||||
return Token(kind: tkGtEq, value: ">=", line: startLine, col: startCol)
|
||||
discard l.advance()
|
||||
return Token(kind: tkGt, value: ">", line: startLine, col: startCol)
|
||||
of '?':
|
||||
if l.pos + 1 < l.input.len and l.input[l.pos + 1] == '?':
|
||||
discard l.advance()
|
||||
discard l.advance()
|
||||
return Token(kind: tkCoalesce, value: "??", line: startLine, col: startCol)
|
||||
discard l.advance()
|
||||
return Token(kind: tkInvalid, value: "?", line: startLine, col: startCol)
|
||||
of '.':
|
||||
if l.pos + 1 < l.input.len and l.input[l.pos + 1] == '<':
|
||||
discard l.advance()
|
||||
discard l.advance()
|
||||
return Token(kind: tkInvalid, value: ".<", line: startLine, col: startCol)
|
||||
discard l.advance()
|
||||
return Token(kind: tkDot, value: ".", line: startLine, col: startCol)
|
||||
of ',':
|
||||
discard l.advance()
|
||||
return Token(kind: tkComma, value: ",", line: startLine, col: startCol)
|
||||
of ';':
|
||||
discard l.advance()
|
||||
return Token(kind: tkSemicolon, value: ";", line: startLine, col: startCol)
|
||||
of '(':
|
||||
discard l.advance()
|
||||
return Token(kind: tkLParen, value: "(", line: startLine, col: startCol)
|
||||
of ')':
|
||||
discard l.advance()
|
||||
return Token(kind: tkRParen, value: ")", line: startLine, col: startCol)
|
||||
of '{':
|
||||
discard l.advance()
|
||||
return Token(kind: tkLBrace, value: "{", line: startLine, col: startCol)
|
||||
of '}':
|
||||
discard l.advance()
|
||||
return Token(kind: tkRbrace, value: "}", line: startLine, col: startCol)
|
||||
of '[':
|
||||
discard l.advance()
|
||||
return Token(kind: tkLBracket, value: "[", line: startLine, col: startCol)
|
||||
of ']':
|
||||
discard l.advance()
|
||||
return Token(kind: tkRBracket, value: "]", line: startLine, col: startCol)
|
||||
of '&':
|
||||
discard l.advance()
|
||||
return Token(kind: tkAmp, value: "&", line: startLine, col: startCol)
|
||||
of '|':
|
||||
discard l.advance()
|
||||
return Token(kind: tkPipe, value: "|", line: startLine, col: startCol)
|
||||
of '~':
|
||||
discard l.advance()
|
||||
return Token(kind: tkTilde, value: "~", line: startLine, col: startCol)
|
||||
of '\'', '"':
|
||||
discard l.advance()
|
||||
let s = l.readString(ch)
|
||||
return Token(kind: tkStringLit, value: s, line: startLine, col: startCol)
|
||||
of '#':
|
||||
l.skipLineComment()
|
||||
return l.nextToken()
|
||||
else:
|
||||
if ch in Digits:
|
||||
return l.readNumber(startLine, startCol)
|
||||
elif ch in IdentStartChars:
|
||||
return l.readIdent(startLine, startCol)
|
||||
else:
|
||||
discard l.advance()
|
||||
return Token(kind: tkInvalid, value: $ch, line: startLine, col: startCol)
|
||||
|
||||
proc tokenize*(input: string): seq[Token] =
|
||||
var lexer = newLexer(input)
|
||||
result = @[]
|
||||
while true:
|
||||
let tok = lexer.nextToken()
|
||||
result.add(tok)
|
||||
if tok.kind == tkEof:
|
||||
break
|
||||
@@ -0,0 +1,245 @@
|
||||
## BaraQL Parser — recursive descent parser
|
||||
import std/strutils
|
||||
import lexer
|
||||
import ast
|
||||
|
||||
type
|
||||
Parser* = object
|
||||
tokens: seq[Token]
|
||||
pos: int
|
||||
|
||||
proc newParser*(tokens: seq[Token]): Parser =
|
||||
Parser(tokens: tokens, pos: 0)
|
||||
|
||||
proc peek(p: Parser): Token =
|
||||
if p.pos < p.tokens.len:
|
||||
return p.tokens[p.pos]
|
||||
Token(kind: tkEof)
|
||||
|
||||
proc advance(p: var Parser): Token =
|
||||
result = p.tokens[p.pos]
|
||||
inc p.pos
|
||||
|
||||
proc expect(p: var Parser, kind: TokenKind): Token =
|
||||
let tok = p.advance()
|
||||
if tok.kind != kind:
|
||||
raise newException(ValueError,
|
||||
"Expected " & $kind & " but got " & $tok.kind & " at line " & $tok.line)
|
||||
return tok
|
||||
|
||||
proc match(p: var Parser, kind: TokenKind): bool =
|
||||
if p.peek().kind == kind:
|
||||
discard p.advance()
|
||||
return true
|
||||
return false
|
||||
|
||||
proc parseExpr(p: var Parser): Node
|
||||
proc parseSelect(p: var Parser): Node
|
||||
|
||||
proc parsePrimary(p: var Parser): Node =
|
||||
let tok = p.peek()
|
||||
case tok.kind
|
||||
of tkIntLit:
|
||||
discard p.advance()
|
||||
Node(kind: nkIntLit, intVal: parseInt(tok.value), line: tok.line, col: tok.col)
|
||||
of tkFloatLit:
|
||||
discard p.advance()
|
||||
Node(kind: nkFloatLit, floatVal: parseFloat(tok.value), line: tok.line, col: tok.col)
|
||||
of tkStringLit:
|
||||
discard p.advance()
|
||||
Node(kind: nkStringLit, strVal: tok.value, line: tok.line, col: tok.col)
|
||||
of tkBoolLit:
|
||||
discard p.advance()
|
||||
Node(kind: nkBoolLit, boolVal: tok.value == "true", line: tok.line, col: tok.col)
|
||||
of tkNull:
|
||||
discard p.advance()
|
||||
Node(kind: nkNullLit, line: tok.line, col: tok.col)
|
||||
of tkIdent:
|
||||
discard p.advance()
|
||||
Node(kind: nkIdent, identName: tok.value, line: tok.line, col: tok.col)
|
||||
of tkLParen:
|
||||
discard p.advance()
|
||||
let expr = p.parseExpr()
|
||||
discard p.expect(tkRParen)
|
||||
expr
|
||||
of tkLBracket:
|
||||
discard p.advance()
|
||||
var elems: seq[Node] = @[]
|
||||
if p.peek().kind != tkRBracket:
|
||||
elems.add(p.parseExpr())
|
||||
while p.match(tkComma):
|
||||
elems.add(p.parseExpr())
|
||||
discard p.expect(tkRBracket)
|
||||
Node(kind: nkArrayLit, arrayElems: elems, line: tok.line, col: tok.col)
|
||||
of tkSelect:
|
||||
p.parseSelect()
|
||||
of tkExists:
|
||||
discard p.advance()
|
||||
discard p.expect(tkLParen)
|
||||
let sub = p.parseSelect()
|
||||
discard p.expect(tkRParen)
|
||||
Node(kind: nkExists, existsExpr: sub, line: tok.line, col: tok.col)
|
||||
of tkNot:
|
||||
discard p.advance()
|
||||
let operand = p.parsePrimary()
|
||||
Node(kind: nkUnaryOp, unOp: ukNot, unOperand: operand, line: tok.line, col: tok.col)
|
||||
of tkMinus:
|
||||
discard p.advance()
|
||||
let operand = p.parsePrimary()
|
||||
Node(kind: nkUnaryOp, unOp: ukNeg, unOperand: operand, line: tok.line, col: tok.col)
|
||||
of tkCount:
|
||||
discard p.advance()
|
||||
discard p.expect(tkLParen)
|
||||
var args: seq[Node] = @[]
|
||||
if p.peek().kind != tkRParen:
|
||||
args.add(p.parseExpr())
|
||||
discard p.expect(tkRParen)
|
||||
Node(kind: nkFuncCall, funcName: "count", funcArgs: args, line: tok.line, col: tok.col)
|
||||
else:
|
||||
discard p.advance()
|
||||
Node(kind: nkNullLit, line: tok.line, col: tok.col)
|
||||
|
||||
proc parseMulDiv(p: var Parser): Node =
|
||||
result = p.parsePrimary()
|
||||
while p.peek().kind in {tkStar, tkSlash, tkPercent, tkFloorDiv}:
|
||||
let op = case p.peek().kind
|
||||
of tkStar: bkMul
|
||||
of tkSlash: bkDiv
|
||||
of tkPercent: bkMod
|
||||
of tkFloorDiv: bkFloorDiv
|
||||
else: bkMul
|
||||
let tok = p.advance()
|
||||
let right = p.parsePrimary()
|
||||
result = Node(kind: nkBinOp, binOp: op, binLeft: result, binRight: right,
|
||||
line: tok.line, col: tok.col)
|
||||
|
||||
proc parseAddSub(p: var Parser): Node =
|
||||
result = p.parseMulDiv()
|
||||
while p.peek().kind in {tkPlus, tkMinus, tkConcat}:
|
||||
let op = case p.peek().kind
|
||||
of tkPlus: bkAdd
|
||||
of tkMinus: bkSub
|
||||
of tkConcat: bkConcat
|
||||
else: bkAdd
|
||||
let tok = p.advance()
|
||||
let right = p.parseMulDiv()
|
||||
result = Node(kind: nkBinOp, binOp: op, binLeft: result, binRight: right,
|
||||
line: tok.line, col: tok.col)
|
||||
|
||||
proc parseComparison(p: var Parser): Node =
|
||||
result = p.parseAddSub()
|
||||
while p.peek().kind in {tkEq, tkNotEq, tkLt, tkLtEq, tkGt, tkGtEq}:
|
||||
let op = case p.peek().kind
|
||||
of tkEq: bkEq
|
||||
of tkNotEq: bkNotEq
|
||||
of tkLt: bkLt
|
||||
of tkLtEq: bkLtEq
|
||||
of tkGt: bkGt
|
||||
of tkGtEq: bkGtEq
|
||||
else: bkEq
|
||||
let tok = p.advance()
|
||||
let right = p.parseAddSub()
|
||||
result = Node(kind: nkBinOp, binOp: op, binLeft: result, binRight: right,
|
||||
line: tok.line, col: tok.col)
|
||||
|
||||
proc parseNot(p: var Parser): Node =
|
||||
if p.peek().kind == tkNot:
|
||||
let tok = p.advance()
|
||||
let operand = p.parseComparison()
|
||||
return Node(kind: nkUnaryOp, unOp: ukNot, unOperand: operand,
|
||||
line: tok.line, col: tok.col)
|
||||
return p.parseComparison()
|
||||
|
||||
proc parseAnd(p: var Parser): Node =
|
||||
result = p.parseNot()
|
||||
while p.peek().kind == tkAnd:
|
||||
let tok = p.advance()
|
||||
let right = p.parseNot()
|
||||
result = Node(kind: nkBinOp, binOp: bkAnd, binLeft: result, binRight: right,
|
||||
line: tok.line, col: tok.col)
|
||||
|
||||
proc parseOr(p: var Parser): Node =
|
||||
result = p.parseAnd()
|
||||
while p.peek().kind == tkOr:
|
||||
let tok = p.advance()
|
||||
let right = p.parseAnd()
|
||||
result = Node(kind: nkBinOp, binOp: bkOr, binLeft: result, binRight: right,
|
||||
line: tok.line, col: tok.col)
|
||||
|
||||
proc parseExpr(p: var Parser): Node =
|
||||
return p.parseOr()
|
||||
|
||||
proc parseSelect(p: var Parser): Node =
|
||||
let tok = p.expect(tkSelect)
|
||||
result = Node(kind: nkSelect, line: tok.line, col: tok.col)
|
||||
|
||||
if p.peek().kind == tkDistinct:
|
||||
discard p.advance()
|
||||
result.selDistinct = true
|
||||
|
||||
result.selResult = @[]
|
||||
result.selResult.add(p.parseExpr())
|
||||
while p.match(tkComma):
|
||||
result.selResult.add(p.parseExpr())
|
||||
|
||||
if p.match(tkFrom):
|
||||
let tableTok = p.expect(tkIdent)
|
||||
var alias = ""
|
||||
if p.match(tkAs):
|
||||
alias = p.expect(tkIdent).value
|
||||
elif p.peek().kind == tkIdent:
|
||||
alias = p.advance().value
|
||||
result.selFrom = Node(kind: nkFrom, fromTable: tableTok.value,
|
||||
fromAlias: alias, line: tableTok.line, col: tableTok.col)
|
||||
|
||||
if p.match(tkWhere):
|
||||
result.selWhere = Node(kind: nkWhere, whereExpr: p.parseExpr())
|
||||
|
||||
if p.match(tkLimit):
|
||||
result.selLimit = Node(kind: nkLimit, limitExpr: p.parseExpr())
|
||||
|
||||
if p.match(tkOffset):
|
||||
result.selOffset = Node(kind: nkOffset, offsetExpr: p.parseExpr())
|
||||
|
||||
proc parseInsert(p: var Parser): Node =
|
||||
let tok = p.expect(tkInsert)
|
||||
let target = p.expect(tkIdent).value
|
||||
result = Node(kind: nkInsert, insTarget: target, line: tok.line, col: tok.col)
|
||||
|
||||
proc parseUpdate(p: var Parser): Node =
|
||||
let tok = p.expect(tkUpdate)
|
||||
let target = p.expect(tkIdent).value
|
||||
result = Node(kind: nkUpdate, updTarget: target, line: tok.line, col: tok.col)
|
||||
|
||||
proc parseDelete(p: var Parser): Node =
|
||||
let tok = p.expect(tkDelete)
|
||||
let target = p.expect(tkIdent).value
|
||||
result = Node(kind: nkDelete, delTarget: target, line: tok.line, col: tok.col)
|
||||
|
||||
proc parseCreateType(p: var Parser): Node =
|
||||
let tok = p.expect(tkCreate)
|
||||
discard p.expect(tkType)
|
||||
let name = p.expect(tkIdent).value
|
||||
result = Node(kind: nkCreateType, ctName: name, line: tok.line, col: tok.col)
|
||||
|
||||
proc parseStatement*(p: var Parser): Node =
|
||||
case p.peek().kind
|
||||
of tkSelect: p.parseSelect()
|
||||
of tkInsert: p.parseInsert()
|
||||
of tkUpdate: p.parseUpdate()
|
||||
of tkDelete: p.parseDelete()
|
||||
of tkCreate: p.parseCreateType()
|
||||
else:
|
||||
let tok = p.advance()
|
||||
Node(kind: nkNullLit, line: tok.line, col: tok.col)
|
||||
|
||||
proc parse*(tokens: seq[Token]): Node =
|
||||
var parser = newParser(tokens)
|
||||
result = Node(kind: nkStatementList)
|
||||
while parser.peek().kind != tkEof:
|
||||
result.stmts.add(parser.parseStatement())
|
||||
discard parser.match(tkSemicolon)
|
||||
|
||||
proc parse*(input: string): Node =
|
||||
let tokens = tokenize(input)
|
||||
parse(tokens)
|
||||
Reference in New Issue
Block a user