Local Imports
In Tomo, you can split a project into multiple files and access the code in
other files by doing use ./file.tm. There are a few major benefits to doing
this:
- Files can be split apart at conceptual boundaries to make the codebase easier to read and navigate.
- Each file has its own namespace and can define local variables not visible outside the file. This makes it easy to avoid namespace collisions.
- Tomo compiles each file in parallel with lazy recompliation. This means that compile times can be much faster if a project is intelligently split into multiple files.
To see how local imports work, let's look at a simple file:
// File: foo.tm
my_variable := "hello"
When this file is compiled to a static object file by tomo -c foo.tm, it
produces the following C header file and C source file:
// File: foo.tm.h
#pragma once
#include <tomo/tomo.h>
extern Text_t my_variable$foo_C3zxCsha;
void $initialize$foo_C3zxCsha(void);
// File: foo.tm.c
#include <tomo/tomo.h>
#include "foo.tm.h"
public Text_t my_variable$foo_C3zxCsha = Text("hello");
public void $initialize$foo_C3zxCsha(void) {
static bool initialized = false;
if (initialized) return;
initialized = true;
}
Notice that the symbols defined here (my_variable$foo_C3zxCsha) use a
filename-based suffix with a random bit at the end that includes a dollar sign.
C compilers support an extension that allows dollar signs in identifiers, and
this allows us to use guaranteed-unique prefixes so symbols from one file don't
have naming collisions with symbols in another file.
The C file is compiled by invoking the C compiler with something like: cc <flags...> -c foo.tm.c -o foo.tm.o
Now, what happens if we want to use the compiled object file?
// File: baz.tm
foo := use ./foo.tm
func say_stuff()
say("I got $(foo.my_variable) from foo")
func main()
say_stuff()
If I want to run baz.tm with tomo baz.tm then this transpiles to:
// File: baz.tm.h
#pragma once
#include <tomo/tomo.h>
#include "./foo.tm.h"
void say_stuff$baz_VEDjfzDs();
void main$baz_VEDjfzDs();
void $initialize$baz_VEDjfzDs(void);
// File: baz.tm.c
#include <tomo/tomo.h>
#include "baz.tm.h"
public void say_stuff$baz_VEDjfzDs() {
say(Texts(Text("I got "), my_variable$foo_C3zxCsha, Text(" from foo")), yes);
}
public void main$foo_VEDjfzDs() {
say_stuff$foo_VEDjfzDs();
}
public void $initialize$foo_VEDjfzDs(void) {
static bool initialized = false;
if (initialized) return;
initialized = true;
$initialize$foo_C3zxCsha();
...
}
int main$baz_VEDjfzDs$parse_and_run(int argc, char *argv[]) {
tomo_init();
$initialize$baz_VEDjfzDs();
Text_t usage = Texts(Text("Usage: "), Text$from_str(argv[0]), Text(" [--help]"));
tomo_parse_args(argc, argv, usage, usage);
main$baz_VEDjfzDs();
return 0;
}
The automatically generated function main$baz_VEDjfzDs$parse_and_run is in
charge of parsing the command line arguments to main() (in this case there
aren't any) and printing out any help/usage errors, then calling main().
Then baz.tm.o is compiled to a static object with cc <flags...> -c baz.tm.c -o baz.tm.o.
Next, we need to create an actual executable file that will invoke
main$baz_VEDjfzDs$parse_and_run() (with any command line arguments). To do
that, we create a small wrapper program:
// File: /tmp/program.c
#include <tomo/tomo.h>
#include "baz.tm.h"
int main(int argc, char *argv[])
{
return main$baz_VEDjfzDs$parse_and_run(argc, argv);
}
This program is compiled with the already-built object files to produce an
executable binary called foo like this: cc <flags...> /tmp/program.c foo.tm.o baz.tm.o -o baz
Finally, the resulting binary can be executed to actually run the program!
1 # Local Imports3 In Tomo, you can split a project into multiple files and access the code in5 this:7 1. Files can be split apart at conceptual boundaries to make the codebase8 easier to read and navigate.9 2. Each file has its own namespace and can define local variables not visible10 outside the file. This makes it easy to avoid namespace collisions.11 3. Tomo compiles each file in parallel with lazy recompliation. This means that12 compile times can be much faster if a project is intelligently split into13 multiple files.15 To see how local imports work, let's look at a simple file:17 ```18 // File: foo.tm19 my_variable := "hello"20 ```23 produces the following C header file and C source file:26 // File: foo.tm.h27 #pragma once28 #include <tomo/tomo.h>30 extern Text_t my_variable$foo_C3zxCsha;31 void $initialize$foo_C3zxCsha(void);32 ```35 // File: foo.tm.c36 #include <tomo/tomo.h>37 #include "foo.tm.h"39 public Text_t my_variable$foo_C3zxCsha = Text("hello");40 public void $initialize$foo_C3zxCsha(void) {41 static bool initialized = false;42 if (initialized) return;43 initialized = true;44 }45 ```48 filename-based suffix with a random bit at the end that includes a dollar sign.49 C compilers support an extension that allows dollar signs in identifiers, and50 this allows us to use guaranteed-unique prefixes so symbols from one file don't51 have naming collisions with symbols in another file.54 <flags...> -c foo.tm.c -o foo.tm.o`58 ```59 // File: baz.tm60 foo := use ./foo.tm62 func say_stuff()63 say("I got $(foo.my_variable) from foo")65 func main()66 say_stuff()67 ```72 // File: baz.tm.h73 #pragma once74 #include <tomo/tomo.h>75 #include "./foo.tm.h"77 void say_stuff$baz_VEDjfzDs();78 void main$baz_VEDjfzDs();79 void $initialize$baz_VEDjfzDs(void);80 ```83 // File: baz.tm.c84 #include <tomo/tomo.h>85 #include "baz.tm.h"87 public void say_stuff$baz_VEDjfzDs() {88 say(Texts(Text("I got "), my_variable$foo_C3zxCsha, Text(" from foo")), yes);89 }91 public void main$foo_VEDjfzDs() {92 say_stuff$foo_VEDjfzDs();93 }95 public void $initialize$foo_VEDjfzDs(void) {96 static bool initialized = false;97 if (initialized) return;98 initialized = true;100 $initialize$foo_C3zxCsha();101 ...102 }104 int main$baz_VEDjfzDs$parse_and_run(int argc, char *argv[]) {105 tomo_init();106 $initialize$baz_VEDjfzDs();108 Text_t usage = Texts(Text("Usage: "), Text$from_str(argv[0]), Text(" [--help]"));109 tomo_parse_args(argc, argv, usage, usage);110 main$baz_VEDjfzDs();111 return 0;112 }113 ```122 Next, we need to create an actual executable file that will invoke124 that, we create a small wrapper program:127 // File: /tmp/program.c128 #include <tomo/tomo.h>129 #include "baz.tm.h"131 int main(int argc, char *argv[])132 {133 return main$baz_VEDjfzDs$parse_and_run(argc, argv);134 }135 ```137 This program is compiled with the already-built object files to produce an139 foo.tm.o baz.tm.o -o baz`141 Finally, the resulting binary can be executed to actually run the program!