-
Notifications
You must be signed in to change notification settings - Fork 84
Description
Problem: Currently all KCL projects have to be a single file. There's no way to reuse code across files.
Solution: KCL files should be able to import each other, so that one file can use functions or constants defined in another.
Principles
One file === one module, and the directory tree (on disk) === the module tree (in code). This isn't really relevant right now but is a key principle we should stick to going forwards. I'm basing this on chats with various Rust maintainers around Rust's module system being overcomplicated and how we'd all like to simplify it.
Hygiene: importing a module should not change the behaviour of your code. This means it shouldn't overwrite your code's constants/functions and it shouldn't change your model. The only changes should happen when you actually use the code you're importing.
Design
Syntax
We add a use
statement, with an optional as
suffix. For example:
use "lens.kcl" as lens
This executes lens.kcl and makes its top-level identifiers (e.g. fn cube()
) available under the namespace lens
. Your KCL file can now invoke lens.cube()
like a normal KCL function.
If you leave off the as lens
part, it'll use the filename as the module name. For example, these two are equivalent:
use "lens.kcl" as lens
// Same as above but shorter
use "lens.kcl"
To make this easier, ZMA will stop letting KCL filenames contain spaces. Most CAD apps don't allow spaces in some contexts anyway SolidWorks, Siemens Sinumerik). The mechanical engineers agree with this decision.
Usage
A use
statement is only valid in the main/top-level scope of the file. In other words, you cannot use it inside a function or any other expression.
While importing a file, the KCL interpreter won't let it send any engine requests. This prevents side-effects when importing. Instead, it returns an error, saying "Imported files cannot execute API requests, consider wrapping this in a function instead, so that the caller can choose when it executes."
Initially we'll only support importing files from the same project (we'll remove this limitation eventually).
Implementation
When you import a file, it'll create a namespace in the importer. So, we'll add a new kind of KclValue variant, KclValue::Namespace(HashMap<String, KclValue>)
. The HashMap contains items in the namespace, mapping their name to their value.
When KCL interpreter executes a use
statement, it'll:
- Read the file
lens.kcl
from the ZMA project - Parse it into a
kcl_lib::types::ast::Program
- Execute it
- Collect all top-level declarations in the
ast::Program
into key-value pairs in aKclValue::Namespace
Future extensions
- Let users only import some symbols, e.g.
use (eyepiece) from "lens.kcl"
which lets you use theeyepiece
symbol without prefixing it aslens.eyepiece
- Marking bindings as private/public
- Module trees (e.g.
use "telescope/eyepiece.kcl"
)