Added system.staticExec proc for executing external command at compile-time

This commit is contained in:
Zahary Karadjov
2012-06-02 19:51:49 +03:00
parent 337e1ecc7c
commit a1da1f987b
13 changed files with 83 additions and 39 deletions

View File

@@ -370,7 +370,7 @@ type
TMagic* = enum # symbols that require compiler magic:
mNone,
mDefined, mDefinedInScope, mLow, mHigh, mSizeOf, mTypeTrait, mIs, mOf,
mEcho, mShallowCopy, mSlurp,
mEcho, mShallowCopy, mSlurp, mStaticExec,
mParseExprToAst, mParseStmtToAst, mExpandToAst,
mUnaryLt, mSucc,
mPred, mInc, mDec, mOrd, mNew, mNewFinalize, mNewSeq, mLengthOpenArray,
@@ -541,6 +541,9 @@ type
# for easy generation of proper error messages
# for variant record fields the discriminant
# expression
# for modules, it's a placeholder for compiler
# generated code that will be appended to the
# module after the sem pass (see appendToModule)
options*: TOptions
position*: int # used for many different things:
# for enum fields its position;
@@ -742,6 +745,16 @@ proc linkTo*(s: PSym, t: PType): PSym {.discardable.} =
s.typ = t
result = s
proc appendToModule*(m: PSym, n: PNode) =
## The compiler will use this internally to add nodes that will be
## appended to the module after the sem pass
if m.ast == nil:
m.ast = newNode(nkStmtList)
m.ast.sons = @[n]
else:
assert m.ast.kind == nkStmtList
m.ast.sons.add(n)
const # for all kind of hash tables:
GrowthFactor* = 2 # must be power of 2, > 0
StartSize* = 8 # must be power of 2, > 0

View File

@@ -1436,6 +1436,8 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
of mArrToSeq: genArrToSeq(p, e, d)
of mNLen..mNError:
localError(e.info, errCannotGenerateCodeForX, e.sons[0].sym.name.s)
of mSlurp, mStaticExec:
localError(e.info, errXMustBeCompileTime, e.sons[0].sym.name.s)
else: internalError(e.info, "genMagicExpr: " & $op)
proc genConstExpr(p: BProc, n: PNode): PRope

View File

@@ -1173,6 +1173,8 @@ proc genMagic(p: var TProc, n: PNode, r: var TCompRes) =
localError(n.info, errCannotGenerateCodeForX, n.sons[0].sym.name.s)
of mNewSeq: binaryStmt(p, n, r, "", "$1 = new Array($2)")
of mEcho: genEcho(p, n, r)
of mSlurp, mStaticExec:
localError(n.info, errXMustBeCompileTime, n.sons[0].sym.name.s)
else:
genCall(p, n, r)
#else internalError(e.info, 'genMagic: ' + magicToStr[op]);

View File

@@ -16,7 +16,7 @@
import
strutils, magicsys, lists, options, ast, astalgo, trees, treetab, nimsets,
msgs, os, condsyms, idents, renderer, types, passes, semfold, transf,
parser, ropes, rodread, idgen
parser, ropes, rodread, idgen, osproc, streams
type
PStackFrame* = ref TStackFrame
@@ -910,6 +910,43 @@ proc evalTypeTrait*(n: PNode, context: PSym): PNode =
else:
internalAssert false
proc expectString(n: PNode) =
if n.kind notin {nkStrLit, nkRStrLit, nkTripleStrLit}:
GlobalError(n.info, errStringLiteralExpected)
proc evalSlurp*(e: PNode, module: PSym): PNode =
expectString(e)
try:
var filename = e.strVal.FindFile
var content = readFile(filename)
result = newStrNode(nkStrLit, content)
result.typ = getSysType(tyString)
result.info = e.info
# we produce a fake include statement for every slurped filename, so that
# the module dependencies are accurate:
appendToModule(module, newNode(nkIncludeStmt, e.info, @[
newStrNode(nkStrLit, filename)]))
except EIO:
GlobalError(e.info, errCannotOpenFile, e.strVal)
proc readOutput(p: PProcess): string =
result = ""
var output = p.outputStream
discard p.waitForExit
while not output.atEnd:
result.add(output.readLine)
proc evalStaticExec*(cmd, input: PNode): PNode =
expectString(cmd)
var p = startCmd(cmd.strVal)
if input != nil:
expectString(input)
p.inputStream.write(input.strVal)
p.inputStream.close()
result = newStrNode(nkStrLit, p.readOutput)
result.typ = getSysType(tyString)
result.info = cmd.info
proc evalExpandToAst(c: PEvalContext, original: PNode): PNode =
var
n = original.copyTree
@@ -960,6 +997,11 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode =
of mParseStmtToAst: result = evalParseStmt(c, n)
of mExpandToAst: result = evalExpandToAst(c, n)
of mTypeTrait: result = evalTypeTrait(n, c.module)
of mSlurp: result = evalSlurp(evalAux(c, n.sons[1], {}), c.module)
of mStaticExec:
let cmd = evalAux(c, n.sons[1], {})
let input = if n.sonsLen == 3: evalAux(c, n.sons[2], {}) else: nil
result = evalStaticExec(cmd, input)
of mNLen:
result = evalAux(c, n.sons[1], {efLValue})
if isSpecial(result): return

View File

@@ -94,7 +94,7 @@ type
errUnhandledExceptionX, errCyclicTree, errXisNoMacroOrTemplate,
errXhasSideEffects, errIteratorExpected, errLetNeedsInit,
errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX,
errXCannotBeClosure,
errXCannotBeClosure, errXMustBeCompileTime,
errUser,
warnCannotOpenFile,
warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit,
@@ -327,6 +327,7 @@ const
errWrongSymbolX: "usage of \'$1\' is a user-defined error",
errIllegalCaptureX: "illegal capture '$1'",
errXCannotBeClosure: "'$1' cannot have 'closure' calling convention",
errXMustBeCompileTime: "'$1' can only be used in compile-time context",
errUser: "$1",
warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]",
warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]",

View File

@@ -89,9 +89,6 @@ proc semConstExpr(c: PContext, n: PNode): PNode =
return nil
result = evalTypedExpr(c, e)
proc semAndEvalConstExpr(c: PContext, n: PNode): PNode =
result = semConstExpr(c, n)
include seminst, semcall
proc semAfterMacroCall(c: PContext, n: PNode, s: PSym): PNode =
@@ -219,12 +216,8 @@ proc myClose(context: PPassContext, n: PNode): PNode =
else:
InternalError(n.info, "n is not nil") #result := n;
addCodeForGenerics(c, result)
# we produce a fake include statement for every slurped filename, so that
# the module dependencies are accurate:
var ics = newNode(nkIncludeStmt)
for s in items(c.slurpedFiles): ics.add(newStrNode(nkStrLit, s))
result.add(ics)
if c.module.ast != nil:
result.add(c.module.ast)
checkThreads(c)
popOwner()
popProcCon(c)

View File

@@ -73,7 +73,6 @@ type
filename*: string # the module's filename
userPragmas*: TStrTable
evalContext*: PEvalContext
slurpedFiles*: seq[string]
var
gGenericsCache: PGenericsCache # save for modularity
@@ -153,7 +152,6 @@ proc newContext(module: PSym, nimfile: string): PContext =
result.filename = nimfile
result.includedFiles = initIntSet()
initStrTable(result.userPragmas)
result.slurpedFiles = @[]
if optSymbolFiles notin gGlobalOptions:
# re-usage of generic instantiations across module boundaries is
# very nice for code size:

View File

@@ -491,12 +491,6 @@ proc analyseIfAddressTakenInCall(c: PContext, n: PNode) =
skipTypes(t.sons[i], abstractInst).kind == tyVar:
n.sons[i] = analyseIfAddressTaken(c, n.sons[i])
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)
include semmagic
proc evalAtCompileTime(c: PContext, n: PNode): PNode =
@@ -901,7 +895,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] = semAndEvalConstExpr(c, n.sons[1])
n.sons[1] = semConstExpr(c, n.sons[1])
if skipTypes(n.sons[1].typ, {tyGenericInst, tyRange, tyOrdinal}).kind in
{tyInt..tyInt64}:
var idx = getOrdValue(n.sons[1])

View File

@@ -207,7 +207,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode =
mExit, mInc, ast.mDec, mEcho, mSwap, mAppendStrCh,
mAppendStrStr, mAppendSeqElem, mSetLengthStr, mSetLengthSeq,
mParseExprToAst, mParseStmtToAst, mExpandToAst, mTypeTrait,
mNLen..mNError, mEqRef:
mNLen..mNError, mEqRef, mSlurp, mStaticExec:
nil
of mRand:
result = newIntNodeT(math.random(a.getInt.int), n)

View File

@@ -14,19 +14,6 @@ proc semIsPartOf(c: PContext, n: PNode, flags: TExprFlags): PNode =
var r = isPartOf(n[1], n[2])
result = newIntNodeT(ord(r), n)
proc semSlurp(c: PContext, n: PNode, flags: TExprFlags): PNode =
assert sonsLen(n) == 2
var a = expectStringArg(c, n, 0)
try:
var filename = a.strVal.FindFile
var content = readFile(filename)
result = newStrNode(nkStrLit, content)
result.typ = getSysType(tyString)
result.info = n.info
c.slurpedFiles.add(a.strVal)
except EIO:
GlobalError(a.info, errCannotOpenFile, a.strVal)
proc expectIntLit(c: PContext, n: PNode): int =
let x = c.semConstExpr(c, n)
case x.kind
@@ -56,7 +43,6 @@ proc semTypeTraits(c: PContext, n: PNode): PNode =
proc magicsAfterOverloadResolution(c: PContext, n: PNode,
flags: TExprFlags): PNode =
case n[0].sym.magic
of mSlurp: result = semSlurp(c, n, flags)
of mIsPartOf: result = semIsPartOf(c, n, flags)
of mTypeTrait: result = semTypeTraits(c, n)
of mAstToStr:

View File

@@ -27,7 +27,7 @@ proc semWhen(c: PContext, n: PNode, semCheck = true): PNode =
case it.kind
of nkElifBranch, nkElifExpr:
checkSonsLen(it, 2)
var e = semAndEvalConstExpr(c, it.sons[0])
var e = semConstExpr(c, it.sons[0])
if e.kind != nkIntLit: InternalError(n.info, "semWhen")
if e.intVal != 0 and result == nil:
setResult(it.sons[1])

View File

@@ -2150,10 +2150,21 @@ proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo".}
## the `typeinfo` module instead.
proc slurp*(filename: string): string {.magic: "Slurp".}
## compiletime ``readFile`` proc for easy `resource`:idx: embedding:
proc staticRead*(filename: string): string {.magic: "Slurp".}
## compile-time ``readFile`` proc for easy `resource`:idx: embedding:
## .. code-block:: nimrod
##
## const myResource = slurp"mydatafile.bin"
## const myResource = staticRead"mydatafile.bin"
##
proc staticExec*(command: string, input = ""): string {.magic: "StaticExec".}
## executes an external process at compile-time.
## if `input` is not an empty string, it will be passed as a standard input
## to the executed program.
## .. code-block:: nimrod
##
## const buildInfo = "Revision " & staticExec("git rev-parse HEAD") &
## "\nCompiled on " & staticExec("uname -v")
##
proc `+=`*[T](x, y: ordinal[T]) {.magic: "Inc", noSideEffect.}

View File

@@ -27,6 +27,8 @@ Library Additions
assignments.
- Added ``system.eval`` that can execute an anonymous block of code at
compile time as if was a macro.
- Added ``system.staticExec`` for compile-time execution of external programs
- Added ``system.staticRead`` as a synonym for slurp
- Added ``macros.emit`` that can emit an arbitrary computed string as nimrod
code during compilation.
- Added ``strutils.parseEnum``.