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
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: TMagic* = enum # symbols that require compiler magic:
mNone, mNone,
mDefined, mDefinedInScope, mLow, mHigh, mSizeOf, mTypeTrait, mIs, mOf, mDefined, mDefinedInScope, mLow, mHigh, mSizeOf, mTypeTrait, mIs, mOf,
mEcho, mShallowCopy, mSlurp, mEcho, mShallowCopy, mSlurp, mStaticExec,
mParseExprToAst, mParseStmtToAst, mExpandToAst, mParseExprToAst, mParseStmtToAst, mExpandToAst,
mUnaryLt, mSucc, mUnaryLt, mSucc,
mPred, mInc, mDec, mOrd, mNew, mNewFinalize, mNewSeq, mLengthOpenArray, mPred, mInc, mDec, mOrd, mNew, mNewFinalize, mNewSeq, mLengthOpenArray,
@ -541,6 +541,9 @@ type
# for easy generation of proper error messages # for easy generation of proper error messages
# for variant record fields the discriminant # for variant record fields the discriminant
# expression # 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 options*: TOptions
position*: int # used for many different things: position*: int # used for many different things:
# for enum fields its position; # for enum fields its position;
@ -742,6 +745,16 @@ proc linkTo*(s: PSym, t: PType): PSym {.discardable.} =
s.typ = t s.typ = t
result = s 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: const # for all kind of hash tables:
GrowthFactor* = 2 # must be power of 2, > 0 GrowthFactor* = 2 # must be power of 2, > 0
StartSize* = 8 # 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 mArrToSeq: genArrToSeq(p, e, d)
of mNLen..mNError: of mNLen..mNError:
localError(e.info, errCannotGenerateCodeForX, e.sons[0].sym.name.s) 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) else: internalError(e.info, "genMagicExpr: " & $op)
proc genConstExpr(p: BProc, n: PNode): PRope 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) localError(n.info, errCannotGenerateCodeForX, n.sons[0].sym.name.s)
of mNewSeq: binaryStmt(p, n, r, "", "$1 = new Array($2)") of mNewSeq: binaryStmt(p, n, r, "", "$1 = new Array($2)")
of mEcho: genEcho(p, n, r) of mEcho: genEcho(p, n, r)
of mSlurp, mStaticExec:
localError(n.info, errXMustBeCompileTime, n.sons[0].sym.name.s)
else: else:
genCall(p, n, r) genCall(p, n, r)
#else internalError(e.info, 'genMagic: ' + magicToStr[op]); #else internalError(e.info, 'genMagic: ' + magicToStr[op]);

View file

@ -16,7 +16,7 @@
import import
strutils, magicsys, lists, options, ast, astalgo, trees, treetab, nimsets, 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, rodread, idgen parser, ropes, rodread, idgen, osproc, streams
type type
PStackFrame* = ref TStackFrame PStackFrame* = ref TStackFrame
@ -910,6 +910,43 @@ proc evalTypeTrait*(n: PNode, context: PSym): PNode =
else: else:
internalAssert false 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 = proc evalExpandToAst(c: PEvalContext, original: PNode): PNode =
var var
n = original.copyTree n = original.copyTree
@ -960,6 +997,11 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode =
of mParseStmtToAst: result = evalParseStmt(c, n) of mParseStmtToAst: result = evalParseStmt(c, n)
of mExpandToAst: result = evalExpandToAst(c, n) of mExpandToAst: result = evalExpandToAst(c, n)
of mTypeTrait: result = evalTypeTrait(n, c.module) 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: of mNLen:
result = evalAux(c, n.sons[1], {efLValue}) result = evalAux(c, n.sons[1], {efLValue})
if isSpecial(result): return if isSpecial(result): return

View file

@ -94,7 +94,7 @@ type
errUnhandledExceptionX, errCyclicTree, errXisNoMacroOrTemplate, errUnhandledExceptionX, errCyclicTree, errXisNoMacroOrTemplate,
errXhasSideEffects, errIteratorExpected, errLetNeedsInit, errXhasSideEffects, errIteratorExpected, errLetNeedsInit,
errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX, errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX,
errXCannotBeClosure, errXCannotBeClosure, errXMustBeCompileTime,
errUser, errUser,
warnCannotOpenFile, warnCannotOpenFile,
warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit, warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit,
@ -327,6 +327,7 @@ const
errWrongSymbolX: "usage of \'$1\' is a user-defined error", errWrongSymbolX: "usage of \'$1\' is a user-defined error",
errIllegalCaptureX: "illegal capture '$1'", errIllegalCaptureX: "illegal capture '$1'",
errXCannotBeClosure: "'$1' cannot have 'closure' calling convention", errXCannotBeClosure: "'$1' cannot have 'closure' calling convention",
errXMustBeCompileTime: "'$1' can only be used in compile-time context",
errUser: "$1", errUser: "$1",
warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]", warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]",
warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]", 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 return nil
result = evalTypedExpr(c, e) result = evalTypedExpr(c, e)
proc semAndEvalConstExpr(c: PContext, n: PNode): PNode =
result = semConstExpr(c, n)
include seminst, semcall include seminst, semcall
proc semAfterMacroCall(c: PContext, n: PNode, s: PSym): PNode = proc semAfterMacroCall(c: PContext, n: PNode, s: PSym): PNode =
@ -219,12 +216,8 @@ proc myClose(context: PPassContext, n: PNode): PNode =
else: else:
InternalError(n.info, "n is not nil") #result := n; InternalError(n.info, "n is not nil") #result := n;
addCodeForGenerics(c, result) addCodeForGenerics(c, result)
# we produce a fake include statement for every slurped filename, so that if c.module.ast != nil:
# the module dependencies are accurate: result.add(c.module.ast)
var ics = newNode(nkIncludeStmt)
for s in items(c.slurpedFiles): ics.add(newStrNode(nkStrLit, s))
result.add(ics)
checkThreads(c) checkThreads(c)
popOwner() popOwner()
popProcCon(c) popProcCon(c)

View file

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

View file

@ -491,12 +491,6 @@ proc analyseIfAddressTakenInCall(c: PContext, n: PNode) =
skipTypes(t.sons[i], abstractInst).kind == tyVar: skipTypes(t.sons[i], abstractInst).kind == tyVar:
n.sons[i] = analyseIfAddressTaken(c, n.sons[i]) 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 include semmagic
proc evalAtCompileTime(c: PContext, n: PNode): PNode = proc evalAtCompileTime(c: PContext, n: PNode): PNode =
@ -901,7 +895,7 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
checkSonsLen(n, 2) checkSonsLen(n, 2)
n.sons[0] = makeDeref(n.sons[0]) n.sons[0] = makeDeref(n.sons[0])
# [] operator for tuples requires constant expression: # [] 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 if skipTypes(n.sons[1].typ, {tyGenericInst, tyRange, tyOrdinal}).kind in
{tyInt..tyInt64}: {tyInt..tyInt64}:
var idx = getOrdValue(n.sons[1]) 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, mExit, mInc, ast.mDec, mEcho, mSwap, mAppendStrCh,
mAppendStrStr, mAppendSeqElem, mSetLengthStr, mSetLengthSeq, mAppendStrStr, mAppendSeqElem, mSetLengthStr, mSetLengthSeq,
mParseExprToAst, mParseStmtToAst, mExpandToAst, mTypeTrait, mParseExprToAst, mParseStmtToAst, mExpandToAst, mTypeTrait,
mNLen..mNError, mEqRef: mNLen..mNError, mEqRef, mSlurp, mStaticExec:
nil nil
of mRand: of mRand:
result = newIntNodeT(math.random(a.getInt.int), n) 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]) var r = isPartOf(n[1], n[2])
result = newIntNodeT(ord(r), n) 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 = proc expectIntLit(c: PContext, n: PNode): int =
let x = c.semConstExpr(c, n) let x = c.semConstExpr(c, n)
case x.kind case x.kind
@ -56,7 +43,6 @@ proc semTypeTraits(c: PContext, n: PNode): PNode =
proc magicsAfterOverloadResolution(c: PContext, n: PNode, proc magicsAfterOverloadResolution(c: PContext, n: PNode,
flags: TExprFlags): PNode = flags: TExprFlags): PNode =
case n[0].sym.magic case n[0].sym.magic
of mSlurp: result = semSlurp(c, n, flags)
of mIsPartOf: result = semIsPartOf(c, n, flags) of mIsPartOf: result = semIsPartOf(c, n, flags)
of mTypeTrait: result = semTypeTraits(c, n) of mTypeTrait: result = semTypeTraits(c, n)
of mAstToStr: of mAstToStr:

View file

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

View file

@ -2150,10 +2150,21 @@ proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo".}
## the `typeinfo` module instead. ## the `typeinfo` module instead.
proc slurp*(filename: string): string {.magic: "Slurp".} 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 ## .. 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.} proc `+=`*[T](x, y: ordinal[T]) {.magic: "Inc", noSideEffect.}

View file

@ -27,6 +27,8 @@ Library Additions
assignments. assignments.
- Added ``system.eval`` that can execute an anonymous block of code at - Added ``system.eval`` that can execute an anonymous block of code at
compile time as if was a macro. 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 - Added ``macros.emit`` that can emit an arbitrary computed string as nimrod
code during compilation. code during compilation.
- Added ``strutils.parseEnum``. - Added ``strutils.parseEnum``.