Added system.staticExec proc for executing external command at compile-time
This commit is contained in:
parent
337e1ecc7c
commit
a1da1f987b
13 changed files with 83 additions and 39 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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]);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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]",
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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])
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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])
|
||||||
|
|
|
||||||
|
|
@ -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.}
|
||||||
|
|
|
||||||
|
|
@ -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``.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue