Merge branch 'master' of github.com:Araq/Nimrod

This commit is contained in:
Araq
2012-06-03 10:11:01 +02:00
17 changed files with 261 additions and 73 deletions

View File

@@ -371,7 +371,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,
@@ -542,6 +542,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;
@@ -568,6 +571,9 @@ type
# for record types a nkRecord node
# for enum types a list of symbols
# else: unused
destructor*: PSym # destructor. warning: nil here may not necessary
# mean that there is no destructor.
# see instantiateDestructor in types.nim
owner*: PSym # the 'owner' of the type
sym*: PSym # types have the sym associated with them
# it is used for converting types to strings
@@ -740,6 +746,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

@@ -1437,6 +1437,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

@@ -57,6 +57,8 @@ proc optInd*(p: var TParser, n: PNode)
proc indAndComment*(p: var TParser, n: PNode)
proc setBaseFlags*(n: PNode, base: TNumericalBase)
proc parseSymbol*(p: var TParser): PNode
proc parseTry(p: var TParser): PNode
proc parseCase(p: var TParser): PNode
# implementation
proc getTok(p: var TParser) =
@@ -468,6 +470,11 @@ proc primarySuffix(p: var TParser, r: PNode): PNode =
addSon(result, a)
exprColonEqExprListAux(p, nkExprEqExpr, tkParRi, tkEquals, result)
parseDoBlocks(p, result)
of tkDo:
var a = result
result = newNodeP(nkCall, p)
addSon(result, a)
parseDoBlocks(p, result)
of tkDot:
result = dotExpr(p, result)
result = parseGStrLit(p, result)
@@ -704,6 +711,8 @@ proc parseExpr(p: var TParser): PNode =
case p.tok.tokType:
of tkIf: result = parseIfExpr(p, nkIfExpr)
of tkWhen: result = parseIfExpr(p, nkWhenExpr)
of tkTry: result = parseTry(p)
of tkCase: result = parseCase(p)
else: result = lowestExpr(p)
proc primary(p: var TParser, skipSuffix = false): PNode =
@@ -944,13 +953,19 @@ proc parseWhile(p: var TParser): PNode =
proc parseCase(p: var TParser): PNode =
var
b: PNode
inElif: bool
inElif= false
wasIndented = false
result = newNodeP(nkCaseStmt, p)
getTok(p)
addSon(result, parseExpr(p))
if p.tok.tokType == tkColon: getTok(p)
skipComment(p, result)
inElif = false
if p.tok.tokType == tkInd:
pushInd(p.lex, p.tok.indent)
getTok(p)
wasIndented = true
while true:
if p.tok.tokType == tkSad: getTok(p)
case p.tok.tokType
@@ -973,8 +988,12 @@ proc parseCase(p: var TParser): PNode =
skipComment(p, b)
addSon(b, parseStmt(p))
addSon(result, b)
if b.kind == nkElse: break
if b.kind == nkElse: break
if wasIndented:
eat(p, tkDed)
popInd(p.lex)
proc parseTry(p: var TParser): PNode =
result = newNodeP(nkTryStmt, p)
getTok(p)
@@ -998,7 +1017,14 @@ proc parseTry(p: var TParser): PNode =
addSon(result, b)
if b.kind == nkFinally: break
if b == nil: parMessage(p, errTokenExpected, "except")
proc parseExceptBlock(p: var TParser, kind: TNodeKind): PNode =
result = newNodeP(kind, p)
getTok(p)
eat(p, tkColon)
skipComment(p, result)
addSon(result, parseStmt(p))
proc parseFor(p: var TParser): PNode =
result = newNodeP(nkForStmt, p)
getTok(p)
@@ -1393,6 +1419,8 @@ proc complexOrSimpleStmt(p: var TParser): PNode =
of tkWhile: result = parseWhile(p)
of tkCase: result = parseCase(p)
of tkTry: result = parseTry(p)
of tkFinally: result = parseExceptBlock(p, nkFinally)
of tkExcept: result = parseExceptBlock(p, nkExceptBranch)
of tkFor: result = parseFor(p)
of tkBlock: result = parseBlock(p)
of tkStatic: result = parseStatic(p)

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])
@@ -772,6 +772,10 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
incl(s.flags, sfForward)
elif sfBorrow in s.flags: semBorrow(c, n, s)
sideEffectsCheck(c, s)
if result.sons[namePos].sym.name.id == ord(wDestroy):
if s.typ.sons.len == 2:
let typ = s.typ.sons[1].skipTypes({tyVar})
typ.destructor = s
if s.typ.callConv == ccClosure and s.owner.kind == skModule:
localError(s.info, errXCannotBeClosure, s.name.s)
closeScope(c.tab) # close scope for parameters
@@ -860,6 +864,62 @@ proc semStaticStmt(c: PContext, n: PNode): PNode =
if result.isNil:
LocalError(n.info, errCannotInterpretNodeX, renderTree(n))
proc insertDestructors(c: PContext, varSection: PNode):
tuple[outer: PNode, inner: PNode] =
# Accepts a var or let section.
#
# When a var section has variables with destructors
# the var section is split up and finally blocks are inserted
# immediately after all "destructable" vars
#
# In case there were no destrucable variables, the proc returns
# (nil, nil) and the enclosing stmt-list requires no modifications.
#
# Otherwise, after the try blocks are created, the rest of the enclosing
# stmt-list should be inserted in the most `inner` such block (corresponding
# to the last variable).
#
# `outer` is a statement list that should replace the original var section.
# It will include the new truncated var section followed by the outermost
# try block.
let totalVars = varSection.sonsLen
for j in countup(0, totalVars - 1):
let
varId = varSection[j][0]
varTyp = varId.sym.typ
info = varId.info
if varTyp != nil and instantiateDestructor(varTyp):
var tryStmt = newNodeI(nkTryStmt, info)
if j < totalVars - 1:
var remainingVars = newNodeI(varSection.kind, info)
remainingVars.sons = varSection.sons[(j+1)..(-1)]
let (outer, inner) = insertDestructors(c, remainingVars)
if outer != nil:
tryStmt.addSon(outer)
result.inner = inner
else:
result.inner = newNodeI(nkStmtList, info)
result.inner.addSon(remainingVars)
tryStmt.addSon(result.inner)
else:
result.inner = newNodeI(nkStmtList, info)
tryStmt.addSon(result.inner)
tryStmt.addSon(
newNode(nkFinally, info, @[
semStmt(c, newNode(nkCall, info, @[
semSym(c, varId, varTyp.destructor, {}),
semSym(c, varId, varId.sym, {})]))]))
result.outer = newNodeI(nkStmtList, info)
varSection.sons.setLen(j+1)
result.outer.addSon(varSection)
result.outer.addSon(tryStmt)
return
proc SemStmt(c: PContext, n: PNode): PNode =
const # must be last statements in a block:
LastBlockStmts = {nkRaiseStmt, nkReturnStmt, nkBreakStmt, nkContinueStmt}
@@ -875,13 +935,43 @@ proc SemStmt(c: PContext, n: PNode): PNode =
of nkBlockStmt: result = semBlock(c, n)
of nkStmtList:
var length = sonsLen(n)
for i in countup(0, length - 1):
n.sons[i] = semStmt(c, n.sons[i])
if n.sons[i].kind in LastBlockStmts:
for j in countup(i + 1, length - 1):
case n.sons[j].kind
of nkPragma, nkCommentStmt, nkNilLit, nkEmpty: nil
else: localError(n.sons[j].info, errStmtInvalidAfterReturn)
for i in countup(0, length - 1):
case n.sons[i].kind
of nkFinally, nkExceptBranch:
# stand-alone finally and except blocks are
# transformed into regular try blocks:
#
# var f = fopen("somefile") | var f = fopen("somefile")
# finally: fcsole(f) | try:
# ... | ...
# | finally:
# | fclose(f)
var tryStmt = newNodeI(nkTryStmt, n.sons[i].info)
var body = newNodeI(nkStmtList, n.sons[i].info)
if i < n.sonsLen - 1:
body.sons = n.sons[(i+1)..(-1)]
tryStmt.addSon(body)
tryStmt.addSon(n.sons[i])
n.sons[i] = semTry(c, tryStmt)
n.sons.setLen(i+1)
return
else:
n.sons[i] = semStmt(c, n.sons[i])
case n.sons[i].kind
of nkVarSection, nkLetSection:
let (outer, inner) = insertDestructors(c, n.sons[i])
if outer != nil:
n.sons[i] = outer
for j in countup(i+1, length-1):
inner.addSon(SemStmt(c, n.sons[j]))
n.sons.setLen(i+1)
return
of LastBlockStmts:
for j in countup(i + 1, length - 1):
case n.sons[j].kind
of nkPragma, nkCommentStmt, nkNilLit, nkEmpty: nil
else: localError(n.sons[j].info, errStmtInvalidAfterReturn)
else: nil
of nkRaiseStmt: result = semRaise(c, n)
of nkVarSection: result = semVarOrLet(c, n, skVar)
of nkLetSection: result = semVarOrLet(c, n, skLet)

View File

@@ -1061,4 +1061,11 @@ proc getReturnType*(s: PSym): PType =
proc getSize(typ: PType): biggestInt =
result = computeSize(typ)
if result < 0: InternalError("getSize(" & $typ.kind & ')')
proc instantiateDestructor*(typ: PType): bool =
# return true if the type already had a user-defined
# destructor or if the compiler generated a default
# member-wise one
if typ.destructor != nil: return true
return false

View File

@@ -36,6 +36,9 @@ type
wColon, wColonColon, wEquals, wDot, wDotDot,
wStar, wMinus,
wMagic, wThread, wFinal, wProfiler, wObjChecks,
wDestroy,
wImmediate, wImportCpp, wImportObjC,
wImportCompilerProc,
wImportc, wExportc, wIncompleteStruct,
@@ -110,7 +113,9 @@ const
":", "::", "=", ".", "..",
"*", "-",
"magic", "thread", "final", "profiler", "objchecks",
"magic", "thread", "final", "profiler", "objchecks",
"destroy",
"immediate", "importcpp", "importobjc",
"importcompilerproc", "importc", "exportc", "incompletestruct",

View File

@@ -388,37 +388,37 @@ indentation tokens is already described in the `Lexical Analysis`_ section.
Nimrod allows user-definable operators.
Binary operators have 10 different levels of precedence.
Relevant character
------------------
Relevant character
------------------
An operator symbol's *relevant character* is its first
character unless the first character is ``\`` and its length is greater than 1
then it is the second character.
This rule allows to escape operator symbols with ``\`` and keeps the operator's
precedence and associativity; this is useful for meta programming.
Associativity
-------------
All binary operators are left-associative, except binary operators whose
then it is the second character.
This rule allows to escape operator symbols with ``\`` and keeps the operator's
precedence and associativity; this is useful for meta programming.
Associativity
-------------
All binary operators are left-associative, except binary operators whose
relevant char is ``^``.
Precedence
----------
Precedence
----------
For operators that are not keywords the precedence is determined by the
following rules:
If the operator ends with ``=`` and its relevant character is none of
``<``, ``>``, ``!``, ``=``, ``~``, ``?``, it is an *assignment operator* which
has the lowest precedence.
If the operator's relevant character is ``@`` it is a `sigil-like`:idx:
operator which binds stronger than a ``primarySuffix``: ``@x.abc`` is parsed
as ``(@x).abc`` whereas ``$x.abc`` is parsed as ``$(x.abc)``.
has the lowest precedence.
If the operator's relevant character is ``@`` it is a `sigil-like`:idx:
operator which binds stronger than a ``primarySuffix``: ``@x.abc`` is parsed
as ``(@x).abc`` whereas ``$x.abc`` is parsed as ``$(x.abc)``.
Otherwise precedence is determined by the relevant character.
@@ -1879,6 +1879,15 @@ handled, it is propagated through the call stack. This means that often
the rest of the procedure - that is not within a ``finally`` clause -
is not executed (if an exception occurs).
`except`:idx: and `finally`:idx: can also be used as a stand-alone statements.
Any statements following them in the current block will be considered to be
in an implicit try block:
.. code-block:: nimrod
var f = fopen("numbers.txt", "r")
finally: fcsole(f)
...
Return statement
~~~~~~~~~~~~~~~~

View File

@@ -2168,10 +2168,22 @@ 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".} = nil
## 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``.
@@ -102,6 +104,7 @@ Language Additions
- ``when`` expressions are now allowed just like ``if`` expressions.
- The precedence for operators starting with ``@`` is different now
allowing for *sigil-like* operators.
- Stand-alone ``finally`` and ``except`` blocks are now supported.
2012-02-09 Version 0.8.14 released