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:
2026-05-06 00:22:12 +03:00
commit 6935889877
18 changed files with 2676 additions and 0 deletions
+318
View File
@@ -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
+461
View File
@@ -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
+245
View File
@@ -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)