2024-09-16 14:07:53 -07:00
|
|
|
#!/usr/bin/env python3
|
2024-10-03 11:37:00 -07:00
|
|
|
import abc
|
2024-09-16 14:07:53 -07:00
|
|
|
import sys
|
|
|
|
|
import time
|
|
|
|
|
from pathlib import Path
|
2024-10-03 11:37:00 -07:00
|
|
|
from typing import Self
|
2024-09-16 14:07:53 -07:00
|
|
|
|
|
|
|
|
|
2024-10-03 11:37:00 -07:00
|
|
|
class SB:
|
|
|
|
|
"String builder"
|
2024-09-16 14:07:53 -07:00
|
|
|
|
2024-10-03 11:37:00 -07:00
|
|
|
def __init__(self, init: str = ""):
|
|
|
|
|
self.value = init
|
|
|
|
|
self.padding = 0
|
2024-09-16 14:07:53 -07:00
|
|
|
|
2024-10-03 11:37:00 -07:00
|
|
|
def indent(self) -> None:
|
|
|
|
|
self.padding += 1
|
2024-09-16 14:07:53 -07:00
|
|
|
|
2024-10-03 11:37:00 -07:00
|
|
|
def dedent(self) -> None:
|
|
|
|
|
self.padding -= 1
|
|
|
|
|
assert self.padding >= 0
|
2024-09-16 14:07:53 -07:00
|
|
|
|
2024-10-03 11:37:00 -07:00
|
|
|
def line(self, line: str = ""):
|
|
|
|
|
self.write(f"{line}\n")
|
2024-09-16 14:07:53 -07:00
|
|
|
|
2024-10-03 11:37:00 -07:00
|
|
|
def write(self, value: str):
|
|
|
|
|
if not self.value or self.value[-1] == "\n":
|
|
|
|
|
self.value += " " * self.padding
|
|
|
|
|
self.value += value
|
2024-09-16 14:07:53 -07:00
|
|
|
|
2024-10-03 11:37:00 -07:00
|
|
|
def __str__(self) -> str:
|
|
|
|
|
return self.value
|
2024-09-16 14:07:53 -07:00
|
|
|
|
|
|
|
|
|
2024-10-03 11:37:00 -07:00
|
|
|
class Generator(metaclass=abc.ABCMeta):
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
|
def generate_members(self) -> str:
|
|
|
|
|
pass
|
2024-09-16 14:07:53 -07:00
|
|
|
|
2024-10-03 11:37:00 -07:00
|
|
|
@abc.abstractmethod
|
|
|
|
|
def generate_visitor(self) -> str:
|
|
|
|
|
pass
|
2024-09-16 14:07:53 -07:00
|
|
|
|
|
|
|
|
|
2024-10-03 11:37:00 -07:00
|
|
|
class GenerateStruct(Generator):
|
|
|
|
|
def __init__(self, base: str, name: str, rules: list[str]) -> None:
|
|
|
|
|
self.base = base
|
|
|
|
|
self.name = name
|
|
|
|
|
self.rules = rules
|
|
|
|
|
|
|
|
|
|
def generate_members(self) -> str:
|
|
|
|
|
sb = SB()
|
|
|
|
|
sb.line("#[derive(Debug)]")
|
|
|
|
|
sb.line(f"pub struct {self.name}{self.base} " + "{")
|
|
|
|
|
sb.indent()
|
|
|
|
|
for member in self.rules:
|
|
|
|
|
sb.write(f"pub {member}")
|
|
|
|
|
sb.line(",")
|
|
|
|
|
sb.dedent()
|
|
|
|
|
sb.line("}")
|
|
|
|
|
sb.line()
|
|
|
|
|
|
|
|
|
|
sb.line(f"impl {self.base} for {self.name}{self.base} " + "{")
|
|
|
|
|
sb.indent()
|
|
|
|
|
sb.line(
|
WIP: Add imports and modules
This is a big change because it touches a lot of stuff, but here is the
overview:
* Import syntax:
```
import foo
import bar from foo
import bar from "foo.npp"
import bar, baz from foo
import * from foo
import "foo.npp"
```
* These are all valid imports. They should be pretty
straightforward, maybe with exception of the last item. If you are
importing a path directly, but not importing any members from it,
it does not insert anything into the current namespace, and just
executes the file. This is probably going to be unused but I want
to include it for completeness. We can always remove it later
before a hypothetical 1.0 release.
* The "from" keyword is only ever used as a keyword here, and I am
allowing it to be used as an identifier elsewhere. Don't export
it, because that's weird and wrong and won't work.
* Modules:
* Doing an `import foo` will look for "foo.npp" at compile-time,
relative to the importer's directory, parse it, and compile it.
The importer will then attempt to execute the module with the new
`EnterModule` op. This instruction will execute the module kind of
like a function, assigning the module's global namespace to an
object that you can pass around.
* `import bar from foo` and `import bar from "foo.npp"` et al syntax
is not currently implemented in the compiler.
* There is a new "Module" object that represents a potentially
un-initialized module. This can't be referred to directly in code.
* VM:
* The VM operates around Module objects now. If you want to "call" a
new module, you should call `enter_module`. This is how the main
chunk is invoked.
* TODOs:
* `exit_module` function in the VM
* Finish up module implementation in compiler
* Built-in modules
* Sub-modules - e.g. `import foo.bar` - how does naming work for
this?
* Module directories. In Python you have `foo/__init__.py` and in
Rust you have `foo/mod.rs`.
* Probably a "Namespace" object that explicitly denotes "this is an
imported module that you're dealing with"
* Tests, tests, tests
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
|
|
|
f"fn accept(&self, visitor: &mut dyn {self.base}Visitor) -> Result<(), Error> "
|
2024-09-20 16:03:37 -07:00
|
|
|
+ "{"
|
|
|
|
|
)
|
2024-10-03 11:37:00 -07:00
|
|
|
sb.indent()
|
|
|
|
|
sb.line(f"visitor.visit_{self.name.lower()}_{self.base.lower()}(self)")
|
|
|
|
|
|
|
|
|
|
sb.dedent()
|
|
|
|
|
sb.line("}")
|
|
|
|
|
sb.line()
|
|
|
|
|
sb.line("fn as_any(self: Box<Self>) -> Box<dyn Any> { self }")
|
|
|
|
|
sb.line("fn as_any_ref(&self) -> &dyn Any { self }")
|
|
|
|
|
|
|
|
|
|
sb.dedent()
|
|
|
|
|
sb.write("}")
|
|
|
|
|
|
|
|
|
|
return str(sb)
|
|
|
|
|
|
|
|
|
|
def generate_visitor(self) -> str:
|
WIP: Add imports and modules
This is a big change because it touches a lot of stuff, but here is the
overview:
* Import syntax:
```
import foo
import bar from foo
import bar from "foo.npp"
import bar, baz from foo
import * from foo
import "foo.npp"
```
* These are all valid imports. They should be pretty
straightforward, maybe with exception of the last item. If you are
importing a path directly, but not importing any members from it,
it does not insert anything into the current namespace, and just
executes the file. This is probably going to be unused but I want
to include it for completeness. We can always remove it later
before a hypothetical 1.0 release.
* The "from" keyword is only ever used as a keyword here, and I am
allowing it to be used as an identifier elsewhere. Don't export
it, because that's weird and wrong and won't work.
* Modules:
* Doing an `import foo` will look for "foo.npp" at compile-time,
relative to the importer's directory, parse it, and compile it.
The importer will then attempt to execute the module with the new
`EnterModule` op. This instruction will execute the module kind of
like a function, assigning the module's global namespace to an
object that you can pass around.
* `import bar from foo` and `import bar from "foo.npp"` et al syntax
is not currently implemented in the compiler.
* There is a new "Module" object that represents a potentially
un-initialized module. This can't be referred to directly in code.
* VM:
* The VM operates around Module objects now. If you want to "call" a
new module, you should call `enter_module`. This is how the main
chunk is invoked.
* TODOs:
* `exit_module` function in the VM
* Finish up module implementation in compiler
* Built-in modules
* Sub-modules - e.g. `import foo.bar` - how does naming work for
this?
* Module directories. In Python you have `foo/__init__.py` and in
Rust you have `foo/mod.rs`.
* Probably a "Namespace" object that explicitly denotes "this is an
imported module that you're dealing with"
* Tests, tests, tests
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
|
|
|
return f"fn visit_{self.name.lower()}_{self.base.lower()}(&mut self, {self.base.lower()}: &{self.name}{self.base}) -> Result<(), Error>;"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GenerateEnum(Generator):
|
|
|
|
|
def __init__(
|
|
|
|
|
self, base: str, name: str, variants: list[tuple[str, str | list[str]]]
|
|
|
|
|
):
|
|
|
|
|
self.base = base
|
|
|
|
|
self.name = name
|
|
|
|
|
self.variants = [
|
|
|
|
|
(variant, ([members] if isinstance(members, str) else members))
|
|
|
|
|
for (variant, members) in variants
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def generate_members(self) -> str:
|
|
|
|
|
sb = SB()
|
|
|
|
|
sb.line("#[derive(Debug)]")
|
|
|
|
|
sb.line(f"pub enum {self.name}{self.base} " + "{")
|
|
|
|
|
sb.indent()
|
|
|
|
|
for variant, members in self.variants:
|
|
|
|
|
sb.write(f"{variant}")
|
|
|
|
|
if members:
|
|
|
|
|
struct_like = any(":" in member for member in members)
|
|
|
|
|
|
|
|
|
|
if struct_like:
|
|
|
|
|
sb.line(" {")
|
|
|
|
|
sb.indent()
|
|
|
|
|
for member in members:
|
|
|
|
|
sb.line(f"{member},")
|
|
|
|
|
sb.dedent()
|
|
|
|
|
sb.line("},")
|
|
|
|
|
else:
|
|
|
|
|
sb.write("(")
|
|
|
|
|
sb.write(", ".join(members))
|
|
|
|
|
sb.line("),")
|
|
|
|
|
else:
|
|
|
|
|
sb.line(",")
|
|
|
|
|
sb.dedent()
|
|
|
|
|
sb.line("}")
|
|
|
|
|
sb.line()
|
|
|
|
|
|
|
|
|
|
sb.line(f"impl {self.base} for {self.name}{self.base} " + "{")
|
|
|
|
|
sb.indent()
|
|
|
|
|
sb.line(
|
|
|
|
|
f"fn accept(&self, visitor: &mut dyn {self.base}Visitor) -> Result<(), Error> "
|
|
|
|
|
+ "{"
|
|
|
|
|
)
|
|
|
|
|
sb.indent()
|
|
|
|
|
sb.line(f"visitor.visit_{self.name.lower()}_{self.base.lower()}(self)")
|
|
|
|
|
|
|
|
|
|
sb.dedent()
|
|
|
|
|
sb.line("}")
|
|
|
|
|
sb.line()
|
|
|
|
|
sb.line("fn as_any(self: Box<Self>) -> Box<dyn Any> { self }")
|
|
|
|
|
sb.line("fn as_any_ref(&self) -> &dyn Any { self }")
|
|
|
|
|
|
|
|
|
|
sb.dedent()
|
|
|
|
|
sb.write("}")
|
|
|
|
|
|
|
|
|
|
return str(sb)
|
|
|
|
|
|
|
|
|
|
def generate_visitor(self) -> str:
|
|
|
|
|
return f"fn visit_{self.name.lower()}_{self.base.lower()}(&mut self, {self.base.lower()}: &{self.name}{self.base}) -> Result<(), Error>;"
|
2024-10-03 11:37:00 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class GenerateGroup(Generator):
|
|
|
|
|
def __init__(self, name: str, group: list[Generator] | None = None) -> None:
|
|
|
|
|
self.name = name
|
|
|
|
|
if not group:
|
|
|
|
|
group = []
|
|
|
|
|
self.group = group
|
|
|
|
|
|
|
|
|
|
def generate(self) -> str:
|
|
|
|
|
sb = SB()
|
|
|
|
|
|
|
|
|
|
sb.line(self.generate_visitor())
|
WIP: Add imports and modules
This is a big change because it touches a lot of stuff, but here is the
overview:
* Import syntax:
```
import foo
import bar from foo
import bar from "foo.npp"
import bar, baz from foo
import * from foo
import "foo.npp"
```
* These are all valid imports. They should be pretty
straightforward, maybe with exception of the last item. If you are
importing a path directly, but not importing any members from it,
it does not insert anything into the current namespace, and just
executes the file. This is probably going to be unused but I want
to include it for completeness. We can always remove it later
before a hypothetical 1.0 release.
* The "from" keyword is only ever used as a keyword here, and I am
allowing it to be used as an identifier elsewhere. Don't export
it, because that's weird and wrong and won't work.
* Modules:
* Doing an `import foo` will look for "foo.npp" at compile-time,
relative to the importer's directory, parse it, and compile it.
The importer will then attempt to execute the module with the new
`EnterModule` op. This instruction will execute the module kind of
like a function, assigning the module's global namespace to an
object that you can pass around.
* `import bar from foo` and `import bar from "foo.npp"` et al syntax
is not currently implemented in the compiler.
* There is a new "Module" object that represents a potentially
un-initialized module. This can't be referred to directly in code.
* VM:
* The VM operates around Module objects now. If you want to "call" a
new module, you should call `enter_module`. This is how the main
chunk is invoked.
* TODOs:
* `exit_module` function in the VM
* Finish up module implementation in compiler
* Built-in modules
* Sub-modules - e.g. `import foo.bar` - how does naming work for
this?
* Module directories. In Python you have `foo/__init__.py` and in
Rust you have `foo/mod.rs`.
* Probably a "Namespace" object that explicitly denotes "this is an
imported module that you're dealing with"
* Tests, tests, tests
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
|
|
|
sb.line()
|
2024-10-03 11:37:00 -07:00
|
|
|
sb.line(self.generate_members())
|
|
|
|
|
|
|
|
|
|
return str(sb)
|
|
|
|
|
|
WIP: Add imports and modules
This is a big change because it touches a lot of stuff, but here is the
overview:
* Import syntax:
```
import foo
import bar from foo
import bar from "foo.npp"
import bar, baz from foo
import * from foo
import "foo.npp"
```
* These are all valid imports. They should be pretty
straightforward, maybe with exception of the last item. If you are
importing a path directly, but not importing any members from it,
it does not insert anything into the current namespace, and just
executes the file. This is probably going to be unused but I want
to include it for completeness. We can always remove it later
before a hypothetical 1.0 release.
* The "from" keyword is only ever used as a keyword here, and I am
allowing it to be used as an identifier elsewhere. Don't export
it, because that's weird and wrong and won't work.
* Modules:
* Doing an `import foo` will look for "foo.npp" at compile-time,
relative to the importer's directory, parse it, and compile it.
The importer will then attempt to execute the module with the new
`EnterModule` op. This instruction will execute the module kind of
like a function, assigning the module's global namespace to an
object that you can pass around.
* `import bar from foo` and `import bar from "foo.npp"` et al syntax
is not currently implemented in the compiler.
* There is a new "Module" object that represents a potentially
un-initialized module. This can't be referred to directly in code.
* VM:
* The VM operates around Module objects now. If you want to "call" a
new module, you should call `enter_module`. This is how the main
chunk is invoked.
* TODOs:
* `exit_module` function in the VM
* Finish up module implementation in compiler
* Built-in modules
* Sub-modules - e.g. `import foo.bar` - how does naming work for
this?
* Module directories. In Python you have `foo/__init__.py` and in
Rust you have `foo/mod.rs`.
* Probably a "Namespace" object that explicitly denotes "this is an
imported module that you're dealing with"
* Tests, tests, tests
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
|
|
|
def add_enum(self, name: str, variants: list[tuple[str, str | list[str]]]) -> Self:
|
|
|
|
|
self.group += [GenerateEnum(self.name, name, variants)]
|
|
|
|
|
return self
|
|
|
|
|
|
2024-10-03 11:37:00 -07:00
|
|
|
def add_struct(self, name: str, rules: list[str]) -> Self:
|
|
|
|
|
self.group += [GenerateStruct(self.name, name, rules)]
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def generate_members(self) -> str:
|
|
|
|
|
sb = SB()
|
|
|
|
|
|
|
|
|
|
# Trait and acceptor
|
|
|
|
|
sb.line(f"pub trait {self.name}: Debug + Any " + "{")
|
|
|
|
|
sb.indent()
|
|
|
|
|
sb.line(
|
WIP: Add imports and modules
This is a big change because it touches a lot of stuff, but here is the
overview:
* Import syntax:
```
import foo
import bar from foo
import bar from "foo.npp"
import bar, baz from foo
import * from foo
import "foo.npp"
```
* These are all valid imports. They should be pretty
straightforward, maybe with exception of the last item. If you are
importing a path directly, but not importing any members from it,
it does not insert anything into the current namespace, and just
executes the file. This is probably going to be unused but I want
to include it for completeness. We can always remove it later
before a hypothetical 1.0 release.
* The "from" keyword is only ever used as a keyword here, and I am
allowing it to be used as an identifier elsewhere. Don't export
it, because that's weird and wrong and won't work.
* Modules:
* Doing an `import foo` will look for "foo.npp" at compile-time,
relative to the importer's directory, parse it, and compile it.
The importer will then attempt to execute the module with the new
`EnterModule` op. This instruction will execute the module kind of
like a function, assigning the module's global namespace to an
object that you can pass around.
* `import bar from foo` and `import bar from "foo.npp"` et al syntax
is not currently implemented in the compiler.
* There is a new "Module" object that represents a potentially
un-initialized module. This can't be referred to directly in code.
* VM:
* The VM operates around Module objects now. If you want to "call" a
new module, you should call `enter_module`. This is how the main
chunk is invoked.
* TODOs:
* `exit_module` function in the VM
* Finish up module implementation in compiler
* Built-in modules
* Sub-modules - e.g. `import foo.bar` - how does naming work for
this?
* Module directories. In Python you have `foo/__init__.py` and in
Rust you have `foo/mod.rs`.
* Probably a "Namespace" object that explicitly denotes "this is an
imported module that you're dealing with"
* Tests, tests, tests
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
|
|
|
f"fn accept(&self, visitor: &mut dyn {self.name}Visitor) -> Result<(), Error>;"
|
2024-10-03 11:37:00 -07:00
|
|
|
)
|
|
|
|
|
sb.line("fn as_any(self: Box<Self>) -> Box<dyn Any>;")
|
|
|
|
|
sb.line("fn as_any_ref(&self) -> &dyn Any;")
|
|
|
|
|
sb.dedent()
|
|
|
|
|
sb.line("}")
|
|
|
|
|
sb.line()
|
|
|
|
|
|
|
|
|
|
# Type
|
|
|
|
|
sb.line(f"pub type {self.name}P = Box<dyn {self.name} + 'static>;")
|
WIP: Add imports and modules
This is a big change because it touches a lot of stuff, but here is the
overview:
* Import syntax:
```
import foo
import bar from foo
import bar from "foo.npp"
import bar, baz from foo
import * from foo
import "foo.npp"
```
* These are all valid imports. They should be pretty
straightforward, maybe with exception of the last item. If you are
importing a path directly, but not importing any members from it,
it does not insert anything into the current namespace, and just
executes the file. This is probably going to be unused but I want
to include it for completeness. We can always remove it later
before a hypothetical 1.0 release.
* The "from" keyword is only ever used as a keyword here, and I am
allowing it to be used as an identifier elsewhere. Don't export
it, because that's weird and wrong and won't work.
* Modules:
* Doing an `import foo` will look for "foo.npp" at compile-time,
relative to the importer's directory, parse it, and compile it.
The importer will then attempt to execute the module with the new
`EnterModule` op. This instruction will execute the module kind of
like a function, assigning the module's global namespace to an
object that you can pass around.
* `import bar from foo` and `import bar from "foo.npp"` et al syntax
is not currently implemented in the compiler.
* There is a new "Module" object that represents a potentially
un-initialized module. This can't be referred to directly in code.
* VM:
* The VM operates around Module objects now. If you want to "call" a
new module, you should call `enter_module`. This is how the main
chunk is invoked.
* TODOs:
* `exit_module` function in the VM
* Finish up module implementation in compiler
* Built-in modules
* Sub-modules - e.g. `import foo.bar` - how does naming work for
this?
* Module directories. In Python you have `foo/__init__.py` and in
Rust you have `foo/mod.rs`.
* Probably a "Namespace" object that explicitly denotes "this is an
imported module that you're dealing with"
* Tests, tests, tests
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
|
|
|
sb.line()
|
2024-10-03 11:37:00 -07:00
|
|
|
|
|
|
|
|
# All members and rules
|
|
|
|
|
for g in self.group:
|
|
|
|
|
sb.line(g.generate_members())
|
|
|
|
|
sb.line()
|
|
|
|
|
|
WIP: Add imports and modules
This is a big change because it touches a lot of stuff, but here is the
overview:
* Import syntax:
```
import foo
import bar from foo
import bar from "foo.npp"
import bar, baz from foo
import * from foo
import "foo.npp"
```
* These are all valid imports. They should be pretty
straightforward, maybe with exception of the last item. If you are
importing a path directly, but not importing any members from it,
it does not insert anything into the current namespace, and just
executes the file. This is probably going to be unused but I want
to include it for completeness. We can always remove it later
before a hypothetical 1.0 release.
* The "from" keyword is only ever used as a keyword here, and I am
allowing it to be used as an identifier elsewhere. Don't export
it, because that's weird and wrong and won't work.
* Modules:
* Doing an `import foo` will look for "foo.npp" at compile-time,
relative to the importer's directory, parse it, and compile it.
The importer will then attempt to execute the module with the new
`EnterModule` op. This instruction will execute the module kind of
like a function, assigning the module's global namespace to an
object that you can pass around.
* `import bar from foo` and `import bar from "foo.npp"` et al syntax
is not currently implemented in the compiler.
* There is a new "Module" object that represents a potentially
un-initialized module. This can't be referred to directly in code.
* VM:
* The VM operates around Module objects now. If you want to "call" a
new module, you should call `enter_module`. This is how the main
chunk is invoked.
* TODOs:
* `exit_module` function in the VM
* Finish up module implementation in compiler
* Built-in modules
* Sub-modules - e.g. `import foo.bar` - how does naming work for
this?
* Module directories. In Python you have `foo/__init__.py` and in
Rust you have `foo/mod.rs`.
* Probably a "Namespace" object that explicitly denotes "this is an
imported module that you're dealing with"
* Tests, tests, tests
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
|
|
|
return str(sb).strip()
|
2024-10-03 11:37:00 -07:00
|
|
|
|
|
|
|
|
def generate_visitor(self) -> str:
|
|
|
|
|
sb = SB()
|
|
|
|
|
|
|
|
|
|
# Visitor
|
|
|
|
|
sb.line(f"pub trait {self.name}Visitor " + "{")
|
|
|
|
|
sb.indent()
|
|
|
|
|
for g in self.group:
|
|
|
|
|
sb.line(g.generate_visitor())
|
|
|
|
|
sb.dedent()
|
|
|
|
|
sb.line("}")
|
|
|
|
|
|
WIP: Add imports and modules
This is a big change because it touches a lot of stuff, but here is the
overview:
* Import syntax:
```
import foo
import bar from foo
import bar from "foo.npp"
import bar, baz from foo
import * from foo
import "foo.npp"
```
* These are all valid imports. They should be pretty
straightforward, maybe with exception of the last item. If you are
importing a path directly, but not importing any members from it,
it does not insert anything into the current namespace, and just
executes the file. This is probably going to be unused but I want
to include it for completeness. We can always remove it later
before a hypothetical 1.0 release.
* The "from" keyword is only ever used as a keyword here, and I am
allowing it to be used as an identifier elsewhere. Don't export
it, because that's weird and wrong and won't work.
* Modules:
* Doing an `import foo` will look for "foo.npp" at compile-time,
relative to the importer's directory, parse it, and compile it.
The importer will then attempt to execute the module with the new
`EnterModule` op. This instruction will execute the module kind of
like a function, assigning the module's global namespace to an
object that you can pass around.
* `import bar from foo` and `import bar from "foo.npp"` et al syntax
is not currently implemented in the compiler.
* There is a new "Module" object that represents a potentially
un-initialized module. This can't be referred to directly in code.
* VM:
* The VM operates around Module objects now. If you want to "call" a
new module, you should call `enter_module`. This is how the main
chunk is invoked.
* TODOs:
* `exit_module` function in the VM
* Finish up module implementation in compiler
* Built-in modules
* Sub-modules - e.g. `import foo.bar` - how does naming work for
this?
* Module directories. In Python you have `foo/__init__.py` and in
Rust you have `foo/mod.rs`.
* Probably a "Namespace" object that explicitly denotes "this is an
imported module that you're dealing with"
* Tests, tests, tests
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
|
|
|
return str(sb).strip()
|
2024-10-03 11:37:00 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
GENERATE = [
|
|
|
|
|
# Expr
|
|
|
|
|
GenerateGroup("Expr")
|
|
|
|
|
.add_struct("Binary", ["lhs: ExprP", "op: Token", "rhs: ExprP"])
|
|
|
|
|
.add_struct("Unary", ["op: Token", "expr: ExprP"])
|
|
|
|
|
.add_struct("Call", ["expr: ExprP", "args: Vec<ExprP>", "rparen: Token"])
|
|
|
|
|
.add_struct("Get", ["expr: ExprP", "name: Token"])
|
|
|
|
|
.add_struct("Index", ["expr: ExprP", "index: ExprP", "rbracket: Token"])
|
|
|
|
|
.add_struct("Primary", ["token: Token"])
|
|
|
|
|
.add_struct(
|
|
|
|
|
"Function",
|
|
|
|
|
[
|
|
|
|
|
"lparen: Token",
|
|
|
|
|
"params: Vec<(Token, Option<ExprP>)>",
|
|
|
|
|
"return_type: Option<ExprP>",
|
|
|
|
|
"body: Vec<StmtP>",
|
|
|
|
|
"rbrace: Token",
|
|
|
|
|
],
|
|
|
|
|
)
|
2024-10-15 19:01:21 -07:00
|
|
|
.add_struct("List", ["lbracket: Token", "exprs: Vec<ExprP>", "rbracket: Token"])
|
|
|
|
|
.add_struct(
|
|
|
|
|
"Map", ["lbracket: Token", "pairs: Vec<(ExprP, ExprP)>", "rbracket: Token"]
|
|
|
|
|
),
|
2024-10-03 11:37:00 -07:00
|
|
|
# Stmt
|
|
|
|
|
GenerateGroup("Stmt")
|
WIP: Add imports and modules
This is a big change because it touches a lot of stuff, but here is the
overview:
* Import syntax:
```
import foo
import bar from foo
import bar from "foo.npp"
import bar, baz from foo
import * from foo
import "foo.npp"
```
* These are all valid imports. They should be pretty
straightforward, maybe with exception of the last item. If you are
importing a path directly, but not importing any members from it,
it does not insert anything into the current namespace, and just
executes the file. This is probably going to be unused but I want
to include it for completeness. We can always remove it later
before a hypothetical 1.0 release.
* The "from" keyword is only ever used as a keyword here, and I am
allowing it to be used as an identifier elsewhere. Don't export
it, because that's weird and wrong and won't work.
* Modules:
* Doing an `import foo` will look for "foo.npp" at compile-time,
relative to the importer's directory, parse it, and compile it.
The importer will then attempt to execute the module with the new
`EnterModule` op. This instruction will execute the module kind of
like a function, assigning the module's global namespace to an
object that you can pass around.
* `import bar from foo` and `import bar from "foo.npp"` et al syntax
is not currently implemented in the compiler.
* There is a new "Module" object that represents a potentially
un-initialized module. This can't be referred to directly in code.
* VM:
* The VM operates around Module objects now. If you want to "call" a
new module, you should call `enter_module`. This is how the main
chunk is invoked.
* TODOs:
* `exit_module` function in the VM
* Finish up module implementation in compiler
* Built-in modules
* Sub-modules - e.g. `import foo.bar` - how does naming work for
this?
* Module directories. In Python you have `foo/__init__.py` and in
Rust you have `foo/mod.rs`.
* Probably a "Namespace" object that explicitly denotes "this is an
imported module that you're dealing with"
* Tests, tests, tests
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
|
|
|
.add_struct("Import", ["import_kw: Token", "what: Vec<Token>", "module: Token"])
|
2024-10-03 11:37:00 -07:00
|
|
|
.add_struct("Expr", ["expr: ExprP"])
|
2024-10-07 10:23:15 -07:00
|
|
|
.add_struct("Assign", ["lhs: Token", "op: Token", "rhs: ExprP"])
|
2024-10-07 11:05:39 -07:00
|
|
|
.add_struct("Set", ["expr: ExprP", "name: Token", "op: Token", "rhs: ExprP"])
|
2024-10-03 11:37:00 -07:00
|
|
|
.add_struct("Block", ["lbrace: Token", "stmts: Vec<StmtP>", "rbrace: Token"])
|
|
|
|
|
.add_struct("Return", ["return_kw: Token", "expr: Option<ExprP>"])
|
|
|
|
|
.add_struct(
|
|
|
|
|
"If",
|
|
|
|
|
[
|
|
|
|
|
"if_kw: Token",
|
|
|
|
|
"condition: ExprP",
|
|
|
|
|
"then_branch: BlockStmt",
|
|
|
|
|
"else_branch: Vec<StmtP>",
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
]
|
2024-09-16 14:07:53 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
if len(sys.argv) == 1:
|
|
|
|
|
path = Path(__file__).parent / "../src/ast.rs"
|
|
|
|
|
else:
|
|
|
|
|
path = Path(sys.argv[1])
|
|
|
|
|
|
|
|
|
|
path = path.resolve()
|
|
|
|
|
print(f"Using {path}")
|
|
|
|
|
|
2024-10-03 11:37:00 -07:00
|
|
|
sb = SB()
|
2024-09-16 14:07:53 -07:00
|
|
|
|
2024-09-20 16:03:37 -07:00
|
|
|
# File headers
|
2024-10-03 11:37:00 -07:00
|
|
|
sb.line(
|
2024-09-16 14:07:53 -07:00
|
|
|
"// This is an auto-generated file. Any changes made to this file may be overwritten."
|
|
|
|
|
)
|
2024-10-03 11:37:00 -07:00
|
|
|
sb.line("// This file was created at: " + time.strftime("%Y-%m-%d %H:%M:%S"))
|
|
|
|
|
sb.line("#![allow(dead_code)]")
|
2024-09-16 14:07:53 -07:00
|
|
|
|
|
|
|
|
# Imports
|
2024-10-03 11:37:00 -07:00
|
|
|
sb.line("use std::fmt::Debug;")
|
|
|
|
|
sb.line("use std::any::Any;")
|
|
|
|
|
sb.line()
|
|
|
|
|
sb.line("use crate::token::Token;")
|
|
|
|
|
sb.line()
|
WIP: Add imports and modules
This is a big change because it touches a lot of stuff, but here is the
overview:
* Import syntax:
```
import foo
import bar from foo
import bar from "foo.npp"
import bar, baz from foo
import * from foo
import "foo.npp"
```
* These are all valid imports. They should be pretty
straightforward, maybe with exception of the last item. If you are
importing a path directly, but not importing any members from it,
it does not insert anything into the current namespace, and just
executes the file. This is probably going to be unused but I want
to include it for completeness. We can always remove it later
before a hypothetical 1.0 release.
* The "from" keyword is only ever used as a keyword here, and I am
allowing it to be used as an identifier elsewhere. Don't export
it, because that's weird and wrong and won't work.
* Modules:
* Doing an `import foo` will look for "foo.npp" at compile-time,
relative to the importer's directory, parse it, and compile it.
The importer will then attempt to execute the module with the new
`EnterModule` op. This instruction will execute the module kind of
like a function, assigning the module's global namespace to an
object that you can pass around.
* `import bar from foo` and `import bar from "foo.npp"` et al syntax
is not currently implemented in the compiler.
* There is a new "Module" object that represents a potentially
un-initialized module. This can't be referred to directly in code.
* VM:
* The VM operates around Module objects now. If you want to "call" a
new module, you should call `enter_module`. This is how the main
chunk is invoked.
* TODOs:
* `exit_module` function in the VM
* Finish up module implementation in compiler
* Built-in modules
* Sub-modules - e.g. `import foo.bar` - how does naming work for
this?
* Module directories. In Python you have `foo/__init__.py` and in
Rust you have `foo/mod.rs`.
* Probably a "Namespace" object that explicitly denotes "this is an
imported module that you're dealing with"
* Tests, tests, tests
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
|
|
|
sb.line("type Error = Box<dyn std::error::Error>;")
|
|
|
|
|
sb.line()
|
2024-10-03 11:37:00 -07:00
|
|
|
|
|
|
|
|
for g in GENERATE:
|
|
|
|
|
sb.line(g.generate())
|
|
|
|
|
|
|
|
|
|
with open(path, "w") as fp:
|
|
|
|
|
fp.write(str(sb).strip())
|
2024-09-16 14:07:53 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|