mirror of
https://github.com/nim-lang/Nim.git
synced 2026-02-12 06:18:51 +00:00
Incremental compilation (IC): Improvements (#11881)
* IC: C codegen is aware of IC * manual: minor change to make VSCode's RST plugin render it properly * IC: minor refactoring * testament: code refactorings * rodutils: removed dead code * IC: always build the compiler with the IC feature * IC: C codegen improvements * IC: implement the undocumented -d:nimMustCache option for testing purposes * IC: added first basic tests * IC: extensive testing of the deserialization feature * testament: refactoring; better IC tests * IC: removes 'nimMustCache' flag; readonly does the same * testament: minor refactoring * update Nimble version * testament: removed dead code and imports; IC: added simple test * IC: progress
This commit is contained in:
@@ -1108,7 +1108,8 @@ proc genProcNoForward(m: BModule, prc: PSym) =
|
||||
# a check for ``m.declaredThings``.
|
||||
if not containsOrIncl(m.declaredThings, prc.id):
|
||||
#if prc.loc.k == locNone:
|
||||
# mangle the inline proc based on the module where it is defined - not on the first module that uses it
|
||||
# mangle the inline proc based on the module where it is defined -
|
||||
# not on the first module that uses it
|
||||
fillProcLoc(findPendingModule(m, prc), prc.ast[namePos])
|
||||
#elif {sfExportc, sfImportc} * prc.flags == {}:
|
||||
# # reset name to restore consistency in case of hashing collisions:
|
||||
@@ -1781,10 +1782,6 @@ proc rawNewModule(g: BModuleList; module: PSym, filename: AbsoluteFile): BModule
|
||||
else: AbsoluteFile""
|
||||
open(result.ndi, ndiName, g.config)
|
||||
|
||||
proc nullify[T](arr: var T) =
|
||||
for i in low(arr)..high(arr):
|
||||
arr[i] = Rope(nil)
|
||||
|
||||
proc rawNewModule(g: BModuleList; module: PSym; conf: ConfigRef): BModule =
|
||||
result = rawNewModule(g, module, AbsoluteFile toFullPath(conf, module.position.FileIndex))
|
||||
|
||||
@@ -1875,7 +1872,9 @@ proc myProcess(b: PPassContext, n: PNode): PNode =
|
||||
result = n
|
||||
if b == nil: return
|
||||
var m = BModule(b)
|
||||
if passes.skipCodegen(m.config, n): return
|
||||
if passes.skipCodegen(m.config, n) or
|
||||
not moduleHasChanged(m.g.graph, m.module):
|
||||
return
|
||||
m.initProc.options = initProcOptions(m)
|
||||
#softRnl = if optLineDir in m.config.options: noRnl else: rnl
|
||||
# XXX replicate this logic!
|
||||
@@ -1886,9 +1885,10 @@ proc myProcess(b: PPassContext, n: PNode): PNode =
|
||||
genStmts(m.initProc, transformedN)
|
||||
|
||||
proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool =
|
||||
result = true
|
||||
if optForceFullMake notin m.config.globalOptions:
|
||||
if not equalsFile(code, cfile.cname):
|
||||
if not moduleHasChanged(m.g.graph, m.module):
|
||||
result = false
|
||||
elif not equalsFile(code, cfile.cname):
|
||||
if false:
|
||||
#m.config.symbolFiles == readOnlySf: #isDefined(m.config, "nimdiff"):
|
||||
if fileExists(cfile.cname):
|
||||
@@ -1898,12 +1898,15 @@ proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool =
|
||||
echo "new file ", cfile.cname.string
|
||||
if not writeRope(code, cfile.cname):
|
||||
rawMessage(m.config, errCannotOpenFile, cfile.cname.string)
|
||||
return
|
||||
if fileExists(cfile.obj) and os.fileNewer(cfile.obj.string, cfile.cname.string):
|
||||
result = true
|
||||
elif fileExists(cfile.obj) and os.fileNewer(cfile.obj.string, cfile.cname.string):
|
||||
result = false
|
||||
else:
|
||||
result = true
|
||||
else:
|
||||
if not writeRope(code, cfile.cname):
|
||||
rawMessage(m.config, errCannotOpenFile, cfile.cname.string)
|
||||
result = true
|
||||
|
||||
# We need 2 different logics here: pending modules (including
|
||||
# 'nim__dat') may require file merging for the combination of dead code
|
||||
@@ -1915,18 +1918,19 @@ proc writeModule(m: BModule, pending: bool) =
|
||||
let cfile = getCFile(m)
|
||||
|
||||
if true or optForceFullMake in m.config.globalOptions:
|
||||
genInitCode(m)
|
||||
finishTypeDescriptions(m)
|
||||
if sfMainModule in m.module.flags:
|
||||
# generate main file:
|
||||
genMainProc(m)
|
||||
add(m.s[cfsProcHeaders], m.g.mainModProcs)
|
||||
generateThreadVarsSize(m)
|
||||
if moduleHasChanged(m.g.graph, m.module):
|
||||
genInitCode(m)
|
||||
finishTypeDescriptions(m)
|
||||
if sfMainModule in m.module.flags:
|
||||
# generate main file:
|
||||
genMainProc(m)
|
||||
add(m.s[cfsProcHeaders], m.g.mainModProcs)
|
||||
generateThreadVarsSize(m)
|
||||
|
||||
var cf = Cfile(nimname: m.module.name.s, cname: cfile,
|
||||
obj: completeCfilePath(m.config, toObjFile(m.config, cfile)), flags: {})
|
||||
var code = genModule(m, cf)
|
||||
if code != nil:
|
||||
if code != nil or m.config.symbolFiles != disabledSf:
|
||||
when hasTinyCBackend:
|
||||
if conf.cmd == cmdRun:
|
||||
tccgen.compileCCode($code)
|
||||
@@ -1983,46 +1987,47 @@ proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode =
|
||||
for destructorCall in graph.globalDestructors:
|
||||
n.add destructorCall
|
||||
if passes.skipCodegen(m.config, n): return
|
||||
# if the module is cached, we don't regenerate the main proc
|
||||
# nor the dispatchers? But if the dispatchers changed?
|
||||
# XXX emit the dispatchers into its own .c file?
|
||||
if n != nil:
|
||||
m.initProc.options = initProcOptions(m)
|
||||
genStmts(m.initProc, n)
|
||||
if moduleHasChanged(graph, m.module):
|
||||
# if the module is cached, we don't regenerate the main proc
|
||||
# nor the dispatchers? But if the dispatchers changed?
|
||||
# XXX emit the dispatchers into its own .c file?
|
||||
if n != nil:
|
||||
m.initProc.options = initProcOptions(m)
|
||||
genStmts(m.initProc, n)
|
||||
|
||||
if m.hcrOn:
|
||||
# make sure this is pulled in (meaning hcrGetGlobal() is called for it during init)
|
||||
discard cgsym(m, "programResult")
|
||||
if m.inHcrInitGuard:
|
||||
endBlock(m.initProc)
|
||||
|
||||
if sfMainModule in m.module.flags:
|
||||
if m.hcrOn:
|
||||
# pull ("define" since they are inline when HCR is on) these functions in the main file
|
||||
# so it can load the HCR runtime and later pass the library handle to the HCR runtime which
|
||||
# will in turn pass it to the other modules it initializes so they can initialize the
|
||||
# register/get procs so they don't have to have the definitions of these functions as well
|
||||
discard cgsym(m, "nimLoadLibrary")
|
||||
discard cgsym(m, "nimLoadLibraryError")
|
||||
discard cgsym(m, "nimGetProcAddr")
|
||||
discard cgsym(m, "procAddrError")
|
||||
discard cgsym(m, "rawWrite")
|
||||
# make sure this is pulled in (meaning hcrGetGlobal() is called for it during init)
|
||||
discard cgsym(m, "programResult")
|
||||
if m.inHcrInitGuard:
|
||||
endBlock(m.initProc)
|
||||
|
||||
# raise dependencies on behalf of genMainProc
|
||||
if m.config.target.targetOS != osStandalone and m.config.selectedGC != gcNone:
|
||||
discard cgsym(m, "initStackBottomWith")
|
||||
if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone:
|
||||
discard cgsym(m, "initThreadVarsEmulation")
|
||||
if sfMainModule in m.module.flags:
|
||||
if m.hcrOn:
|
||||
# pull ("define" since they are inline when HCR is on) these functions in the main file
|
||||
# so it can load the HCR runtime and later pass the library handle to the HCR runtime which
|
||||
# will in turn pass it to the other modules it initializes so they can initialize the
|
||||
# register/get procs so they don't have to have the definitions of these functions as well
|
||||
discard cgsym(m, "nimLoadLibrary")
|
||||
discard cgsym(m, "nimLoadLibraryError")
|
||||
discard cgsym(m, "nimGetProcAddr")
|
||||
discard cgsym(m, "procAddrError")
|
||||
discard cgsym(m, "rawWrite")
|
||||
|
||||
if m.g.breakpoints != nil:
|
||||
discard cgsym(m, "dbgRegisterBreakpoint")
|
||||
if optEndb in m.config.options:
|
||||
discard cgsym(m, "dbgRegisterFilename")
|
||||
# raise dependencies on behalf of genMainProc
|
||||
if m.config.target.targetOS != osStandalone and m.config.selectedGC != gcNone:
|
||||
discard cgsym(m, "initStackBottomWith")
|
||||
if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone:
|
||||
discard cgsym(m, "initThreadVarsEmulation")
|
||||
|
||||
if m.g.forwardedProcs.len == 0:
|
||||
incl m.flags, objHasKidsValid
|
||||
let disp = generateMethodDispatchers(graph)
|
||||
for x in disp: genProcAux(m, x.sym)
|
||||
if m.g.breakpoints != nil:
|
||||
discard cgsym(m, "dbgRegisterBreakpoint")
|
||||
if optEndb in m.config.options:
|
||||
discard cgsym(m, "dbgRegisterFilename")
|
||||
|
||||
if m.g.forwardedProcs.len == 0:
|
||||
incl m.flags, objHasKidsValid
|
||||
let disp = generateMethodDispatchers(graph)
|
||||
for x in disp: genProcAux(m, x.sym)
|
||||
|
||||
m.g.modulesClosed.add m
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ import
|
||||
wordrecg, parseutils, nimblecmd, parseopt, sequtils, lineinfos,
|
||||
pathutils, strtabs
|
||||
|
||||
from incremental import nimIncremental
|
||||
|
||||
# but some have deps to imported modules. Yay.
|
||||
bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc")
|
||||
bootSwitch(usedNativeStacktrace,
|
||||
@@ -669,7 +671,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
helpOnError(conf, pass)
|
||||
of "symbolfiles": discard "ignore for backwards compat"
|
||||
of "incremental":
|
||||
when not defined(nimIncremental):
|
||||
when not nimIncremental:
|
||||
localError(conf, info, "the compiler was not built with " &
|
||||
"incremental compilation features; bootstrap with " &
|
||||
"-d:nimIncremental to enable")
|
||||
|
||||
@@ -10,13 +10,14 @@
|
||||
## Basic type definitions the module graph needs in order to support
|
||||
## incremental compilations.
|
||||
|
||||
const nimIncremental* = defined(nimIncremental)
|
||||
const nimIncremental* = true # defined(nimIncremental)
|
||||
|
||||
import options, lineinfos
|
||||
|
||||
when nimIncremental:
|
||||
import ast, msgs, intsets, btrees, db_sqlite, std / sha1, pathutils
|
||||
from strutils import parseInt
|
||||
from os import isAbsolute
|
||||
|
||||
type
|
||||
Writer* = object
|
||||
@@ -47,7 +48,7 @@ when nimIncremental:
|
||||
|
||||
proc hashFileCached*(conf: ConfigRef; fileIdx: FileIndex; fullpath: AbsoluteFile): string =
|
||||
result = msgs.getHash(conf, fileIdx)
|
||||
if result.len == 0:
|
||||
if result.len == 0 and isAbsolute(string fullpath):
|
||||
result = $secureHashFile(string fullpath)
|
||||
msgs.setHash(conf, fileIdx, result)
|
||||
|
||||
|
||||
@@ -77,8 +77,6 @@ proc compileModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags): P
|
||||
if result == nil:
|
||||
result = newModule(graph, fileIdx)
|
||||
result.flags = result.flags + flags
|
||||
if sfMainModule in result.flags:
|
||||
graph.config.mainPackageId = result.owner.id
|
||||
result.id = id
|
||||
registerModule(graph, result)
|
||||
else:
|
||||
|
||||
@@ -113,6 +113,8 @@ const
|
||||
nkExportStmt, nkExportExceptStmt, nkFromStmt, nkImportStmt, nkImportExceptStmt}
|
||||
|
||||
proc prepareConfigNotes(graph: ModuleGraph; module: PSym) =
|
||||
if sfMainModule in module.flags:
|
||||
graph.config.mainPackageId = module.owner.id
|
||||
# don't be verbose unless the module belongs to the main package:
|
||||
if module.owner.id == graph.config.mainPackageId:
|
||||
graph.config.notes = graph.config.mainPackageNotes
|
||||
@@ -121,7 +123,7 @@ proc prepareConfigNotes(graph: ModuleGraph; module: PSym) =
|
||||
graph.config.notes = graph.config.foreignPackageNotes
|
||||
|
||||
proc moduleHasChanged*(graph: ModuleGraph; module: PSym): bool {.inline.} =
|
||||
result = module.id >= 0
|
||||
result = module.id >= 0 or isDefined(graph.config, "nimBackendAssumesChange")
|
||||
|
||||
proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool {.discardable.} =
|
||||
if graph.stopCompile(): return true
|
||||
@@ -131,7 +133,7 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool {
|
||||
s: PLLStream
|
||||
fileIdx = module.fileIdx
|
||||
prepareConfigNotes(graph, module)
|
||||
if not moduleHasChanged(graph, module):
|
||||
if module.id < 0:
|
||||
# new module caching mechanism:
|
||||
for i in 0 ..< graph.passes.len:
|
||||
if not isNil(graph.passes[i].open) and not graph.passes[i].isFrontend:
|
||||
|
||||
@@ -17,7 +17,6 @@ import strutils, os, intsets, tables, ropes, db_sqlite, msgs, options, types,
|
||||
## - Dependency computation should use *signature* hashes in order to
|
||||
## avoid recompiling dependent modules.
|
||||
## - Patch the rest of the compiler to do lazy loading of proc bodies.
|
||||
## - Patch the C codegen to cache proc bodies and maybe types.
|
||||
|
||||
template db(): DbConn = g.incr.db
|
||||
|
||||
@@ -72,9 +71,12 @@ proc getModuleId(g: ModuleGraph; fileIdx: FileIndex; fullpath: AbsoluteFile): in
|
||||
# not changed, so use the cached AST:
|
||||
doAssert(result != 0)
|
||||
var cycleCheck = initIntSet()
|
||||
if not needsRecompile(g, fileIdx, fullpath, cycleCheck) and not g.incr.configChanged:
|
||||
echo "cached successfully! ", string fullpath
|
||||
return -result
|
||||
if not needsRecompile(g, fileIdx, fullpath, cycleCheck):
|
||||
if not g.incr.configChanged or g.config.symbolFiles == readOnlySf:
|
||||
#echo "cached successfully! ", string fullpath
|
||||
return -result
|
||||
elif g.config.symbolFiles == readOnlySf:
|
||||
internalError(g.config, "file needs to be recompiled: " & (string fullpath))
|
||||
db.exec(sql"update modules set fullHash = ? where id = ?", currentFullhash, module[0])
|
||||
db.exec(sql"delete from deps where module = ?", module[0])
|
||||
db.exec(sql"delete from types where module = ?", module[0])
|
||||
@@ -296,7 +298,7 @@ proc encodeSym(g: ModuleGraph, s: PSym, result: var string) =
|
||||
pushSym(w, s.owner)
|
||||
if s.flags != {}:
|
||||
result.add('$')
|
||||
encodeVInt(cast[int32](s.flags), result)
|
||||
encodeVBiggestInt(cast[int64](s.flags), result)
|
||||
if s.magic != mNone:
|
||||
result.add('@')
|
||||
encodeVInt(ord(s.magic), result)
|
||||
@@ -723,7 +725,7 @@ proc loadSymFromBlob(g; b; info: TLineInfo): PSym =
|
||||
result.owner = loadSym(g, decodeVInt(b.s, b.pos), result.info)
|
||||
if b.s[b.pos] == '$':
|
||||
inc(b.pos)
|
||||
result.flags = cast[TSymFlags](int32(decodeVInt(b.s, b.pos)))
|
||||
result.flags = cast[TSymFlags](decodeVBiggestInt(b.s, b.pos))
|
||||
if b.s[b.pos] == '@':
|
||||
inc(b.pos)
|
||||
result.magic = TMagic(decodeVInt(b.s, b.pos))
|
||||
@@ -756,6 +758,7 @@ proc loadSymFromBlob(g; b; info: TLineInfo): PSym =
|
||||
if b.s[b.pos] == '\24':
|
||||
inc b.pos
|
||||
result.transformedBody = decodeNode(g, b, result.info)
|
||||
#result.transformedBody = nil
|
||||
of skModule, skPackage:
|
||||
decodeInstantiations(g, b, result.info, result.usedGenerics)
|
||||
of skLet, skVar, skField, skForVar:
|
||||
@@ -769,7 +772,7 @@ proc loadSymFromBlob(g; b; info: TLineInfo): PSym =
|
||||
|
||||
if b.s[b.pos] == '(':
|
||||
#if result.kind in routineKinds:
|
||||
# result.ast = decodeNodeLazyBody(b, result.info, result)
|
||||
# result.ast = nil
|
||||
#else:
|
||||
result.ast = decodeNode(g, b, result.info)
|
||||
if sfCompilerProc in result.flags:
|
||||
@@ -886,6 +889,10 @@ proc replay(g: ModuleGraph; module: PSym; n: PNode) =
|
||||
internalAssert g.config, imported.id < 0
|
||||
of nkStmtList, nkStmtListExpr:
|
||||
for x in n: replay(g, module, x)
|
||||
of nkExportStmt:
|
||||
for x in n:
|
||||
doAssert x.kind == nkSym
|
||||
strTableAdd(module.tab, x.sym)
|
||||
else: discard "nothing to do for this node"
|
||||
|
||||
proc loadNode*(g: ModuleGraph; module: PSym): PNode =
|
||||
|
||||
@@ -46,14 +46,9 @@ proc toStrMaxPrecision*(f: BiggestFloat, literalPostfix = ""): string =
|
||||
of fcNegInf:
|
||||
result = "-INF"
|
||||
else:
|
||||
when defined(nimNoArrayToCstringConversion):
|
||||
result = newString(81)
|
||||
let n = c_snprintf(result.cstring, result.len.uint, "%#.16e%s", f, literalPostfix.cstring)
|
||||
setLen(result, n)
|
||||
else:
|
||||
var buf: array[0..80, char]
|
||||
discard c_snprintf(buf.cstring, buf.len.uint, "%#.16e%s", f, literalPostfix.cstring)
|
||||
result = $buf.cstring
|
||||
result = newString(81)
|
||||
let n = c_snprintf(result.cstring, result.len.uint, "%#.16e%s", f, literalPostfix.cstring)
|
||||
setLen(result, n)
|
||||
|
||||
proc encodeStr*(s: string, result: var string) =
|
||||
for i in 0 ..< len(s):
|
||||
|
||||
@@ -95,8 +95,8 @@ and code execution in the executable.
|
||||
|
||||
The compiler parses Nim source code into an internal data structure called the
|
||||
`abstract syntax tree`:idx: (`AST`:idx:). Then, before executing the code or
|
||||
compiling it into the executable, it transforms the AST through `semantic
|
||||
analysis`:idx:. This adds semantic information such as expression types,
|
||||
compiling it into the executable, it transforms the AST through
|
||||
`semantic analysis`:idx:. This adds semantic information such as expression types,
|
||||
identifier meanings, and in some cases expression values. An error detected
|
||||
during semantic analysis is called a `static error`:idx:. Errors described in
|
||||
this manual are static errors when not otherwise specified.
|
||||
|
||||
2
koch.nim
2
koch.nim
@@ -10,7 +10,7 @@
|
||||
#
|
||||
|
||||
const
|
||||
NimbleStableCommit = "d15c8530cb7480ce39ffa85a2dd9819d2d4fc645" # 0.10.2
|
||||
NimbleStableCommit = "da82e3111e662fc1b12f96b3cddd66c749c0f686" # master
|
||||
|
||||
when defined(gcc) and defined(windows):
|
||||
when defined(x86):
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
## Include for the tester that contains test suites that test special features
|
||||
## of the compiler.
|
||||
|
||||
# included from tester.nim
|
||||
|
||||
import important_packages
|
||||
import sequtils
|
||||
|
||||
const
|
||||
specialCategories = [
|
||||
@@ -24,6 +25,7 @@ const
|
||||
"gc",
|
||||
"io",
|
||||
"js",
|
||||
"ic",
|
||||
"lib",
|
||||
"longgc",
|
||||
"manyloc",
|
||||
@@ -41,58 +43,52 @@ const
|
||||
"dir with space"
|
||||
]
|
||||
|
||||
# included from tester.nim
|
||||
# ---------------- ROD file tests ---------------------------------------------
|
||||
proc isTestFile*(file: string): bool =
|
||||
let (_, name, ext) = splitFile(file)
|
||||
result = ext == ".nim" and name.startsWith("t")
|
||||
|
||||
const
|
||||
rodfilesDir = "tests/rodfiles"
|
||||
# ---------------- IC tests ---------------------------------------------
|
||||
|
||||
proc delNimCache(filename, options: string) =
|
||||
for target in low(TTarget)..high(TTarget):
|
||||
let dir = nimcacheDir(filename, options, target)
|
||||
try:
|
||||
removeDir(dir)
|
||||
except OSError:
|
||||
echo "[Warning] could not delete: ", dir
|
||||
proc icTests(r: var TResults; testsDir: string, cat: Category, options: string) =
|
||||
const
|
||||
tooltests = ["compiler/nim.nim", "tools/nimgrep.nim"]
|
||||
writeOnly = " --incremental:writeonly "
|
||||
readOnly = " --incremental:readonly "
|
||||
incrementalOn = " --incremental:on "
|
||||
|
||||
proc runRodFiles(r: var TResults, cat: Category, options: string) =
|
||||
template test(filename: string, clearCacheFirst=false) =
|
||||
if clearCacheFirst: delNimCache(filename, options)
|
||||
testSpec r, makeTest(rodfilesDir / filename, options, cat)
|
||||
template test(x: untyped) =
|
||||
testSpecWithNimcache(r, makeRawTest(file, x & options, cat), nimcache)
|
||||
|
||||
template editedTest(x: untyped) =
|
||||
var test = makeTest(file, x & options, cat)
|
||||
test.spec.targets = {getTestSpecTarget()}
|
||||
testSpecWithNimcache(r, test, nimcache)
|
||||
|
||||
# test basic recompilation scheme:
|
||||
test "hallo", true
|
||||
test "hallo"
|
||||
when false:
|
||||
# test incremental type information:
|
||||
test "hallo2"
|
||||
const tempExt = "_temp.nim"
|
||||
for it in walkDirRec(testsDir / "ic"):
|
||||
if isTestFile(it) and not it.endsWith(tempExt):
|
||||
let nimcache = nimcacheDir(it, options, getTestSpecTarget())
|
||||
removeDir(nimcache)
|
||||
|
||||
# test type converters:
|
||||
test "aconv", true
|
||||
test "bconv"
|
||||
let content = readFile(it)
|
||||
for fragment in content.split("#!EDIT!#"):
|
||||
let file = it.replace(".nim", tempExt)
|
||||
writeFile(file, fragment)
|
||||
let oldPassed = r.passed
|
||||
editedTest incrementalOn
|
||||
if r.passed != oldPassed+1: break
|
||||
|
||||
# test G, A, B example from the documentation; test init sections:
|
||||
test "deada", true
|
||||
test "deada2"
|
||||
for file in tooltests:
|
||||
let nimcache = nimcacheDir(file, options, getTestSpecTarget())
|
||||
removeDir(nimcache)
|
||||
|
||||
when false:
|
||||
# test method generation:
|
||||
test "bmethods", true
|
||||
test "bmethods2"
|
||||
let oldPassed = r.passed
|
||||
test writeOnly
|
||||
|
||||
# test generics:
|
||||
test "tgeneric1", true
|
||||
test "tgeneric2"
|
||||
|
||||
proc compileRodFiles(r: var TResults, cat: Category, options: string) =
|
||||
template test(filename: untyped, clearCacheFirst=true) =
|
||||
if clearCacheFirst: delNimCache(filename, options)
|
||||
testSpec r, makeTest(rodfilesDir / filename, options, cat)
|
||||
|
||||
# test DLL interfacing:
|
||||
test "gtkex1", true
|
||||
test "gtkex2"
|
||||
if r.passed == oldPassed+1:
|
||||
test readOnly
|
||||
if r.passed == oldPassed+2:
|
||||
test readOnly & "-d:nimBackendAssumesChange "
|
||||
|
||||
# --------------------- flags tests -------------------------------------------
|
||||
|
||||
@@ -116,12 +112,6 @@ proc flagTests(r: var TResults, cat: Category, options: string) =
|
||||
|
||||
# --------------------- DLL generation tests ----------------------------------
|
||||
|
||||
proc safeCopyFile(src, dest: string) =
|
||||
try:
|
||||
copyFile(src, dest)
|
||||
except OSError:
|
||||
echo "[Warning] could not copy: ", src, " to ", dest
|
||||
|
||||
proc runBasicDLLTest(c, r: var TResults, cat: Category, options: string) =
|
||||
const rpath = when defined(macosx):
|
||||
" --passL:-rpath --passL:@loader_path"
|
||||
@@ -153,11 +143,12 @@ proc runBasicDLLTest(c, r: var TResults, cat: Category, options: string) =
|
||||
|
||||
if "boehm" notin options:
|
||||
# force build required - see the comments in the .nim file for more details
|
||||
var hcr_integration = makeTest("tests/dll/nimhcr_integration.nim",
|
||||
var hcri = makeTest("tests/dll/nimhcr_integration.nim",
|
||||
options & " --forceBuild --hotCodeReloading:on" & rpath, cat)
|
||||
hcr_integration.args = prepareTestArgs(hcr_integration.spec.getCmd, hcr_integration.name,
|
||||
hcr_integration.options, getTestSpecTarget())
|
||||
testSpec r, hcr_integration
|
||||
let nimcache = nimcacheDir(hcri.name, hcri.options, getTestSpecTarget())
|
||||
hcri.args = prepareTestArgs(hcri.spec.getCmd, hcri.name,
|
||||
hcri.options, nimcache, getTestSpecTarget())
|
||||
testSpec r, hcri
|
||||
|
||||
proc dllTests(r: var TResults, cat: Category, options: string) =
|
||||
# dummy compile result:
|
||||
@@ -363,7 +354,8 @@ proc testNimInAction(r: var TResults, cat: Category, options: string) =
|
||||
for i, test in tests:
|
||||
let filename = testsDir / test.addFileExt("nim")
|
||||
let testHash = getMD5(readFile(filename).string)
|
||||
doAssert testHash == refHashes[i], "Nim in Action test " & filename & " was changed: " & $(i: i, testHash: testHash, refHash: refHashes[i])
|
||||
doAssert testHash == refHashes[i], "Nim in Action test " & filename &
|
||||
" was changed: " & $(i: i, testHash: testHash, refHash: refHashes[i])
|
||||
# Run the tests.
|
||||
for testfile in tests:
|
||||
test "tests/" & testfile & ".nim"
|
||||
@@ -449,25 +441,6 @@ let
|
||||
nimbleExe = findExe("nimble")
|
||||
packageIndex = nimbleDir / "packages_official.json"
|
||||
|
||||
proc waitForExitEx(p: Process): int =
|
||||
var outp = outputStream(p)
|
||||
var line = newStringOfCap(120).TaintedString
|
||||
while true:
|
||||
if outp.readLine(line):
|
||||
discard
|
||||
else:
|
||||
result = peekExitCode(p)
|
||||
if result != -1: break
|
||||
close(p)
|
||||
|
||||
proc getPackageDir(package: string): string =
|
||||
## TODO - Replace this with dom's version comparison magic.
|
||||
let commandOutput = execCmdEx("nimble path $#" % package)
|
||||
if commandOutput.exitCode != QuitSuccess:
|
||||
return ""
|
||||
else:
|
||||
result = commandOutput[0].string
|
||||
|
||||
iterator listPackages(): tuple[name, url, cmd: string, hasDeps: bool] =
|
||||
let defaultCmd = "nimble test"
|
||||
let packageList = parseFile(packageIndex)
|
||||
@@ -481,8 +454,8 @@ iterator listPackages(): tuple[name, url, cmd: string, hasDeps: bool] =
|
||||
let name = package["name"].str
|
||||
if name == n:
|
||||
found = true
|
||||
let p_url = package["url"].str
|
||||
yield (name, p_url, cmd, hasDeps)
|
||||
let pUrl = package["url"].str
|
||||
yield (name, pUrl, cmd, hasDeps)
|
||||
break
|
||||
if not found:
|
||||
raise newException(ValueError, "Cannot find package '$#'." % n)
|
||||
@@ -551,7 +524,7 @@ proc testNimblePackages(r: var TResults, cat: Category) =
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
const AdditionalCategories = ["debugger", "examples", "lib"]
|
||||
const AdditionalCategories = ["debugger", "examples", "lib", "ic"]
|
||||
const MegaTestCat = "megatest"
|
||||
|
||||
proc `&.?`(a, b: string): string =
|
||||
@@ -584,17 +557,12 @@ proc isJoinableSpec(spec: TSpec): bool =
|
||||
(spec.targets == {} or spec.targets == {targetC})
|
||||
|
||||
proc norm(s: var string) =
|
||||
# equivalent of s/\n+/\n/g (could use a single pass over input if needed)
|
||||
while true:
|
||||
let tmp = s.replace("\n\n", "\n")
|
||||
if tmp == s: break
|
||||
s = tmp
|
||||
s = s.strip
|
||||
|
||||
proc isTestFile*(file: string): bool =
|
||||
let (_, name, ext) = splitFile(file)
|
||||
result = ext == ".nim" and name.startsWith("t")
|
||||
|
||||
proc quoted(a: string): string =
|
||||
# todo: consider moving to system.nim
|
||||
result.addQuoted(a)
|
||||
@@ -607,10 +575,10 @@ proc runJoinedTest(r: var TResults, cat: Category, testsDir: string) =
|
||||
let cat = dir[testsDir.len .. ^1]
|
||||
if kind == pcDir and cat notin specialCategories:
|
||||
for file in walkDirRec(testsDir / cat):
|
||||
if not isTestFile(file): continue
|
||||
let spec = parseSpec(file)
|
||||
if isJoinableSpec(spec):
|
||||
specs.add spec
|
||||
if isTestFile(file):
|
||||
let spec = parseSpec(file)
|
||||
if isJoinableSpec(spec):
|
||||
specs.add spec
|
||||
|
||||
proc cmp(a: TSpec, b:TSpec): auto = cmp(a.file, b.file)
|
||||
sort(specs, cmp=cmp) # reproducible order
|
||||
@@ -646,14 +614,13 @@ proc runJoinedTest(r: var TResults, cat: Category, testsDir: string) =
|
||||
|
||||
let args = ["c", "--nimCache:" & outDir, "-d:testing", "--listCmd",
|
||||
"--listFullPaths:off", "--excessiveStackTrace:off", "megatest.nim"]
|
||||
proc onStdout(line: string) = echo line
|
||||
|
||||
var (cmdLine, buf, exitCode) = execCmdEx2(command = compilerPrefix, args = args, input = "")
|
||||
if exitCode != 0:
|
||||
echo "$ ", cmdLine
|
||||
echo buf.string
|
||||
quit("megatest compilation failed")
|
||||
|
||||
# Could also use onStdout here.
|
||||
(buf, exitCode) = execCmdEx("./megatest")
|
||||
if exitCode != 0:
|
||||
echo buf.string
|
||||
@@ -690,6 +657,8 @@ proc processCategory(r: var TResults, cat: Category,
|
||||
when false:
|
||||
compileRodFiles(r, cat, options)
|
||||
runRodFiles(r, cat, options)
|
||||
of "ic":
|
||||
icTests(r, testsDir, cat, options)
|
||||
of "js":
|
||||
# only run the JS tests on Windows or Linux because Travis is bad
|
||||
# and other OSes like Haiku might lack nodejs:
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
## HTML generator for the tester.
|
||||
|
||||
import cgi, backend, strutils, json, os, tables, times
|
||||
import strutils, json, os, times
|
||||
|
||||
import "testamenthtml.nimf"
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import strutils
|
||||
|
||||
template pkg(name: string; cmd = "nimble test"; hasDeps = false; url = ""): untyped =
|
||||
packages.add((name, cmd, hasDeps, url))
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
import sequtils, parseutils, strutils, os, osproc, streams, parsecfg
|
||||
import sequtils, parseutils, strutils, os, streams, parsecfg
|
||||
|
||||
var compilerPrefix* = findExe("nim")
|
||||
|
||||
@@ -122,6 +122,9 @@ proc addLine*(self: var string; a,b: string) =
|
||||
self.add b
|
||||
self.add "\n"
|
||||
|
||||
proc initSpec*(filename: string): TSpec =
|
||||
result.file = filename
|
||||
|
||||
proc parseSpec*(filename: string): TSpec =
|
||||
result.file = filename
|
||||
let specStr = extractSpec(filename)
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
## This program verifies Nim against the testcases.
|
||||
|
||||
import
|
||||
parseutils, strutils, pegs, os, osproc, streams, parsecfg, json,
|
||||
marshal, backend, parseopt, specs, htmlgen, browsers, terminal,
|
||||
algorithm, times, sets, md5, sequtils
|
||||
strutils, pegs, os, osproc, streams, json,
|
||||
backend, parseopt, specs, htmlgen, browsers, terminal,
|
||||
algorithm, times, md5, sequtils
|
||||
|
||||
include compiler/nodejs
|
||||
|
||||
@@ -124,19 +124,20 @@ proc execCmdEx2(command: string, args: openarray[string]; workingDir, input: str
|
||||
proc nimcacheDir(filename, options: string, target: TTarget): string =
|
||||
## Give each test a private nimcache dir so they don't clobber each other's.
|
||||
let hashInput = options & $target
|
||||
return "nimcache" / (filename & '_' & hashInput.getMD5)
|
||||
result = "nimcache" / (filename & '_' & hashInput.getMD5)
|
||||
|
||||
proc prepareTestArgs(cmdTemplate, filename, options: string,
|
||||
proc prepareTestArgs(cmdTemplate, filename, options, nimcache: string,
|
||||
target: TTarget, extraOptions=""): seq[string] =
|
||||
let nimcache = nimcacheDir(filename, options, target)
|
||||
let options = options & " " & quoteShell("--nimCache:" & nimcache) & extraOptions
|
||||
return parseCmdLine(cmdTemplate % ["target", targetToCmd[target],
|
||||
result = parseCmdLine(cmdTemplate % ["target", targetToCmd[target],
|
||||
"options", options, "file", filename.quoteShell,
|
||||
"filedir", filename.getFileDir()])
|
||||
|
||||
proc callCompiler(cmdTemplate, filename, options: string,
|
||||
target: TTarget, extraOptions=""): TSpec =
|
||||
let c = prepareTestArgs(cmdTemplate, filename, options, target, extraOptions)
|
||||
proc callCompiler(cmdTemplate, filename, options, nimcache: string,
|
||||
target: TTarget,
|
||||
extraOptions=""): TSpec =
|
||||
let c = prepareTestArgs(cmdTemplate, filename, options, nimcache, target,
|
||||
extraOptions)
|
||||
result.cmd = quoteShellCommand(c)
|
||||
var p = startProcess(command=c[0], args=c[1 .. ^1],
|
||||
options={poStdErrToStdOut, poUsePath})
|
||||
@@ -370,7 +371,7 @@ proc nimoutCheck(test: TTest; expectedNimout: string; given: var TSpec) =
|
||||
currentPos = giv.find(line.strip, currentPos)
|
||||
if currentPos < 0:
|
||||
given.err = reMsgsDiffer
|
||||
return
|
||||
break
|
||||
|
||||
proc compilerOutputTests(test: TTest, target: TTarget, given: var TSpec,
|
||||
expected: TSpec; r: var TResults) =
|
||||
@@ -391,9 +392,9 @@ proc compilerOutputTests(test: TTest, target: TTarget, given: var TSpec,
|
||||
|
||||
proc getTestSpecTarget(): TTarget =
|
||||
if getEnv("NIM_COMPILE_TO_CPP", "false").string == "true":
|
||||
return targetCpp
|
||||
result = targetCpp
|
||||
else:
|
||||
return targetC
|
||||
result = targetC
|
||||
|
||||
proc checkDisabled(r: var TResults, test: TTest): bool =
|
||||
if test.spec.err in {reDisabled, reJoined}:
|
||||
@@ -401,13 +402,72 @@ proc checkDisabled(r: var TResults, test: TTest): bool =
|
||||
r.addResult(test, targetC, "", "", test.spec.err)
|
||||
inc(r.skipped)
|
||||
inc(r.total)
|
||||
return
|
||||
true
|
||||
result = false
|
||||
else:
|
||||
result = true
|
||||
|
||||
var count = 0
|
||||
|
||||
proc testSpecHelper(r: var TResults, test: TTest, expected: TSpec, target: TTarget, nimcache: string) =
|
||||
case expected.action
|
||||
of actionCompile:
|
||||
var given = callCompiler(expected.getCmd, test.name, test.options, nimcache, target,
|
||||
extraOptions = " --stdout --hint[Path]:off --hint[Processing]:off")
|
||||
compilerOutputTests(test, target, given, expected, r)
|
||||
of actionRun:
|
||||
var given = callCompiler(expected.getCmd, test.name, test.options, nimcache, target)
|
||||
if given.err != reSuccess:
|
||||
r.addResult(test, target, "", "$ " & given.cmd & "\n" & given.nimout, given.err)
|
||||
else:
|
||||
let isJsTarget = target == targetJS
|
||||
var exeFile = changeFileExt(test.name, if isJsTarget: "js" else: ExeExt)
|
||||
if not existsFile(exeFile):
|
||||
r.addResult(test, target, expected.output,
|
||||
"executable not found: " & exeFile, reExeNotFound)
|
||||
else:
|
||||
let nodejs = if isJsTarget: findNodeJs() else: ""
|
||||
if isJsTarget and nodejs == "":
|
||||
r.addResult(test, target, expected.output, "nodejs binary not in PATH",
|
||||
reExeNotFound)
|
||||
else:
|
||||
var exeCmd: string
|
||||
var args = test.args
|
||||
if isJsTarget:
|
||||
exeCmd = nodejs
|
||||
args = concat(@[exeFile], args)
|
||||
else:
|
||||
exeCmd = exeFile
|
||||
var (_, buf, exitCode) = execCmdEx2(exeCmd, args, input = expected.input)
|
||||
# Treat all failure codes from nodejs as 1. Older versions of nodejs used
|
||||
# to return other codes, but for us it is sufficient to know that it's not 0.
|
||||
if exitCode != 0: exitCode = 1
|
||||
let bufB =
|
||||
if expected.sortoutput:
|
||||
var x = splitLines(strip(buf.string))
|
||||
sort(x, system.cmp)
|
||||
join(x, "\n")
|
||||
else:
|
||||
strip(buf.string)
|
||||
if exitCode != expected.exitCode:
|
||||
r.addResult(test, target, "exitcode: " & $expected.exitCode,
|
||||
"exitcode: " & $exitCode & "\n\nOutput:\n" &
|
||||
bufB, reExitCodesDiffer)
|
||||
elif (expected.outputCheck == ocEqual and expected.output != bufB) or
|
||||
(expected.outputCheck == ocSubstr and expected.output notin bufB):
|
||||
given.err = reOutputsDiffer
|
||||
r.addResult(test, target, expected.output, bufB, reOutputsDiffer)
|
||||
else:
|
||||
compilerOutputTests(test, target, given, expected, r)
|
||||
of actionReject:
|
||||
var given = callCompiler(expected.getCmd, test.name, test.options,
|
||||
nimcache, target)
|
||||
cmpMsgs(r, expected, given, test, target)
|
||||
|
||||
|
||||
proc testSpec(r: var TResults, test: TTest, targets: set[TTarget] = {}) =
|
||||
var expected = test.spec
|
||||
if expected.parseErrors.len > 0:
|
||||
# targetC is a lie, but parameter is required
|
||||
# targetC is a lie, but a parameter is required
|
||||
r.addResult(test, targetC, "", expected.parseErrors, reInvalidSpec)
|
||||
inc(r.total)
|
||||
return
|
||||
@@ -422,73 +482,18 @@ proc testSpec(r: var TResults, test: TTest, targets: set[TTarget] = {}) =
|
||||
if target notin gTargets:
|
||||
r.addResult(test, target, "", "", reDisabled)
|
||||
inc(r.skipped)
|
||||
continue
|
||||
|
||||
if simulate:
|
||||
var count {.global.} = 0
|
||||
count.inc
|
||||
elif simulate:
|
||||
inc count
|
||||
echo "testSpec count: ", count, " expected: ", expected
|
||||
continue
|
||||
else:
|
||||
let nimcache = nimcacheDir(test.name, test.options, target)
|
||||
testSpecHelper(r, test, expected, target, nimcache)
|
||||
|
||||
case expected.action
|
||||
of actionCompile:
|
||||
var given = callCompiler(expected.getCmd, test.name, test.options, target,
|
||||
extraOptions=" --stdout --hint[Path]:off --hint[Processing]:off")
|
||||
compilerOutputTests(test, target, given, expected, r)
|
||||
of actionRun:
|
||||
# In this branch of code "early return" pattern is clearer than deep
|
||||
# nested conditionals - the empty rows in between to clarify the "danger"
|
||||
var given = callCompiler(expected.getCmd, test.name, test.options, target)
|
||||
if given.err != reSuccess:
|
||||
r.addResult(test, target, "", "$ " & given.cmd & "\n" & given.nimout, given.err)
|
||||
continue
|
||||
let isJsTarget = target == targetJS
|
||||
var exeFile = changeFileExt(test.name, if isJsTarget: "js" else: ExeExt)
|
||||
if not existsFile(exeFile):
|
||||
r.addResult(test, target, expected.output,
|
||||
"executable not found: " & exeFile, reExeNotFound)
|
||||
continue
|
||||
|
||||
let nodejs = if isJsTarget: findNodeJs() else: ""
|
||||
if isJsTarget and nodejs == "":
|
||||
r.addResult(test, target, expected.output, "nodejs binary not in PATH",
|
||||
reExeNotFound)
|
||||
continue
|
||||
var exeCmd: string
|
||||
var args = test.args
|
||||
if isJsTarget:
|
||||
exeCmd = nodejs
|
||||
args = concat(@[exeFile], args)
|
||||
else:
|
||||
exeCmd = exeFile
|
||||
var (cmdLine, buf, exitCode) = execCmdEx2(exeCmd, args, input = expected.input)
|
||||
# Treat all failure codes from nodejs as 1. Older versions of nodejs used
|
||||
# to return other codes, but for us it is sufficient to know that it's not 0.
|
||||
if exitCode != 0: exitCode = 1
|
||||
let bufB =
|
||||
if expected.sortoutput:
|
||||
var x = splitLines(strip(buf.string))
|
||||
sort(x, system.cmp)
|
||||
join(x, "\n")
|
||||
else:
|
||||
strip(buf.string)
|
||||
if exitCode != expected.exitCode:
|
||||
r.addResult(test, target, "exitcode: " & $expected.exitCode,
|
||||
"exitcode: " & $exitCode & "\n\nOutput:\n" &
|
||||
bufB, reExitCodesDiffer)
|
||||
continue
|
||||
if (expected.outputCheck == ocEqual and expected.output != bufB) or
|
||||
(expected.outputCheck == ocSubstr and expected.output notin bufB):
|
||||
given.err = reOutputsDiffer
|
||||
r.addResult(test, target, expected.output, bufB, reOutputsDiffer)
|
||||
continue
|
||||
compilerOutputTests(test, target, given, expected, r)
|
||||
continue
|
||||
of actionReject:
|
||||
var given = callCompiler(expected.getCmd, test.name, test.options,
|
||||
target)
|
||||
cmpMsgs(r, expected, given, test, target)
|
||||
continue
|
||||
proc testSpecWithNimcache(r: var TResults, test: TTest; nimcache: string) =
|
||||
if not checkDisabled(r, test): return
|
||||
for target in test.spec.targets:
|
||||
inc(r.total)
|
||||
testSpecHelper(r, test, test.spec, target, nimcache)
|
||||
|
||||
proc testC(r: var TResults, test: TTest, action: TTestAction) =
|
||||
# runs C code. Doesn't support any specs, just goes by exit code.
|
||||
@@ -529,6 +534,15 @@ proc makeTest(test, options: string, cat: Category): TTest =
|
||||
result.spec = parseSpec(addFileExt(test, ".nim"))
|
||||
result.startTime = epochTime()
|
||||
|
||||
proc makeRawTest(test, options: string, cat: Category): TTest =
|
||||
result.cat = cat
|
||||
result.name = test
|
||||
result.options = options
|
||||
result.spec = initSpec(addFileExt(test, ".nim"))
|
||||
result.startTime = epochTime()
|
||||
result.spec.action = actionCompile
|
||||
result.spec.targets = {getTestSpecTarget()}
|
||||
|
||||
# TODO: fix these files
|
||||
const disabledFilesDefault = @[
|
||||
"LockFreeHash.nim",
|
||||
@@ -553,23 +567,18 @@ when defined(windows):
|
||||
else:
|
||||
const
|
||||
# array of modules disabled from compilation test of stdlib.
|
||||
# TODO: why the ["-"]? (previous code should've prob used seq[string] = @[] instead)
|
||||
disabledFiles = disabledFilesDefault & @["-"]
|
||||
disabledFiles = disabledFilesDefault
|
||||
|
||||
include categories
|
||||
|
||||
proc loadSkipFrom(name: string): seq[string] =
|
||||
if name.len() == 0: return
|
||||
|
||||
if name.len == 0: return
|
||||
# One skip per line, comments start with #
|
||||
# used by `nlvm` (at least)
|
||||
try:
|
||||
for line in lines(name):
|
||||
let sline = line.strip()
|
||||
if sline.len > 0 and not sline.startsWith("#"):
|
||||
result.add sline
|
||||
except:
|
||||
echo "Could not load " & name & ", ignoring"
|
||||
for line in lines(name):
|
||||
let sline = line.strip()
|
||||
if sline.len > 0 and not sline.startsWith("#"):
|
||||
result.add sline
|
||||
|
||||
proc main() =
|
||||
os.putenv "NIMTEST_COLOR", "never"
|
||||
|
||||
39
tests/ic/tgenerics.nim
Normal file
39
tests/ic/tgenerics.nim
Normal file
@@ -0,0 +1,39 @@
|
||||
discard """
|
||||
output: "bar"
|
||||
disabled: "true"
|
||||
"""
|
||||
|
||||
import tables
|
||||
|
||||
var tab: Table[string, string]
|
||||
|
||||
tab["foo"] = "bar"
|
||||
echo tab["foo"]
|
||||
|
||||
#!EDIT!#
|
||||
|
||||
discard """
|
||||
output: "bar 3"
|
||||
"""
|
||||
|
||||
import tables
|
||||
|
||||
var tab: Table[string, string]
|
||||
var tab2: Table[string, int]
|
||||
|
||||
tab["foo"] = "bar"
|
||||
tab2["meh"] = 3
|
||||
echo tab["foo"], " ", tab2["meh"]
|
||||
|
||||
#!EDIT!#
|
||||
|
||||
discard """
|
||||
output: "3"
|
||||
"""
|
||||
|
||||
import tables
|
||||
|
||||
var tab2: Table[string, int]
|
||||
|
||||
tab2["meh"] = 3
|
||||
echo tab2["meh"]
|
||||
28
tests/ic/thallo.nim
Normal file
28
tests/ic/thallo.nim
Normal file
@@ -0,0 +1,28 @@
|
||||
discard """
|
||||
output: "Hello World"
|
||||
"""
|
||||
|
||||
const str = "Hello World"
|
||||
echo str
|
||||
|
||||
# Splitters are done with this special comment:
|
||||
|
||||
#!EDIT!#
|
||||
|
||||
discard """
|
||||
output: "Hello World B"
|
||||
"""
|
||||
|
||||
const str = "Hello World"
|
||||
echo str, " B"
|
||||
|
||||
#!EDIT!#
|
||||
|
||||
discard """
|
||||
output: "Hello World C"
|
||||
"""
|
||||
|
||||
const str = "Hello World"
|
||||
var x = 7
|
||||
if 3+4 == x:
|
||||
echo str, " C"
|
||||
Reference in New Issue
Block a user