5
$\begingroup$

The only way that I know how import statements could be implemented is in the parsing stage where the imported module's AST is directly concatenated onto the main script's AST and the variables from the imported file are added to the main Symbol Table.

I wanted to know if this method is suitable for a production language, as it seems rather simplistic and does not take into account things like namespaces and access modifiers. Is there any advantage of doing it this way? Do any other languages just insert the AST into the main file?

If not, what other ways can this be implemented, regardless of import semantics, and how have other languages done it? What might the differences in implementation be in a dynamic language, like Python, vs a static language, like Rust?

If possible, could you please point to some links/implementations on GitHub?

$\endgroup$
6
  • $\begingroup$ This assumes you're performing identifier resolution at compile time. If your languages defers scope until runtime (such as Python), then you don't add anything to the symbol table. $\endgroup$ May 18, 2023 at 16:31
  • 4
    $\begingroup$ Possible duplicate of What are the different ways of handling imports? $\endgroup$
    – pppery
    May 18, 2023 at 16:39
  • 1
    $\begingroup$ @pppery That question is about the possible semantics of import declarations or statements; this question is about the implementation of import statements. $\endgroup$
    – kaya3
    May 18, 2023 at 16:48
  • 1
    $\begingroup$ @user16217248 As I said, the other question is about possible semantics and this one is about implementation of one of those semantics. I don't see how it's unclear. $\endgroup$
    – kaya3
    May 19, 2023 at 4:16
  • $\begingroup$ For the languages where imports are resolved at runtime, are you asking about the implementation of the runtime resolution? Or about attempts to do resolution statically for things like developer tooling? Or both? $\endgroup$
    – starball
    May 19, 2023 at 7:12

2 Answers 2

7
$\begingroup$

Here's how a modern dynamic language like Python might do it. Assume the interpreter maintains a cache of loaded modules, indexed either by the module name, filesystem path, or some other unique identifier.

  • If the module already exists in the cache, return the cached object.
  • Locate and open the appropriate source file for the imported module. If there is no such module, raise an error.
  • Insert an empty object for the module into the cache. (This marks the module's state as "in progress" and prevents unbounded recursion if there is a cyclic import.)
  • Recursively interpret the source file (i.e. invoke the interpreter's function for executing a source file).
  • Update the cache by adding the module's exported members to the object representing the imported module.
  • Bind the object or its imported members to the appropriate name(s) in the current scope.

The above might be difficult to follow, so here's an example. Suppose we have three files:

# foo.py
import bar
from baz import y
print(bar.x, y)

# bar.py
import baz
x = baz.y + 2

# baz.py
y = 3

We begin executing the program foo.py. Suppose Module is a class used to represent modules internally.

  • The first statement is import bar, so we locate and open bar.py, add bar: Module() to the cache, and begin executing the source in that file.
    • The first statement is import baz, so we locate and open baz.py, add baz: Module() to the cache, and begin executing the source in that file.
      • The statement y = 3 binds the name y to the value 3.
    • The property y: 3 is added to the module object associated with the name 'baz'.
    • The module object {y: 3} from the cache is bound to the name baz.
    • The statement x = baz.y accesses the y property from the module object baz, and binds the name x to the value 5.
  • The properties x: 5 and baz: {y: 3} are added to the module object associated with the name 'bar'.
  • The module object {x: 5, baz: {y: 3}} from the cache is bound to the name bar.
  • The second statement is from baz import y, and 'baz' is already in the cache, so we find the module object {y: 3} associated with it, and bind the name y to the value 3.
  • The final statement print(bar.x, y) reads the x property from the module object {x: 5, baz: {y: 3}}, and hence prints 5 3.

it seems rather simplistic and does not take into account things like namespaces and access modifiers

It does take into account namespaces ─ notice how a simple import statement like import bar binds the name bar to a module object which serves a namespace for bar's exported members. Moreover, since bar.py exports the baz module object, this could be accessed in foo.py as bar.baz.y.

Access modifiers are different, and typically aren't used with import statements in dynamic languages; public vs. private vs. package-private visibility is more a concept in static languages, which would have import declarations instead.

However, other dynamic languages such as Javascript do have a mechanism to control which module members are exported, via the export keyword. (Or in Python, this is controlled through the special __all__ variable.) In this case, the implementation only needs to include just the module's exported members in the object, not all of its top-level names.

$\endgroup$
2
  • $\begingroup$ Are there examples of some languages that do not put imported modules into their own namespace automatically, except for something like C/C++? $\endgroup$ May 19, 2023 at 5:58
  • 1
    $\begingroup$ @FireTheLost If you count "includes" as import statements, then sure ─ PHP is a notable example. $\endgroup$
    – kaya3
    May 19, 2023 at 6:27
3
$\begingroup$

#include

Some languages, like C, C++, and Lua (partially), literally just copy-paste the imported file into the current file. This may have a propensity for making huge files and long compilation times, but it’s the simplest way that works.

$\endgroup$
1
  • 1
    $\begingroup$ Lua doesn't work this way exactly; the require function will only import the required script once. It will also return its return value, if any. Reference. $\endgroup$
    – 124816
    May 23, 2023 at 16:19

This site is temporarily in read-only mode and not accepting new answers.

Not the answer you're looking for? Browse other questions tagged .