mirror of
https://github.com/nim-lang/Nim.git
synced 2026-07-19 15:31:28 +00:00
Added system.staticExec proc for executing external command at compile-time
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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.}
|
||||
|
||||
@@ -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``.
|
||||
|
||||
Reference in New Issue
Block a user