Merge pull request #54 from zah/pretty-print-ast

String interpolation implemented in terms of macros
This commit is contained in:
Araq 2011-09-20 12:13:24 -07:00
commit 57f184cf48
13 changed files with 492 additions and 53 deletions

View file

@ -57,10 +57,10 @@ type
nkStrLit, # a string literal ""
nkRStrLit, # a raw string literal r""
nkTripleStrLit, # a triple string literal """
nkMetaNode, # difficult to explain; represents itself
# (used for macros)
nkNilLit, # the nil literal
# end of atoms
nkMetaNode, # difficult to explain; represents itself
# (used for macros)
nkDotCall, # used to temporarily flag a nkCall node;
# this is used
# for transforming ``s.len`` to ``len(s)``
@ -313,6 +313,7 @@ type
TMagic* = enum # symbols that require compiler magic:
mNone, mDefined, mDefinedInScope, mLow, mHigh, mSizeOf, mIs, mOf,
mEcho, mShallowCopy, mSlurp,
mAstToYaml, mParseExprToAst, mParseStmtToAst, mExpandMacroToAst,
mUnaryLt, mSucc,
mPred, mInc, mDec, mOrd, mNew, mNewFinalize, mNewSeq, mLengthOpenArray,
mLengthStr, mLengthArray, mLengthSeq, mIncl, mExcl, mCard, mChr, mGCref,
@ -610,6 +611,23 @@ proc copyTree*(src: PNode): PNode
proc discardSons*(father: PNode)
proc len*(n: PNode): int {.inline.} =
if isNil(n.sons): result = 0
else: result = len(n.sons)
proc safeLen*(n: PNode): int {.inline.} =
## works even for leaves.
if n.kind in {nkNone..nkNilLit} or isNil(n.sons): result = 0
else: result = len(n.sons)
proc add*(father, son: PNode) =
assert son != nil
if isNil(father.sons): father.sons = @[]
add(father.sons, son)
proc `[]`*(n: PNode, i: int): PNode {.inline.} =
result = n.sons[i]
var emptyNode* = newNode(nkEmpty)
# There is a single empty node that is shared! Do not overwrite it!
@ -754,6 +772,10 @@ proc newNodeIT(kind: TNodeKind, info: TLineInfo, typ: PType): PNode =
result.info = info
result.typ = typ
proc newMetaNodeIT*(tree: PNode, info: TLineInfo, typ: PType): PNode =
result = newNodeIT(nkMetaNode, info, typ)
result.add(tree)
proc NewType(kind: TTypeKind, owner: PSym): PType =
new(result)
result.kind = kind
@ -866,23 +888,6 @@ proc sonsLen(n: PNode): int =
if isNil(n.sons): result = 0
else: result = len(n.sons)
proc len*(n: PNode): int {.inline.} =
if isNil(n.sons): result = 0
else: result = len(n.sons)
proc safeLen*(n: PNode): int {.inline.} =
## works even for leaves.
if n.kind in {nkNone..nkNilLit} or isNil(n.sons): result = 0
else: result = len(n.sons)
proc add*(father, son: PNode) =
assert son != nil
if isNil(father.sons): father.sons = @[]
add(father.sons, son)
proc `[]`*(n: PNode, i: int): PNode {.inline.} =
result = n.sons[i]
proc newSons(father: PNode, length: int) =
if isNil(father.sons): father.sons = @[]
setlen(father.sons, len(father.sons) + length)

View file

@ -15,7 +15,8 @@
import
strutils, magicsys, lists, options, ast, astalgo, trees, treetab, nimsets,
msgs, os, condsyms, idents, renderer, types, passes, semfold, transf
msgs, os, condsyms, idents, renderer, types, passes, semfold, transf,
parser, ropes
type
PStackFrame* = ref TStackFrame
@ -751,6 +752,31 @@ proc evalRepr(c: PEvalContext, n: PNode): PNode =
proc isEmpty(n: PNode): bool =
result = (n != nil) and (n.kind == nkEmpty)
# The lexer marks multi-line strings as residing at the line where they are closed
# This function returns the line where the string begins
# Maybe the lexer should mark both the beginning and the end of expressions, then
# this function could be removed
proc stringStartingLine(s: PNode): int =
var totalLines = 0
for ln in splitLines(s.strVal): inc totalLines
result = s.info.line - totalLines
proc evalParseExpr(c: PEvalContext, n: Pnode): Pnode =
var code = evalAux(c, n.sons[1], {})
var ast = parseString(code.getStrValue, code.info.toFilename, code.stringStartingLine)
if sonsLen(ast) != 1:
GlobalError(code.info, errExprExpected, "multiple statements")
result = ast.sons[0]
result.typ = newType(tyExpr, c.module)
proc evalParseStmt(c: PEvalContext, n: Pnode): Pnode =
var code = evalAux(c, n.sons[1], {})
result = parseString(code.getStrValue, code.info.toFilename, code.stringStartingLine)
result.typ = newType(tyStmt, c.module)
proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode =
var m = getMagic(n)
case m
@ -775,6 +801,8 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode =
of mAppendStrCh: result = evalAppendStrCh(c, n)
of mAppendStrStr: result = evalAppendStrStr(c, n)
of mAppendSeqElem: result = evalAppendSeqElem(c, n)
of mParseExprToAst: result = evalParseExpr(c, n)
of mParseStmtToAst: result = evalParseStmt(c, n)
of mNLen:
result = evalAux(c, n.sons[1], {efLValue})
if isSpecial(result): return
@ -981,6 +1009,9 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode =
if (a == b) or
(b.kind in {nkNilLit, nkEmpty}) and (a.kind in {nkNilLit, nkEmpty}):
result.intVal = 1
of mAstToYaml:
var ast = evalAux(c, n.sons[1], {efLValue})
result = newStrNode(nkStrLit, ast.treeToYaml.ropeToStr)
of mNHint:
result = evalAux(c, n.sons[1], {})
if isSpecial(result): return
@ -1034,6 +1065,9 @@ proc evalAux(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode =
dec(gNestedEvals)
if gNestedEvals <= 0: stackTrace(c, n, errTooManyIterations)
case n.kind # atoms:
of nkMetaNode:
result = copyTree(n.sons[0])
result.typ = n.typ
of nkEmpty: result = n
of nkSym: result = evalSym(c, n, flags)
of nkType..nkNilLit: result = copyNode(n) # end of atoms

View file

@ -30,6 +30,11 @@ proc closeParser*(p: var TParser)
proc parseTopLevelStmt*(p: var TParser): PNode
# implements an iterator. Returns the next top-level statement or
# emtyNode if end of stream.
proc parseString*(s: string, filename: string = "", line: int = 0): PNode
# filename and line could be set optionally, when the string originates
# from a certain source file. This way, the compiler could generate
# correct error messages referring to the original source.
# helpers for the other parsers
proc getPrecedence*(tok: TToken): int
@ -1369,3 +1374,14 @@ proc parseTopLevelStmt(p: var TParser): PNode =
result = complexOrSimpleStmt(p)
if result.kind == nkEmpty: parMessage(p, errExprExpected, p.tok)
break
proc parseString(s: string, filename: string = "", line: int = 0): PNode =
var stream = LLStreamOpen(s)
stream.lineOffset = line
var parser : TParser
OpenParser(parser, filename, stream)
result = parser.parseAll
CloseParser(parser)

View file

@ -8,6 +8,7 @@
#
# this module does the semantic checking for expressions
const
ConstAbstractTypes = {tyNil, tyChar, tyInt..tyInt64, tyFloat..tyFloat128,
tyArrayConstr, tyTuple, tySet}
@ -384,6 +385,10 @@ proc isAssignable(c: PContext, n: PNode): TAssignableResult =
else:
nil
proc isCallExpr(n: PNode): bool =
result = n.kind in {nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand,
nkCallStrLit}
proc newHiddenAddrTaken(c: PContext, n: PNode): PNode =
if n.kind == nkHiddenDeref:
checkSonsLen(n, 1)
@ -742,7 +747,7 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
checkSonsLen(n, 2)
n.sons[0] = makeDeref(n.sons[0])
# [] operator for tuples requires constant expression:
n.sons[1] = semConstExpr(c, n.sons[1])
n.sons[1] = semAndEvalConstExpr(c, n.sons[1])
if skipTypes(n.sons[1].typ, {tyGenericInst, tyRange, tyOrdinal}).kind in
{tyInt..tyInt64}:
var idx = getOrdValue(n.sons[1])
@ -883,11 +888,38 @@ proc setMs(n: PNode, s: PSym): PNode =
n.sons[0] = newSymNode(s)
n.sons[0].info = n.info
proc expectStringArg(c: PContext, n: PNode, i: int): PNode =
result = c.semAndEvalConstExpr(n.sons[i+1])
if result.kind notin {nkStrLit, nkRStrLit, nkTripleStrLit}:
GlobalError(result.info, errStringLiteralExpected)
proc semExpandMacroToAst(c: PContext, n: PNode, flags: TExprFlags): PNode =
if sonsLen(n) == 2:
if not isCallExpr(n.sons[1]):
GlobalError(n.info, errXisNoMacroOrTemplate, n.renderTree)
var macroCall = n.sons[1]
var s = qualifiedLookup(c, macroCall.sons[0], {checkUndeclared})
if s == nil:
GlobalError(n.info, errUndeclaredIdentifier, macroCall.sons[0].renderTree)
var expanded : Pnode
case s.kind
of skMacro: expanded = semMacroExpr(c, macroCall, s, false)
of skTemplate: expanded = semTemplateExpr(c, macroCall, s, false)
else: GlobalError(n.info, errXisNoMacroOrTemplate, s.name.s)
var macroRetType = newTypeS(s.typ.sons[0].kind, c)
result = newMetaNodeIT(expanded, n.info, macroRetType)
else:
result = semDirectOp(c, n, flags)
proc semSlurp(c: PContext, n: PNode, flags: TExprFlags): PNode =
if sonsLen(n) == 2:
var a = c.semConstExpr(c, n.sons[1])
if a.kind notin {nkStrLit, nkRStrLit, nkTripleStrLit}:
GlobalError(a.info, errStringLiteralExpected)
var a = expectStringArg(c, n, 0)
try:
var content = readFile(a.strVal)
result = newStrNode(nkStrLit, content)
@ -921,6 +953,7 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
else:
result = semDirectOp(c, n, flags)
of mSlurp: result = semSlurp(c, n, flags)
of mExpandMacroToAst: result = semExpandMacroToAst(c, n, flags)
else: result = semDirectOp(c, n, flags)
proc semIfExpr(c: PContext, n: PNode): PNode =
@ -1078,10 +1111,6 @@ proc semBlockExpr(c: PContext, n: PNode): PNode =
closeScope(c.tab)
Dec(c.p.nestedBlockCounter)
proc isCallExpr(n: PNode): bool =
result = n.kind in {nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand,
nkCallStrLit}
proc semMacroStmt(c: PContext, n: PNode, semCheck = true): PNode =
checkMinSonsLen(n, 2)
var a: PNode

View file

@ -208,6 +208,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode =
of mNewString, mNewStringOfCap,
mExit, mInc, ast.mDec, mEcho, mAssert, mSwap, mAppendStrCh,
mAppendStrStr, mAppendSeqElem, mSetLengthStr, mSetLengthSeq,
mParseExprToAst, mParseStmtToAst,
mNLen..mNError, mEqRef:
nil
else: InternalError(a.info, "evalOp(" & $m & ')')

View file

@ -19,7 +19,7 @@ proc semWhen(c: PContext, n: PNode): PNode =
case it.kind
of nkElifBranch:
checkSonsLen(it, 2)
var e = semConstBoolExpr(c, it.sons[0])
var e = semAndEvalConstExpr(c, it.sons[0])
if (e.kind != nkIntLit): InternalError(n.info, "semWhen")
if (e.intVal != 0) and (result == nil):
result = semStmt(c, it.sons[1]) # do not open a new scope!