summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2012-12-01 19:10:47 +0100
committerAraq <rumpf_a@web.de>2012-12-01 19:10:47 +0100
commitc98e3d2c274ac4bd4227d4e3f7e0d2636827f88c (patch)
treec9b624562ae38f56d19e7ed190307caea8c61241
parentf503439e811d822f19daa9591cb3bd9e90edabe7 (diff)
downloadNim-c98e3d2c274ac4bd4227d4e3f7e0d2636827f88c.tar.gz
implements 'export' feature
-rwxr-xr-xcompiler/astalgo.nim17
-rwxr-xr-xcompiler/importer.nim71
-rwxr-xr-xcompiler/semexprs.nim23
-rwxr-xr-xdoc/manual.txt31
-rwxr-xr-xlib/pure/collections/intsets.nim5
-rw-r--r--tests/compile/mexporta.nim8
-rw-r--r--tests/compile/mexportb.nim7
-rw-r--r--tests/compile/texport.nim10
-rwxr-xr-xtodo.txt2
-rwxr-xr-xweb/news.txt3
10 files changed, 137 insertions, 40 deletions
diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim
index da0de3e94..9a2bebab4 100755
--- a/compiler/astalgo.nim
+++ b/compiler/astalgo.nim
@@ -585,8 +585,9 @@ proc StrTableContains(t: TStrTable, n: PSym): bool =
 proc StrTableRawInsert(data: var TSymSeq, n: PSym) = 
   var h: THash = n.name.h and high(data)
   while data[h] != nil: 
-    if data[h] == n: 
-      InternalError(n.info, "StrTableRawInsert: " & n.name.s)
+    if data[h] == n:
+      # allowed for 'export' feature:
+      #InternalError(n.info, "StrTableRawInsert: " & n.name.s)
       return
     h = nextTry(h, high(data))
   assert(data[h] == nil)
@@ -617,23 +618,23 @@ proc StrTableAdd(t: var TStrTable, n: PSym) =
   StrTableRawInsert(t.data, n)
   inc(t.counter)
 
-proc StrTableIncl*(t: var TStrTable, n: PSym): bool = 
+proc StrTableIncl*(t: var TStrTable, n: PSym): bool {.discardable.} =
   # returns true if n is already in the string table:
   # It is essential that `n` is written nevertheless!
   # This way the newest redefinition is picked by the semantic analyses!
   assert n.name != nil
   var h: THash = n.name.h and high(t.data)
-  while true: 
+  while true:
     var it = t.data[h]
-    if it == nil: break 
-    if it.name.id == n.name.id: 
+    if it == nil: break
+    if it.name.id == n.name.id:
       t.data[h] = n           # overwrite it with newer definition!
       return true             # found it
     h = nextTry(h, high(t.data))
-  if mustRehash(len(t.data), t.counter): 
+  if mustRehash(len(t.data), t.counter):
     StrTableEnlarge(t)
     StrTableRawInsert(t.data, n)
-  else: 
+  else:
     assert(t.data[h] == nil)
     t.data[h] = n
   inc(t.counter)
diff --git a/compiler/importer.nim b/compiler/importer.nim
index 11fc45601..774bcea6a 100755
--- a/compiler/importer.nim
+++ b/compiler/importer.nim
@@ -15,7 +15,6 @@ import
 
 proc evalImport*(c: PContext, n: PNode): PNode
 proc evalFrom*(c: PContext, n: PNode): PNode
-proc importAllSymbols*(c: PContext, fromMod: PSym)
 
 proc getModuleName*(n: PNode): string =
   # This returns a short relative module name without the nim extension
@@ -42,42 +41,43 @@ proc checkModuleName*(n: PNode): string =
   if result.len == 0:
     LocalError(n.info, errCannotOpenFile, modulename)
 
-proc rawImportSymbol(c: PContext, s: PSym) = 
+proc rawImportSymbol(c: PContext, s: PSym) =
   # This does not handle stubs, because otherwise loading on demand would be
   # pointless in practice. So importing stubs is fine here!
-  var copy = s # do not copy symbols when importing!
   # check if we have already a symbol of the same name:
   var check = StrTableGet(c.tab.stack[importTablePos], s.name)
-  if check != nil and check.id != copy.id: 
-    if s.kind notin OverloadableSyms: 
+  if check != nil and check.id != s.id:
+    if s.kind notin OverloadableSyms:
       # s and check need to be qualified:
-      Incl(c.AmbiguousSymbols, copy.id)
+      Incl(c.AmbiguousSymbols, s.id)
       Incl(c.AmbiguousSymbols, check.id)
-  StrTableAdd(c.tab.stack[importTablePos], copy)
-  if s.kind == skType: 
+  # thanks to 'export' feature, it could be we import the same symbol from
+  # multiple sources, so we need to call 'StrTableAdd' here:
+  StrTableAdd(c.tab.stack[importTablePos], s)
+  if s.kind == skType:
     var etyp = s.typ
-    if etyp.kind in {tyBool, tyEnum} and sfPure notin s.flags: 
-      for j in countup(0, sonsLen(etyp.n) - 1): 
+    if etyp.kind in {tyBool, tyEnum} and sfPure notin s.flags:
+      for j in countup(0, sonsLen(etyp.n) - 1):
         var e = etyp.n.sons[j].sym
-        if (e.Kind != skEnumField): 
+        if e.Kind != skEnumField: 
           InternalError(s.info, "rawImportSymbol") 
           # BUGFIX: because of aliases for enums the symbol may already
           # have been put into the symbol table
           # BUGFIX: but only iff they are the same symbols!
         var it: TIdentIter 
         check = InitIdentIter(it, c.tab.stack[importTablePos], e.name)
-        while check != nil: 
-          if check.id == e.id: 
+        while check != nil:
+          if check.id == e.id:
             e = nil
-            break 
+            break
           check = NextIdentIter(it, c.tab.stack[importTablePos])
-        if e != nil: 
+        if e != nil:
           rawImportSymbol(c, e)
   else:
     # rodgen assures that converters and patterns are no stubs
     if s.kind == skConverter: addConverter(c, s)
     if hasPattern(s): addPattern(c, s)
-  
+
 proc importSymbol(c: PContext, n: PNode, fromMod: PSym) = 
   let ident = lookups.considerAcc(n)
   let s = StrTableGet(fromMod.tab, ident)
@@ -98,17 +98,6 @@ proc importSymbol(c: PContext, n: PNode, fromMod: PSym) =
         rawImportSymbol(c, e)
         e = NextIdentIter(it, fromMod.tab)
     else: rawImportSymbol(c, s)
-  
-proc importAllSymbols(c: PContext, fromMod: PSym) = 
-  var i: TTabIter
-  var s = InitTabIter(i, fromMod.tab)
-  while s != nil: 
-    if s.kind != skModule: 
-      if s.kind != skEnumField: 
-        if s.Kind notin ExportableSymKinds:
-          InternalError(s.info, "importAllSymbols: " & $s.kind)
-        rawImportSymbol(c, s) # this is correct!
-    s = NextIter(i, fromMod.tab)
 
 proc importAllSymbolsExcept(c: PContext, fromMod: PSym, exceptSet: TIntSet) =
   var i: TTabIter
@@ -118,12 +107,34 @@ proc importAllSymbolsExcept(c: PContext, fromMod: PSym, exceptSet: TIntSet) =
       if s.kind != skEnumField:
         if s.Kind notin ExportableSymKinds:
           InternalError(s.info, "importAllSymbols: " & $s.kind)
-        if s.name.id notin exceptSet:
+        if exceptSet.empty or s.name.id notin exceptSet:
           rawImportSymbol(c, s)
     s = NextIter(i, fromMod.tab)
 
+proc importAllSymbols*(c: PContext, fromMod: PSym) =
+  var exceptSet: TIntSet
+  importAllSymbolsExcept(c, fromMod, exceptSet)
+
+proc importForwarded(c: PContext, n: PNode, exceptSet: TIntSet) =
+  if n.isNil: return
+  case n.kind
+  of nkExportStmt:
+    for a in n:
+      assert a.kind == nkSym
+      let s = a.sym
+      if s.kind == skModule:
+        importAllSymbolsExcept(c, s, exceptSet)
+      elif exceptSet.empty or s.name.id notin exceptSet:
+        rawImportSymbol(c, s)
+  of nkExportExceptStmt:
+    localError(n.info, errGenerated, "'export except' not implemented")
+  else:
+    for i in 0 ..safeLen(n)-1:
+      importForwarded(c, n.sons[i], exceptSet)
+
 proc evalImport(c: PContext, n: PNode): PNode = 
   result = n
+  var emptySet: TIntSet
   for i in countup(0, sonsLen(n) - 1): 
     var f = checkModuleName(n.sons[i])
     if f.len > 0:
@@ -132,7 +143,8 @@ proc evalImport(c: PContext, n: PNode): PNode =
         Message(n.sons[i].info, warnDeprecated, m.name.s) 
       # ``addDecl`` needs to be done before ``importAllSymbols``!
       addDecl(c, m)             # add symbol to symbol table of module
-      importAllSymbols(c, m)
+      importAllSymbolsExcept(c, m, emptySet)
+      importForwarded(c, m.ast, emptySet)
 
 proc evalFrom(c: PContext, n: PNode): PNode = 
   result = n
@@ -157,3 +169,4 @@ proc evalImportExcept*(c: PContext, n: PNode): PNode =
       let ident = lookups.considerAcc(n.sons[i])
       exceptSet.incl(ident.id)
     importAllSymbolsExcept(c, m, exceptSet)
+    importForwarded(c, m.ast, exceptSet)
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index 9f2b431f6..7acfb830f 100755
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -1657,6 +1657,26 @@ proc fixImmediateParams(n: PNode): PNode =
   
   result = n
 
+proc semExport(c: PContext, n: PNode): PNode =
+  var x = newNodeI(n.kind, n.info)
+  #let L = if n.kind == nkExportExceptStmt: L = 1 else: n.len
+  for i in 0.. <n.len:
+    let a = n.sons[i]
+    var o: TOverloadIter
+    var s = initOverloadIter(o, c, a)
+    if s == nil:
+      localError(a.info, errGenerated, "invalid expr for 'export': " &
+          renderTree(a))
+    while s != nil:
+      if s.kind in ExportableSymKinds+{skModule}:
+        x.add(newSymNode(s, a.info))
+      s = nextOverloadIter(o, c, a)
+  if c.module.ast.isNil:
+    c.module.ast = newNodeI(nkStmtList, n.info)
+  assert c.module.ast.kind == nkStmtList
+  c.module.ast.add x
+  result = n
+
 proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = 
   result = n
   if gCmd == cmdIdeTools: suggestExpr(c, n)
@@ -1859,6 +1879,9 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   of nkIncludeStmt: 
     if not isTopLevel(c): LocalError(n.info, errXOnlyAtModuleScope, "include")
     result = evalInclude(c, n)
+  of nkExportStmt, nkExportExceptStmt:
+    if not isTopLevel(c): LocalError(n.info, errXOnlyAtModuleScope, "export")
+    result = semExport(c, n)
   of nkPragmaBlock:
     result = semPragmaBlock(c, n)
   of nkStaticStmt:
diff --git a/doc/manual.txt b/doc/manual.txt
index a5bce775b..5ea3575ee 100755
--- a/doc/manual.txt
+++ b/doc/manual.txt
@@ -3656,7 +3656,7 @@ don't need to instantiate the code multiple times, because types then can be
 manipulated using the unified internal symbol representation. In such context
 typedesc acts as any other type. You can create variables, store typedesc
 values inside containers and so on. For example, here is how we can create 
-a type-safe wrapper for the unsafe `printf` function form C:
+a type-safe wrapper for the unsafe `printf` function from C:
 
 .. code-block:: nimrod
   macro safePrintF(formatString: string{lit}, args: vararg[expr]): expr =
@@ -3744,7 +3744,7 @@ This is best illustrated by an example:
 Import statement
 ~~~~~~~~~~~~~~~~
 
-After the import statement a list of module names can follow or a single
+After the `import`:idx: statement a list of module names can follow or a single
 module name followed by an ``except`` to prevent some symbols to be imported:
 
 .. code-block:: nimrod
@@ -3754,6 +3754,33 @@ module name followed by an ``except`` to prevent some symbols to be imported:
   echo "$1" % "abc"
 
 
+Export statement
+~~~~~~~~~~~~~~~~
+
+An `export`:idx: statement can be used for symbol fowarding so that client
+modules don't need to import a module's dependencies:
+
+.. code-block:: nimrod
+  # module B
+  type TMyObject* = object
+
+.. code-block:: nimrod
+  # module A
+  import B
+  export B.TMyObject
+  
+  proc `$`*(x: TMyObject): string = "my object"
+
+
+.. code-block:: nimrod
+  # module C
+  import A
+  
+  # B.TMyObject has been imported implicitly here: 
+  var x: TMyObject
+  echo($x)
+
+
 Scope rules
 -----------
 Identifiers are valid from the point of their declaration until the end of
diff --git a/lib/pure/collections/intsets.nim b/lib/pure/collections/intsets.nim
index fcaf7b212..2a8d7eec2 100755
--- a/lib/pure/collections/intsets.nim
+++ b/lib/pure/collections/intsets.nim
@@ -190,6 +190,11 @@ proc `$`*(s: TIntSet): string =
   ## The `$` operator for int sets.
   dollarImpl()
 
+proc empty*(s: TIntSet): bool {.inline.} =
+  ## returns true if `s` is empty. This is safe to call even before
+  ## the set has been initialized with `initIntSet`.
+  result = s.counter == 0
+
 when isMainModule:
   var x = initIntSet()
   x.incl(1)
diff --git a/tests/compile/mexporta.nim b/tests/compile/mexporta.nim
new file mode 100644
index 000000000..b7d4ddec9
--- /dev/null
+++ b/tests/compile/mexporta.nim
@@ -0,0 +1,8 @@
+# module A
+import mexportb
+export mexportb.TMyObject, mexportb.xyz
+
+export mexportb.q
+
+proc `$`*(x: TMyObject): string = "my object"
+
diff --git a/tests/compile/mexportb.nim b/tests/compile/mexportb.nim
new file mode 100644
index 000000000..10d89f388
--- /dev/null
+++ b/tests/compile/mexportb.nim
@@ -0,0 +1,7 @@
+# module B
+type TMyObject* = object
+
+const xyz* = 13
+
+proc q*(x: int): int = 6
+proc q*(x: string): string = "8"
diff --git a/tests/compile/texport.nim b/tests/compile/texport.nim
new file mode 100644
index 000000000..99228dfce
--- /dev/null
+++ b/tests/compile/texport.nim
@@ -0,0 +1,10 @@
+discard """
+  output: "my object68"
+"""
+
+import mexporta
+
+# B.TMyObject has been imported implicitly here: 
+var x: TMyObject
+echo($x, q(0), q"0")
+
diff --git a/todo.txt b/todo.txt
index e6731582f..61b178a23 100755
--- a/todo.txt
+++ b/todo.txt
@@ -1,7 +1,7 @@
 version 0.9.2
 =============
 
-- 'export' feature
+- fix tfShared and tfNotNil
 - test&finish first class iterators:
   * nested iterators
   * test generic iterators
diff --git a/web/news.txt b/web/news.txt
index 6cd120e39..fc54bbce2 100755
--- a/web/news.txt
+++ b/web/news.txt
@@ -51,6 +51,9 @@ Language Additions
   that ``nil`` is not allowed. However currently the compiler performs no
   advanced static checking for this; for now it's merely for documentation
   purposes.
+- An ``export`` statement has been added to the language: It can be used for
+  symbol forwarding so client modules don't have to import a module's 
+  dependencies explicitly.
 
 
 2012-09-23 Version 0.9.0 released