diff options
Diffstat (limited to 'lib/pure/parsesql.nim')
-rw-r--r-- | lib/pure/parsesql.nim | 218 |
1 files changed, 109 insertions, 109 deletions
diff --git a/lib/pure/parsesql.nim b/lib/pure/parsesql.nim index cfa60094d..d6fafec08 100644 --- a/lib/pure/parsesql.nim +++ b/lib/pure/parsesql.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -## The ``parsesql`` module implements a high performance SQL file +## The ``parsesql`` module implements a high performance SQL file ## parser. It parses PostgreSQL syntax and the SQL ANSI standard. import @@ -37,11 +37,11 @@ type tkBracketLe, ## '[' tkBracketRi, ## ']' tkDot ## '.' - + Token = object # a token kind: TokKind # the type of the token literal: string # the parsed (string) literal - + SqlLexer* = object of BaseLexer ## the parser object. filename: string @@ -55,82 +55,82 @@ const ";", ":", ",", "(", ")", "[", "]", "." ] -proc open(L: var SqlLexer, input: Stream, filename: string) = +proc open(L: var SqlLexer, input: Stream, filename: string) = lexbase.open(L, input) L.filename = filename - -proc close(L: var SqlLexer) = + +proc close(L: var SqlLexer) = lexbase.close(L) -proc getColumn(L: SqlLexer): int = +proc getColumn(L: SqlLexer): int = ## get the current column the parser has arrived at. result = getColNumber(L, L.bufpos) -proc getLine(L: SqlLexer): int = +proc getLine(L: SqlLexer): int = result = L.lineNumber -proc handleHexChar(c: var SqlLexer, xi: var int) = +proc handleHexChar(c: var SqlLexer, xi: var int) = case c.buf[c.bufpos] - of '0'..'9': + of '0'..'9': xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('0')) inc(c.bufpos) - of 'a'..'f': + of 'a'..'f': xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('a') + 10) inc(c.bufpos) - of 'A'..'F': + of 'A'..'F': xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('A') + 10) inc(c.bufpos) - else: + else: discard -proc handleOctChar(c: var SqlLexer, xi: var int) = +proc handleOctChar(c: var SqlLexer, xi: var int) = if c.buf[c.bufpos] in {'0'..'7'}: xi = (xi shl 3) or (ord(c.buf[c.bufpos]) - ord('0')) inc(c.bufpos) -proc getEscapedChar(c: var SqlLexer, tok: var Token) = +proc getEscapedChar(c: var SqlLexer, tok: var Token) = inc(c.bufpos) case c.buf[c.bufpos] - of 'n', 'N': + of 'n', 'N': add(tok.literal, '\L') inc(c.bufpos) - of 'r', 'R', 'c', 'C': + of 'r', 'R', 'c', 'C': add(tok.literal, '\c') inc(c.bufpos) - of 'l', 'L': + of 'l', 'L': add(tok.literal, '\L') inc(c.bufpos) - of 'f', 'F': + of 'f', 'F': add(tok.literal, '\f') inc(c.bufpos) - of 'e', 'E': + of 'e', 'E': add(tok.literal, '\e') inc(c.bufpos) - of 'a', 'A': + of 'a', 'A': add(tok.literal, '\a') inc(c.bufpos) - of 'b', 'B': + of 'b', 'B': add(tok.literal, '\b') inc(c.bufpos) - of 'v', 'V': + of 'v', 'V': add(tok.literal, '\v') inc(c.bufpos) - of 't', 'T': + of 't', 'T': add(tok.literal, '\t') inc(c.bufpos) - of '\'', '\"': + of '\'', '\"': add(tok.literal, c.buf[c.bufpos]) inc(c.bufpos) - of '\\': + of '\\': add(tok.literal, '\\') inc(c.bufpos) - of 'x', 'X': + of 'x', 'X': inc(c.bufpos) var xi = 0 handleHexChar(c, xi) handleHexChar(c, xi) add(tok.literal, chr(xi)) - of '0'..'7': + of '0'..'7': var xi = 0 handleOctChar(c, xi) handleOctChar(c, xi) @@ -138,20 +138,20 @@ proc getEscapedChar(c: var SqlLexer, tok: var Token) = if (xi <= 255): add(tok.literal, chr(xi)) else: tok.kind = tkInvalid else: tok.kind = tkInvalid - -proc handleCRLF(c: var SqlLexer, pos: int): int = + +proc handleCRLF(c: var SqlLexer, pos: int): int = case c.buf[pos] of '\c': result = lexbase.handleCR(c, pos) of '\L': result = lexbase.handleLF(c, pos) else: result = pos -proc skip(c: var SqlLexer) = +proc skip(c: var SqlLexer) = var pos = c.bufpos var buf = c.buf var nested = 0 - while true: + while true: case buf[pos] - of ' ', '\t': + of ' ', '\t': inc(pos) of '-': if buf[pos+1] == '-': @@ -164,7 +164,7 @@ proc skip(c: var SqlLexer) = while true: case buf[pos] of '\0': break - of '\c', '\L': + of '\c', '\L': pos = handleCRLF(c, pos) buf = c.buf of '*': @@ -182,20 +182,20 @@ proc skip(c: var SqlLexer) = inc(pos) else: inc(pos) else: break - of '\c', '\L': + of '\c', '\L': pos = handleCRLF(c, pos) buf = c.buf - else: + else: break # EndOfFile also leaves the loop c.bufpos = pos - -proc getString(c: var SqlLexer, tok: var Token, kind: TokKind) = + +proc getString(c: var SqlLexer, tok: var Token, kind: TokKind) = var pos = c.bufpos + 1 var buf = c.buf tok.kind = kind block parseLoop: while true: - while true: + while true: var ch = buf[pos] if ch == '\'': if buf[pos+1] == '\'': @@ -203,15 +203,15 @@ proc getString(c: var SqlLexer, tok: var Token, kind: TokKind) = add(tok.literal, '\'') else: inc(pos) - break - elif ch in {'\c', '\L', lexbase.EndOfFile}: + break + elif ch in {'\c', '\L', lexbase.EndOfFile}: tok.kind = tkInvalid break parseLoop - elif (ch == '\\') and kind == tkEscapeConstant: + elif (ch == '\\') and kind == tkEscapeConstant: c.bufpos = pos getEscapedChar(c, tok) pos = c.bufpos - else: + else: add(tok.literal, ch) inc(pos) c.bufpos = pos @@ -227,7 +227,7 @@ proc getString(c: var SqlLexer, tok: var Token, kind: TokKind) = else: break parseLoop c.bufpos = pos -proc getDollarString(c: var SqlLexer, tok: var Token) = +proc getDollarString(c: var SqlLexer, tok: var Token) = var pos = c.bufpos + 1 var buf = c.buf tok.kind = tkDollarQuotedConstant @@ -241,7 +241,7 @@ proc getDollarString(c: var SqlLexer, tok: var Token) = return while true: case buf[pos] - of '\c', '\L': + of '\c', '\L': pos = handleCRLF(c, pos) buf = c.buf add(tok.literal, "\L") @@ -263,10 +263,10 @@ proc getDollarString(c: var SqlLexer, tok: var Token) = inc(pos) c.bufpos = pos -proc getSymbol(c: var SqlLexer, tok: var Token) = +proc getSymbol(c: var SqlLexer, tok: var Token) = var pos = c.bufpos var buf = c.buf - while true: + while true: add(tok.literal, buf[pos]) inc(pos) if buf[pos] notin {'a'..'z','A'..'Z','0'..'9','_','$', '\128'..'\255'}: @@ -274,7 +274,7 @@ proc getSymbol(c: var SqlLexer, tok: var Token) = c.bufpos = pos tok.kind = tkIdentifier -proc getQuotedIdentifier(c: var SqlLexer, tok: var Token) = +proc getQuotedIdentifier(c: var SqlLexer, tok: var Token) = var pos = c.bufpos + 1 var buf = c.buf tok.kind = tkQuotedIdentifier @@ -287,7 +287,7 @@ proc getQuotedIdentifier(c: var SqlLexer, tok: var Token) = else: inc(pos) break - elif ch in {'\c', '\L', lexbase.EndOfFile}: + elif ch in {'\c', '\L', lexbase.EndOfFile}: tok.kind = tkInvalid break else: @@ -300,15 +300,15 @@ proc getBitHexString(c: var SqlLexer, tok: var Token, validChars: set[char]) = var buf = c.buf block parseLoop: while true: - while true: + while true: var ch = buf[pos] if ch in validChars: add(tok.literal, ch) - inc(pos) + inc(pos) elif ch == '\'': inc(pos) break - else: + else: tok.kind = tkInvalid break parseLoop c.bufpos = pos @@ -353,7 +353,7 @@ proc getNumeric(c: var SqlLexer, tok: var Token) = inc(pos) else: tok.kind = tkInvalid - c.bufpos = pos + c.bufpos = pos proc getOperator(c: var SqlLexer, tok: var Token) = const operators = {'+', '-', '*', '/', '<', '>', '=', '~', '!', '@', '#', '%', @@ -381,12 +381,12 @@ proc getOperator(c: var SqlLexer, tok: var Token) = inc(pos) c.bufpos = pos -proc getTok(c: var SqlLexer, tok: var Token) = +proc getTok(c: var SqlLexer, tok: var Token) = tok.kind = tkInvalid setLen(tok.literal, 0) skip(c) case c.buf[c.bufpos] - of ';': + of ';': tok.kind = tkSemicolon inc(c.bufpos) add(tok.literal, ';') @@ -394,15 +394,15 @@ proc getTok(c: var SqlLexer, tok: var Token) = tok.kind = tkComma inc(c.bufpos) add(tok.literal, ',') - of ':': + of ':': tok.kind = tkColon inc(c.bufpos) add(tok.literal, ':') - of 'e', 'E': - if c.buf[c.bufpos + 1] == '\'': + of 'e', 'E': + if c.buf[c.bufpos + 1] == '\'': inc(c.bufpos) getString(c, tok, tkEscapeConstant) - else: + else: getSymbol(c, tok) of 'b', 'B': if c.buf[c.bufpos + 1] == '\'': @@ -417,11 +417,11 @@ proc getTok(c: var SqlLexer, tok: var Token) = else: getSymbol(c, tok) of '$': getDollarString(c, tok) - of '[': + of '[': tok.kind = tkBracketLe inc(c.bufpos) add(tok.literal, '[') - of ']': + of ']': tok.kind = tkBracketRi inc(c.bufpos) add(tok.literal, ']') @@ -433,7 +433,7 @@ proc getTok(c: var SqlLexer, tok: var Token) = tok.kind = tkParRi inc(c.bufpos) add(tok.literal, ')') - of '.': + of '.': if c.buf[c.bufpos + 1] in Digits: getNumeric(c, tok) else: @@ -443,7 +443,7 @@ proc getTok(c: var SqlLexer, tok: var Token) = of '0'..'9': getNumeric(c, tok) of '\'': getString(c, tok, tkStringConstant) of '"': getQuotedIdentifier(c, tok) - of lexbase.EndOfFile: + of lexbase.EndOfFile: tok.kind = tkEof tok.literal = "[EOF]" of 'a', 'c', 'd', 'f'..'w', 'y', 'z', 'A', 'C', 'D', 'F'..'W', 'Y', 'Z', '_', @@ -455,8 +455,8 @@ proc getTok(c: var SqlLexer, tok: var Token) = else: add(tok.literal, c.buf[c.bufpos]) inc(c.bufpos) - -proc errorStr(L: SqlLexer, msg: string): string = + +proc errorStr(L: SqlLexer, msg: string): string = result = "$1($2, $3) Error: $4" % [L.filename, $getLine(L), $getColumn(L), msg] @@ -496,7 +496,7 @@ type nkPrimaryKey, nkForeignKey, nkNotNull, - + nkStmtList, nkDot, nkDotDot, @@ -530,13 +530,13 @@ type nkValueList, nkWhere, nkCreateTable, - nkCreateTableIfNotExists, + nkCreateTableIfNotExists, nkCreateType, nkCreateTypeIfNotExists, nkCreateIndex, nkCreateIndexIfNotExists, nkEnumDef - + type SqlParseError* = object of ValueError ## Invalid SQL encountered SqlNode* = ref SqlNodeObj ## an SQL abstract syntax tree node @@ -563,11 +563,11 @@ proc newNode(k: SqlNodeKind, s: string): SqlNode = new(result) result.kind = k result.strVal = s - + proc len*(n: SqlNode): int = if isNil(n.sons): result = 0 else: result = n.sons.len - + proc add*(father, n: SqlNode) = if isNil(father.sons): father.sons = @[] add(father.sons, n) @@ -641,11 +641,11 @@ proc parseDataType(p: var SqlParser): SqlNode = getTok(p) eat(p, tkParRi) -proc getPrecedence(p: SqlParser): int = +proc getPrecedence(p: SqlParser): int = if isOpr(p, "*") or isOpr(p, "/") or isOpr(p, "%"): result = 6 elif isOpr(p, "+") or isOpr(p, "-"): - result = 5 + result = 5 elif isOpr(p, "=") or isOpr(p, "<") or isOpr(p, ">") or isOpr(p, ">=") or isOpr(p, "<=") or isOpr(p, "<>") or isOpr(p, "!=") or isKeyw(p, "is") or isKeyw(p, "like"): @@ -664,7 +664,7 @@ proc parseExpr(p: var SqlParser): SqlNode proc identOrLiteral(p: var SqlParser): SqlNode = case p.tok.kind - of tkIdentifier, tkQuotedIdentifier: + of tkIdentifier, tkQuotedIdentifier: result = newNode(nkIdent, p.tok.literal) getTok(p) of tkStringConstant, tkEscapeConstant, tkDollarQuotedConstant: @@ -686,21 +686,21 @@ proc identOrLiteral(p: var SqlParser): SqlNode = getTok(p) result = parseExpr(p) eat(p, tkParRi) - else: + else: sqlError(p, "expression expected") getTok(p) # we must consume a token here to prevend endless loops! -proc primary(p: var SqlParser): SqlNode = - if p.tok.kind == tkOperator or isKeyw(p, "not"): +proc primary(p: var SqlParser): SqlNode = + if p.tok.kind == tkOperator or isKeyw(p, "not"): result = newNode(nkPrefix) result.add(newNode(nkIdent, p.tok.literal)) getTok(p) result.add(primary(p)) return result = identOrLiteral(p) - while true: + while true: case p.tok.kind - of tkParLe: + of tkParLe: var a = result result = newNode(nkCall) result.add(a) @@ -710,7 +710,7 @@ proc primary(p: var SqlParser): SqlNode = if p.tok.kind == tkComma: getTok(p) else: break eat(p, tkParRi) - of tkDot: + of tkDot: getTok(p) var a = result if p.tok.kind == tkDot: @@ -727,14 +727,14 @@ proc primary(p: var SqlParser): SqlNode = sqlError(p, "identifier expected") getTok(p) else: break - -proc lowestExprAux(p: var SqlParser, v: var SqlNode, limit: int): int = + +proc lowestExprAux(p: var SqlParser, v: var SqlNode, limit: int): int = var v2, node, opNode: SqlNode v = primary(p) # expand while operators have priorities higher than 'limit' var opPred = getPrecedence(p) result = opPred - while opPred > limit: + while opPred > limit: node = newNode(nkInfix) opNode = newNode(nkIdent, p.tok.literal) getTok(p) @@ -744,8 +744,8 @@ proc lowestExprAux(p: var SqlParser, v: var SqlNode, limit: int): int = node.add(v2) v = node opPred = getPrecedence(p) - -proc parseExpr(p: var SqlParser): SqlNode = + +proc parseExpr(p: var SqlParser): SqlNode = discard lowestExprAux(p, result, - 1) proc parseTableName(p: var SqlParser): SqlNode = @@ -765,7 +765,7 @@ proc parseColumnReference(p: var SqlParser): SqlNode = result.add(parseTableName(p)) eat(p, tkParRi) -proc parseCheck(p: var SqlParser): SqlNode = +proc parseCheck(p: var SqlParser): SqlNode = getTok(p) result = newNode(nkCheck) result.add(parseExpr(p)) @@ -817,9 +817,9 @@ proc parseColumnDef(p: var SqlParser): SqlNode = result.add(newNode(nkIdent, p.tok.literal)) getTok(p) result.add(parseDataType(p)) - parseColumnConstraints(p, result) + parseColumnConstraints(p, result) -proc parseIfNotExists(p: var SqlParser, k: SqlNodeKind): SqlNode = +proc parseIfNotExists(p: var SqlParser, k: SqlNodeKind): SqlNode = getTok(p) if isKeyw(p, "if"): getTok(p) @@ -880,7 +880,7 @@ proc parseTableDef(p: var SqlParser): SqlNode = result.add(parseTableConstraint(p)) if p.tok.kind != tkComma: break eat(p, tkParRi) - + proc parseTypeDef(p: var SqlParser): SqlNode = result = parseIfNotExists(p, nkCreateType) expectIdent(p) @@ -966,7 +966,7 @@ proc parseUpdate(p: var SqlParser): SqlNode = result.add(parseWhere(p)) else: result.add(nil) - + proc parseDelete(p: var SqlParser): SqlNode = getTok(p) result = newNode(nkDelete) @@ -1018,14 +1018,14 @@ proc parseSelect(p: var SqlParser): SqlNode = while true: getTok(p) h.add(parseExpr(p)) - if p.tok.kind != tkComma: break + if p.tok.kind != tkComma: break result.add(h) if isKeyw(p, "union"): result.add(newNode(nkUnion)) getTok(p) elif isKeyw(p, "intersect"): result.add(newNode(nkIntersect)) - getTok(p) + getTok(p) elif isKeyw(p, "except"): result.add(newNode(nkExcept)) getTok(p) @@ -1083,7 +1083,7 @@ proc open(p: var SqlParser, input: Stream, filename: string) = p.tok.kind = tkInvalid p.tok.literal = "" getTok(p) - + proc parse(p: var SqlParser): SqlNode = ## parses the content of `p`'s input stream and returns the SQL AST. ## Syntax errors raise an `EInvalidSql` exception. @@ -1094,13 +1094,13 @@ proc parse(p: var SqlParser): SqlNode = result.add(s) if result.len == 1: result = result.sons[0] - + proc close(p: var SqlParser) = ## closes the parser `p`. The associated input stream is closed too. close(SqlLexer(p)) proc parseSQL*(input: Stream, filename: string): SqlNode = - ## parses the SQL from `input` into an AST and returns the AST. + ## parses the SQL from `input` into an AST and returns the AST. ## `filename` is only used for error messages. ## Syntax errors raise an `EInvalidSql` exception. var p: SqlParser @@ -1114,7 +1114,7 @@ proc ra(n: SqlNode, s: var string, indent: int) proc rs(n: SqlNode, s: var string, indent: int, prefix = "(", suffix = ")", - sep = ", ") = + sep = ", ") = if n.len > 0: s.add(prefix) for i in 0 .. n.len-1: @@ -1162,7 +1162,7 @@ proc ra(n: SqlNode, s: var string, indent: int) = ra(n.sons[1], s, indent) s.add(')') of nkInfix: - s.add('(') + s.add('(') ra(n.sons[1], s, indent) s.add(' ') ra(n.sons[0], s, indent) @@ -1207,13 +1207,13 @@ proc ra(n: SqlNode, s: var string, indent: int) = s.add("insert into ") ra(n.sons[0], s, indent) ra(n.sons[1], s, indent) - if n.sons[2].kind == nkDefault: + if n.sons[2].kind == nkDefault: s.add("default values") else: s.add("\nvalues ") ra(n.sons[2], s, indent) s.add(';') - of nkUpdate: + of nkUpdate: s.add("update ") ra(n.sons[0], s, indent) s.add(" set ") @@ -1225,7 +1225,7 @@ proc ra(n: SqlNode, s: var string, indent: int) = ra(it, s, indent) ra(n.sons[L-1], s, indent) s.add(';') - of nkDelete: + of nkDelete: s.add("delete from ") ra(n.sons[0], s, indent) ra(n.sons[1], s, indent) @@ -1237,12 +1237,12 @@ proc ra(n: SqlNode, s: var string, indent: int) = rs(n.sons[0], s, indent, "", "", ", ") for i in 1 .. n.len-1: ra(n.sons[i], s, indent) s.add(';') - of nkSelectColumns: + of nkSelectColumns: assert(false) of nkAsgn: ra(n.sons[0], s, indent) s.add(" = ") - ra(n.sons[1], s, indent) + ra(n.sons[1], s, indent) of nkFrom: s.add("\nfrom ") rs(n, s, indent, "", "", ", ") @@ -1306,23 +1306,23 @@ proc ra(n: SqlNode, s: var string, indent: int) = s.add("enum ") rs(n, s, indent) -# What I want: +# What I want: # -#select(columns = [T1.all, T2.name], +#select(columns = [T1.all, T2.name], # fromm = [T1, T2], # where = T1.name ==. T2.name, # orderby = [name]): -# -#for row in dbQuery(db, """select x, y, z -# from a, b +# +#for row in dbQuery(db, """select x, y, z +# from a, b # where a.name = b.name"""): -# +# #select x, y, z: # fromm: Table1, Table2 # where: x.name == y.name #db.select(fromm = [t1, t2], where = t1.name == t2.name): -#for x, y, z in db.select(fromm = a, b where = a.name == b.name): +#for x, y, z in db.select(fromm = a, b where = a.name == b.name): # writeLine x, y, z proc renderSQL*(n: SqlNode): string = @@ -1338,7 +1338,7 @@ when not defined(testing) and isMainModule: happiness happiness ); CREATE INDEX table1_attr1 ON table1(attr1); - + SELECT * FROM myTab WHERE col1 = 'happy'; """), "stdin"))) |