implemented new experimental scriptable import mechanism

This commit is contained in:
Andreas Rumpf 2017-10-01 23:34:19 +02:00
commit 02ff5f596c
8 changed files with 297 additions and 64 deletions

83
compiler/gorgeimpl.nim Normal file
View file

@ -0,0 +1,83 @@
#
#
# The Nim Compiler
# (c) Copyright 2017 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Module that implements ``gorge`` for the compiler as well as
## the scriptable import mechanism.
import msgs, securehash, os, osproc, streams, strutils, options
proc readOutput(p: Process): (string, int) =
result[0] = ""
var output = p.outputStream
while not output.atEnd:
result[0].add(output.readLine)
result[0].add("\n")
if result[0].len > 0:
result[0].setLen(result[0].len - "\n".len)
result[1] = p.waitForExit
proc opGorge*(cmd, input, cache: string, info: TLineInfo): (string, int) =
let workingDir = parentDir(info.toFullPath)
if cache.len > 0:# and optForceFullMake notin gGlobalOptions:
let h = secureHash(cmd & "\t" & input & "\t" & cache)
let filename = options.toGeneratedFile("gorge_" & $h, "txt")
var f: File
if open(f, filename):
result = (f.readAll, 0)
f.close
return
var readSuccessful = false
try:
var p = startProcess(cmd, workingDir,
options={poEvalCommand, poStderrToStdout})
if input.len != 0:
p.inputStream.write(input)
p.inputStream.close()
result = p.readOutput
readSuccessful = true
# only cache successful runs:
if result[1] == 0:
writeFile(filename, result[0])
except IOError, OSError:
if not readSuccessful: result = ("", -1)
else:
try:
var p = startProcess(cmd, workingDir,
options={poEvalCommand, poStderrToStdout})
if input.len != 0:
p.inputStream.write(input)
p.inputStream.close()
result = p.readOutput
except IOError, OSError:
result = ("", -1)
proc scriptableImport*(pkg, subdir: string; info: TLineInfo): string =
var cmd = getConfigVar("resolver.exe")
if cmd.len == 0: cmd = "nimresolve"
else: cmd = quoteShell(cmd)
cmd.add " --source:"
cmd.add quoteShell(info.toFullPath())
cmd.add " --stdlib:"
cmd.add quoteShell(options.libpath)
cmd.add " --project:"
cmd.add quoteShell(gProjectFull)
if subdir.len != 0:
cmd.add " --subdir:"
cmd.add quoteShell(subdir)
if options.gNoNimblePath:
cmd.add " --nonimblepath"
cmd.add ' '
cmd.add quoteShell(pkg)
let (res, exitCode) = opGorge(cmd, "", cmd, info)
if exitCode == 0:
result = res.strip()
elif res.len > 0:
localError(info, res)
else:
localError(info, "cannot resolve: " & (pkg / subdir))

View file

@ -11,11 +11,22 @@
import import
intsets, strutils, os, ast, astalgo, msgs, options, idents, rodread, lookups, intsets, strutils, os, ast, astalgo, msgs, options, idents, rodread, lookups,
semdata, passes, renderer semdata, passes, renderer, gorgeimpl
proc evalImport*(c: PContext, n: PNode): PNode proc evalImport*(c: PContext, n: PNode): PNode
proc evalFrom*(c: PContext, n: PNode): PNode proc evalFrom*(c: PContext, n: PNode): PNode
proc lookupPackage(pkg, subdir: PNode): string =
let sub = if subdir != nil: renderTree(subdir, {renderNoComments}).replace(" ") else: ""
case pkg.kind
of nkStrLit, nkRStrLit, nkTripleStrLit:
result = scriptableImport(pkg.strVal, sub, pkg.info)
of nkIdent:
result = scriptableImport(pkg.ident.s, sub, pkg.info)
else:
localError(pkg.info, "package name must be an identifier or string literal")
result = ""
proc getModuleName*(n: PNode): string = proc getModuleName*(n: PNode): string =
# This returns a short relative module name without the nim extension # This returns a short relative module name without the nim extension
# e.g. like "system", "importer" or "somepath/module" # e.g. like "system", "importer" or "somepath/module"
@ -31,14 +42,31 @@ proc getModuleName*(n: PNode): string =
result = n.ident.s result = n.ident.s
of nkSym: of nkSym:
result = n.sym.name.s result = n.sym.name.s
of nkInfix, nkPrefix: of nkInfix:
if n.sons[0].kind == nkIdent and n.sons[0].ident.id == getIdent("as").id: let n0 = n[0]
let n1 = n[1]
if n0.kind == nkIdent and n0.ident.id == getIdent("as").id:
# XXX hack ahead: # XXX hack ahead:
n.kind = nkImportAs n.kind = nkImportAs
n.sons[0] = n.sons[1] n.sons[0] = n.sons[1]
n.sons[1] = n.sons[2] n.sons[1] = n.sons[2]
n.sons.setLen(2) n.sons.setLen(2)
return getModuleName(n.sons[0]) return getModuleName(n.sons[0])
if n1.kind == nkPrefix and n1[0].kind == nkIdent and n1[0].ident.s == "$":
if n0.kind == nkIdent and n0.ident.s == "/":
result = lookupPackage(n1[1], n[2])
else:
localError(n.info, "only '/' supported with $package notation")
result = ""
else:
# hacky way to implement 'x / y /../ z':
result = getModuleName(n1)
result.add renderTree(n0, {renderNoComments})
result.add getModuleName(n[2])
of nkPrefix:
if n.sons[0].kind == nkIdent and n.sons[0].ident.s == "$":
result = lookupPackage(n[1], nil)
else:
# hacky way to implement 'x / y /../ z': # hacky way to implement 'x / y /../ z':
result = renderTree(n, {renderNoComments}).replace(" ") result = renderTree(n, {renderNoComments}).replace(" ")
of nkDotExpr: of nkDotExpr:
@ -209,12 +237,14 @@ proc evalImport(c: PContext, n: PNode): PNode =
for i in countup(0, sonsLen(n) - 1): for i in countup(0, sonsLen(n) - 1):
let it = n.sons[i] let it = n.sons[i]
if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket: if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket:
let sep = renderTree(it.sons[0], {renderNoComments}) let sep = it[0]
let dir = renderTree(it.sons[1], {renderNoComments}) let dir = it[1]
let a = newNodeI(nkInfix, it.info)
a.add sep
a.add dir
a.add sep # dummy entry, replaced in the loop
for x in it[2]: for x in it[2]:
let f = renderTree(x, {renderNoComments}) a.sons[2] = x
let a = newStrNode(nkStrLit, (dir & sep & f).replace(" "))
a.info = it.info
impMod(c, a) impMod(c, a)
else: else:
impMod(c, it) impMod(c, it)

View file

@ -16,11 +16,11 @@ proc addPath*(path: string, info: TLineInfo) =
options.searchPaths.insert(path, 0) options.searchPaths.insert(path, 0)
type type
Version = distinct string Version* = distinct string
proc `$`(ver: Version): string {.borrow.} proc `$`*(ver: Version): string {.borrow.}
proc newVersion(ver: string): Version = proc newVersion*(ver: string): Version =
doAssert(ver.len == 0 or ver[0] in {'#', '\0'} + Digits, doAssert(ver.len == 0 or ver[0] in {'#', '\0'} + Digits,
"Wrong version: " & ver) "Wrong version: " & ver)
return Version(ver) return Version(ver)
@ -28,7 +28,7 @@ proc newVersion(ver: string): Version =
proc isSpecial(ver: Version): bool = proc isSpecial(ver: Version): bool =
return ($ver).len > 0 and ($ver)[0] == '#' return ($ver).len > 0 and ($ver)[0] == '#'
proc `<`(ver: Version, ver2: Version): bool = proc `<`*(ver: Version, ver2: Version): bool =
## This is synced from Nimble's version module. ## This is synced from Nimble's version module.
# Handling for special versions such as "#head" or "#branch". # Handling for special versions such as "#head" or "#branch".

View file

@ -495,10 +495,12 @@ proc isImportSystemStmt(n: PNode): bool =
case n.kind case n.kind
of nkImportStmt: of nkImportStmt:
for x in n: for x in n:
if x.kind == nkIdent:
let f = checkModuleName(x, false) let f = checkModuleName(x, false)
if f == magicsys.systemModule.info.fileIndex: if f == magicsys.systemModule.info.fileIndex:
return true return true
of nkImportExceptStmt, nkFromStmt: of nkImportExceptStmt, nkFromStmt:
if n[0].kind == nkIdent:
let f = checkModuleName(n[0], false) let f = checkModuleName(n[0], false)
if f == magicsys.systemModule.info.fileIndex: if f == magicsys.systemModule.info.fileIndex:
return true return true

View file

@ -19,7 +19,7 @@ import ast except getstr
import import
strutils, astalgo, msgs, vmdef, vmgen, nimsets, types, passes, strutils, astalgo, msgs, vmdef, vmgen, nimsets, types, passes,
parser, vmdeps, idents, trees, renderer, options, transf, parseutils, parser, vmdeps, idents, trees, renderer, options, transf, parseutils,
vmmarshal vmmarshal, gorgeimpl
from semfold import leValueConv, ordinalValToString from semfold import leValueConv, ordinalValToString
from evaltempl import evalTemplate from evaltempl import evalTemplate

View file

@ -7,50 +7,7 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
import ast, types, msgs, os, osproc, streams, options, idents, securehash import ast, types, msgs, os, streams, options, idents
proc readOutput(p: Process): (string, int) =
result[0] = ""
var output = p.outputStream
while not output.atEnd:
result[0].add(output.readLine)
result[0].add("\n")
if result[0].len > 0:
result[0].setLen(result[0].len - "\n".len)
result[1] = p.waitForExit
proc opGorge*(cmd, input, cache: string, info: TLineInfo): (string, int) =
let workingDir = parentDir(info.toFullPath)
if cache.len > 0:# and optForceFullMake notin gGlobalOptions:
let h = secureHash(cmd & "\t" & input & "\t" & cache)
let filename = options.toGeneratedFile("gorge_" & $h, "txt")
var f: File
if open(f, filename):
result = (f.readAll, 0)
f.close
return
var readSuccessful = false
try:
var p = startProcess(cmd, workingDir,
options={poEvalCommand, poStderrToStdout})
if input.len != 0:
p.inputStream.write(input)
p.inputStream.close()
result = p.readOutput
readSuccessful = true
writeFile(filename, result[0])
except IOError, OSError:
if not readSuccessful: result = ("", -1)
else:
try:
var p = startProcess(cmd, workingDir,
options={poEvalCommand, poStderrToStdout})
if input.len != 0:
p.inputStream.write(input)
p.inputStream.close()
result = p.readOutput
except IOError, OSError:
result = ("", -1)
proc opSlurp*(file: string, info: TLineInfo, module: PSym): string = proc opSlurp*(file: string, info: TLineInfo, module: PSym): string =
try: try:

View file

@ -262,6 +262,9 @@ proc buildTools(latest: bool) =
let nimgrepExe = "bin/nimgrep".exe let nimgrepExe = "bin/nimgrep".exe
nimexec "c -o:" & nimgrepExe & " tools/nimgrep.nim" nimexec "c -o:" & nimgrepExe & " tools/nimgrep.nim"
when defined(windows): buildVccTool() when defined(windows): buildVccTool()
nimexec "c -o:" & ("bin/nimresolve".exe) & " tools/nimresolve.nim"
buildNimble(latest) buildNimble(latest)
proc nsis(args: string) = proc nsis(args: string) =

158
tools/nimresolve.nim Normal file
View file

@ -0,0 +1,158 @@
#
#
# The Nim Compiler
# (c) Copyright 2017 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Standard tool that resolves import paths.
import
os, strutils, parseopt
import "../compiler/nimblecmd"
# You can change these constants to build you own adapted resolver.
const
considerParentDirs = not defined(noParentProjects)
considerNimbleDirs = not defined(noNimbleDirs)
const
Version = "1.0"
Usage = "nimresolve - Nim Resolve Package Path Version " & Version & """
(c) 2017 Andreas Rumpf
Usage:
nimresolve [options] package
Options:
--source:FILE the file that requests to resolve 'package'
--stdlib:PATH the path to use for the standard library
--project:FILE the main '.nim' file that was passed to the Nim compiler
--subdir:EXPR the subdir part in: 'import $pkg / subdir'
--noNimblePath do not search the Nimble path to resolve the package
"""
proc writeHelp() =
stdout.write(Usage)
stdout.flushFile()
quit(0)
proc writeVersion() =
stdout.write(Version & "\n")
stdout.flushFile()
quit(0)
type
Task = object
source, stdlib, subdir, project, pkg: string
noNimblePath: bool
proc findInNimbleDir(t: Task; dir: string): bool =
var best = ""
var bestv = ""
for k, p in os.walkDir(dir, relative=true):
if k == pcDir and p.len > t.pkg.len+1 and
p[t.pkg.len] == '-' and p.startsWith(t.pkg):
let (_, a) = getPathVersion(p)
if bestv.len == 0 or bestv < a:
bestv = a
best = dir / p
if best.len > 0:
var f: File
if open(f, best / changeFileExt(t.pkg, ".nimble-link")):
# the second line contains what we're interested in, see:
# https://github.com/nim-lang/nimble#nimble-link
var override = ""
discard readLine(f, override)
discard readLine(f, override)
close(f)
if not override.isAbsolute():
best = best / override
else:
best = override
let f = if t.subdir.len == 0: t.pkg else: t.subdir
let res = addFileExt(best / f, "nim")
if best.len > 0 and fileExists(res):
echo res
result = true
const stdlibDirs = [
"pure", "core", "arch",
"pure/collections",
"pure/concurrency", "impure",
"wrappers", "wrappers/linenoise",
"windows", "posix", "js"]
proc resolve(t: Task) =
template attempt(a) =
let x = addFileExt(a, "nim")
if fileExists(x):
echo x
return
case t.pkg
of "stdlib":
if t.subdir.len == 0:
echo t.stdlib
return
else:
for candidate in stdlibDirs:
attempt(t.stdlib / candidate / t.subdir)
of "root":
let root = t.project.splitFile.dir
if t.subdir.len == 0:
echo root
return
else:
attempt(root / t.subdir)
else:
when considerParentDirs:
var p = parentDir(t.source.splitFile.dir)
# support 'import $karax':
let f = if t.subdir.len == 0: t.pkg else: t.subdir
while p.len > 0:
let dir = p / t.pkg
if dirExists(dir):
attempt(dir / f)
# 2nd attempt: try to use 'karax/karax'
attempt(dir / t.pkg / f)
# 3rd attempt: try to use 'karax/src/karax'
attempt(dir / "src" / f)
attempt(dir / "src" / t.pkg / f)
p = parentDir(p)
when considerNimbleDirs:
if not t.noNimblePath:
if findInNimbleDir(t, getHomeDir() / ".nimble" / "pkgs"): return
when not defined(windows):
if findInNimbleDir(t, "/opt/nimble/pkgs"): return
quit "cannot resolve: " & (t.pkg / t.subdir)
proc main =
var t: Task
t.subdir = ""
for kind, key, val in getopt():
case kind
of cmdArgument:
t.pkg = key
of cmdLongoption, cmdShortOption:
case normalize(key)
of "source": t.source = val
of "stdlib": t.stdlib = val
of "project": t.project = val
of "subdir": t.subdir = val
of "nonimblepath": t.noNimblePath = true
of "help", "h": writeHelp()
of "version", "v": writeVersion()
else: writeHelp()
of cmdEnd: assert(false) # cannot happen
if t.pkg.len == 0:
quit "[Error] no package to resolve."
resolve(t)
main()