Compare commits
31 Commits
master
...
a838035e1a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a838035e1a | ||
|
|
30a4cee5af | ||
|
|
3bac625714 | ||
|
|
9f21d0fd1d | ||
|
|
b5a28daebf | ||
|
|
8cc47ae63e | ||
|
|
13f3f33740 | ||
|
|
a0645d94dd | ||
|
|
79ff11f622 | ||
|
|
55a45ff17c | ||
|
|
42c9ff6ca7 | ||
|
|
312f7949bd | ||
|
|
bce3ed7ba1 | ||
|
|
977c449c3f | ||
|
|
2a6da91eb9 | ||
|
|
4d3c0c6c3c | ||
|
|
5da45e78c2 | ||
|
|
4f7608a601 | ||
|
|
0097ce782d | ||
|
|
299551db1b | ||
|
|
906b15c93c | ||
|
|
07c0688d1b | ||
|
|
564f00a419 | ||
|
|
58f74d3e1d | ||
|
|
4b46483895 | ||
|
|
b2816164f9 | ||
|
|
6f3d9a5bcc | ||
|
|
2d0daf4b90 | ||
|
|
7669f3cbca | ||
|
|
48e16c38b9 | ||
|
|
7acb2bd8e6 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,6 +3,4 @@
|
|||||||
/.coverage
|
/.coverage
|
||||||
/venv
|
/venv
|
||||||
|
|
||||||
/tests/integration/test_lang/test_generated_*.py
|
|
||||||
|
|
||||||
__pycache__
|
__pycache__
|
||||||
|
|||||||
36
Makefile
36
Makefile
@ -1,5 +1,7 @@
|
|||||||
TEST_FILES := tests
|
WABT_DIR := /home/johan/Sources/github.com/WebAssembly/wabt
|
||||||
WAT2WASM := venv/bin/python wat2wasm.py
|
|
||||||
|
WAT2WASM := $(WABT_DIR)/bin/wat2wasm
|
||||||
|
WASM2C := $(WABT_DIR)/bin/wasm2c
|
||||||
|
|
||||||
%.wat: %.py $(shell find phasm -name '*.py') venv/.done
|
%.wat: %.py $(shell find phasm -name '*.py') venv/.done
|
||||||
venv/bin/python -m phasm $< $@
|
venv/bin/python -m phasm $< $@
|
||||||
@ -13,38 +15,30 @@ WAT2WASM := venv/bin/python wat2wasm.py
|
|||||||
%.wasm: %.wat
|
%.wasm: %.wat
|
||||||
$(WAT2WASM) $^ -o $@
|
$(WAT2WASM) $^ -o $@
|
||||||
|
|
||||||
|
%.c: %.wasm
|
||||||
|
$(WASM2C) $^ -o $@
|
||||||
|
|
||||||
|
# %.exe: %.c
|
||||||
|
# cc $^ -o $@ -I $(WABT_DIR)/wasm2c
|
||||||
|
|
||||||
examples: venv/.done $(subst .py,.wasm,$(wildcard examples/*.py)) $(subst .py,.wat.html,$(wildcard examples/*.py)) $(subst .py,.py.html,$(wildcard examples/*.py))
|
examples: venv/.done $(subst .py,.wasm,$(wildcard examples/*.py)) $(subst .py,.wat.html,$(wildcard examples/*.py)) $(subst .py,.py.html,$(wildcard examples/*.py))
|
||||||
venv/bin/python3 -m http.server --directory examples
|
venv/bin/python3 -m http.server --directory examples
|
||||||
|
|
||||||
test: venv/.done $(subst .json,.py,$(subst /generator_,/test_generated_,$(wildcard tests/integration/test_lang/generator_*.json)))
|
test: venv/.done
|
||||||
venv/bin/pytest $(TEST_FILES) $(TEST_FLAGS)
|
venv/bin/pytest tests $(TEST_FLAGS)
|
||||||
|
|
||||||
lint: venv/.done
|
lint: venv/.done
|
||||||
venv/bin/ruff check phasm tests
|
venv/bin/pylint phasm
|
||||||
|
|
||||||
typecheck: venv/.done
|
typecheck: venv/.done
|
||||||
venv/bin/mypy --strict phasm wat2wasm.py tests/integration/helpers.py tests/integration/memory.py tests/integration/runners.py
|
venv/bin/mypy --strict phasm tests/integration/runners.py
|
||||||
|
|
||||||
venv/.done: requirements.txt
|
venv/.done: requirements.txt
|
||||||
python3.12 -m venv venv
|
python3.8 -m venv venv
|
||||||
venv/bin/python3 -m pip install wheel pip --upgrade
|
venv/bin/python3 -m pip install wheel pip --upgrade
|
||||||
venv/bin/python3 -m pip install -r $^
|
venv/bin/python3 -m pip install -r $^
|
||||||
touch $@
|
touch $@
|
||||||
|
|
||||||
tests/integration/test_lang/test_generated_%.py: venv/.done tests/integration/test_lang/generator.py tests/integration/test_lang/generator.md tests/integration/test_lang/generator_%.json
|
|
||||||
venv/bin/python3 tests/integration/test_lang/generator.py tests/integration/test_lang/generator.md tests/integration/test_lang/generator_$*.json > $@
|
|
||||||
|
|
||||||
clean-examples:
|
|
||||||
rm -f examples/*.wat examples/*.wasm examples/*.wat.html examples/*.py.html
|
|
||||||
|
|
||||||
clean-generated-tests:
|
|
||||||
rm -f tests/integration/test_lang/test_generated_*.py
|
|
||||||
|
|
||||||
.SECONDARY: # Keep intermediate files
|
.SECONDARY: # Keep intermediate files
|
||||||
|
|
||||||
.PHONY: examples
|
.PHONY: examples
|
||||||
|
|
||||||
# So generally the right thing to do is to delete the target file if the recipe fails after beginning to change the file.
|
|
||||||
# make will do this if .DELETE_ON_ERROR appears as a target.
|
|
||||||
# This is almost always what you want make to do, but it is not historical practice; so for compatibility, you must explicitly request it.
|
|
||||||
.DELETE_ON_ERROR:
|
|
||||||
|
|||||||
93
README.md
93
README.md
@ -3,14 +3,46 @@ phasm
|
|||||||
|
|
||||||
Elevator pitch
|
Elevator pitch
|
||||||
--------------
|
--------------
|
||||||
A programming language that looks like Python, handles like Haskell,
|
A programming language, that looks like Python, handles like Haskell,
|
||||||
and compiles directly to WebAssembly.
|
and compiles directly to WebAssembly.
|
||||||
|
|
||||||
|
Project state
|
||||||
|
-------------
|
||||||
|
This is a hobby project for now. Use at your own risk.
|
||||||
|
|
||||||
|
How to run
|
||||||
|
----------
|
||||||
|
You should only need make and python3. Currently, we're working with python3.8,
|
||||||
|
since we're using the python ast parser, it might not work on other versions.
|
||||||
|
|
||||||
|
To run the examples:
|
||||||
|
```sh
|
||||||
|
make examples
|
||||||
|
```
|
||||||
|
|
||||||
|
To run the tests:
|
||||||
|
```sh
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
To run the linting and type checking:
|
||||||
|
```sh
|
||||||
|
make lint typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
To compile a Phasm file:
|
||||||
|
```sh
|
||||||
|
python3.8 -m phasm source.py output.wat
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional required tools
|
||||||
|
-------------------------
|
||||||
|
At the moment, the compiler outputs WebAssembly text format. To actually
|
||||||
|
get a binary, you will need the wat2wasm tool[6].
|
||||||
|
|
||||||
Example
|
Example
|
||||||
-------
|
-------
|
||||||
|
For more examples, see the examples directory.
|
||||||
From `examples/fib.py`:
|
|
||||||
|
|
||||||
```py
|
```py
|
||||||
def helper(n: u64, a: u64, b: u64) -> u64:
|
def helper(n: u64, a: u64, b: u64) -> u64:
|
||||||
if n < 1:
|
if n < 1:
|
||||||
@ -29,55 +61,6 @@ def fib(n: u64) -> u64:
|
|||||||
return helper(n - 1, 0, 1)
|
return helper(n - 1, 0, 1)
|
||||||
```
|
```
|
||||||
|
|
||||||
Compile to a WebAssembly text file:
|
|
||||||
```sh
|
|
||||||
python3 -m phasm examples/fib.py examples/fib.wat
|
|
||||||
```
|
|
||||||
|
|
||||||
Generate a WebAssembly binary file:
|
|
||||||
```sh
|
|
||||||
python wat2wasm.py examples/fib.wat -o examples/fib.wasm
|
|
||||||
```
|
|
||||||
|
|
||||||
Ready for including in your WebAssembly runtime!
|
|
||||||
|
|
||||||
Run `make examples` to start a local web server with some more examples. Each example has the source listed, as well as the compiled WebAssembly text.
|
|
||||||
|
|
||||||
Project state
|
|
||||||
-------------
|
|
||||||
This is a hobby project for now. Use at your own risk.
|
|
||||||
|
|
||||||
The parser, compiler and type checker are in a reasonably usable state.
|
|
||||||
|
|
||||||
What's still lacking is support for runtimes - notably, making it easier to get values in and out of the runtime.
|
|
||||||
For example, while Phasm supports a u32 type, when you get your value out, it will probably be a signed value.
|
|
||||||
And getting strings, structs, arrays and other combined values in and out requires manual work.
|
|
||||||
|
|
||||||
How to run
|
|
||||||
----------
|
|
||||||
You should only need make and python3. Currently, we're working with python3.12,
|
|
||||||
since we're using the python ast parser, it might not work on other versions.
|
|
||||||
|
|
||||||
To compile a Phasm file:
|
|
||||||
```sh
|
|
||||||
python3.12 -m phasm source.py output.wat
|
|
||||||
```
|
|
||||||
|
|
||||||
To run the examples:
|
|
||||||
```sh
|
|
||||||
make examples
|
|
||||||
```
|
|
||||||
|
|
||||||
To run the tests:
|
|
||||||
```sh
|
|
||||||
make test
|
|
||||||
```
|
|
||||||
|
|
||||||
To run the linting and type checking:
|
|
||||||
```sh
|
|
||||||
make lint typecheck
|
|
||||||
```
|
|
||||||
|
|
||||||
Gotcha's
|
Gotcha's
|
||||||
--------
|
--------
|
||||||
- When importing and exporting unsigned values to WebAssembly, they will become
|
- When importing and exporting unsigned values to WebAssembly, they will become
|
||||||
@ -100,9 +83,6 @@ Also, if you are trying out Phasm, and you're running into a limitation, we're
|
|||||||
interested in a minimal test case that shows what you want to achieve and how
|
interested in a minimal test case that shows what you want to achieve and how
|
||||||
Phasm currently fails you.
|
Phasm currently fails you.
|
||||||
|
|
||||||
We're also investigating using WASI - but that's still ongoing research. If you
|
|
||||||
have tips or ideas on that, we'd be interested.
|
|
||||||
|
|
||||||
Name origin
|
Name origin
|
||||||
-----------
|
-----------
|
||||||
- p from python
|
- p from python
|
||||||
@ -116,3 +96,4 @@ References
|
|||||||
[3] https://webassembly.org/
|
[3] https://webassembly.org/
|
||||||
[4] https://www.w3.org/TR/wasm-core-1/
|
[4] https://www.w3.org/TR/wasm-core-1/
|
||||||
[5] https://en.wikipedia.org/w/index.php?title=WebAssembly&oldid=1103639883
|
[5] https://en.wikipedia.org/w/index.php?title=WebAssembly&oldid=1103639883
|
||||||
|
[6] https://github.com/WebAssembly/wabt
|
||||||
|
|||||||
23
TODO.md
23
TODO.md
@ -1,27 +1,14 @@
|
|||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
- Decide between lineair types / uniqueness vs garbage collector
|
- Implement a proper type matching / checking system
|
||||||
- https://borretti.me/article/how-australs-linear-type-checker-works
|
- Implement subscript as an operator
|
||||||
|
- Re-implement Subscript contraints - Doing the LiteralFitsConstraint with a tuple doesn't put the types on the tuple elements
|
||||||
|
- Implement structs again, with the `.foo` notation working
|
||||||
|
|
||||||
- Rename constant to literal
|
- Rename constant to literal
|
||||||
|
|
||||||
- Implement a trace() builtin for debugging
|
- Implement a trace() builtin for debugging
|
||||||
- Check if we can use DataView in the Javascript examples, e.g. with setUint32
|
- Check if we can use DataView in the Javascript examples, e.g. with setUint32
|
||||||
|
- Storing u8 in memory still claims 32 bits (since that's what you need in local variables). However, using load8_u / loadu_s we can optimize this.
|
||||||
- Implement a FizzBuzz example
|
- Implement a FizzBuzz example
|
||||||
- Also, check the codes for FIXME and TODO
|
- Also, check the codes for FIXME and TODO
|
||||||
- Allocation is done using pointers for members, is this desired?
|
|
||||||
- See if we want to replace Fractional with Real, and add Rational, Irrationl, Algebraic, Transendental
|
|
||||||
- Implement q32? q64? Two i32/i64 divided?
|
|
||||||
- Have a set of rules or guidelines for the constraint comments, they're messy.
|
|
||||||
- calculate_alloc_size can be reworked; is_member isn't useful with TYPE_INFO_MAP
|
|
||||||
|
|
||||||
- Parser is putting stuff in ModuleDataBlock
|
|
||||||
- Surely the compiler should build data blocks
|
|
||||||
- Also put the struct.pack constants in TYPE_INFO_MAP
|
|
||||||
- Implemented Bounded: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#t:Bounded
|
|
||||||
- Try to implement the min and max functions using select
|
|
||||||
|
|
||||||
- Read https://bytecodealliance.org/articles/multi-value-all-the-wasm
|
|
||||||
|
|
||||||
- Implement type class 'inheritance'
|
|
||||||
- Rework type classes - already started on a separate dir for those, but quite a few things are still in other places.
|
|
||||||
|
|||||||
51
examples/buffer.html
Normal file
51
examples/buffer.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Examples - Buffer</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Buffer</h1>
|
||||||
|
|
||||||
|
<a href="index.html">List</a> - <a href="buffer.py.html">Source</a> - <a href="buffer.wat.html">WebAssembly</a>
|
||||||
|
|
||||||
|
<div style="white-space: pre;" id="results"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript" src="./include.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
let importObject = {};
|
||||||
|
|
||||||
|
// Run a single test
|
||||||
|
function run_test(app, str)
|
||||||
|
{
|
||||||
|
let offset = alloc_bytes(app, str);
|
||||||
|
|
||||||
|
let js_chars = [];
|
||||||
|
let wasm_chars = [];
|
||||||
|
for(let idx = 0; idx < str.length; ++idx) {
|
||||||
|
js_chars.push(str.charCodeAt(idx));
|
||||||
|
wasm_chars.push(app.instance.exports.index(offset, idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = js_chars.every(function(value, index) { return value === wasm_chars[index]})
|
||||||
|
|
||||||
|
test_result(result, {
|
||||||
|
'summary': 'js_chars == wasm_chars, for "' + str + '"',
|
||||||
|
'attributes': {
|
||||||
|
'js_chars': js_chars,
|
||||||
|
'wasm_chars': wasm_chars,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
WebAssembly.instantiateStreaming(fetch('buffer.wasm'), importObject)
|
||||||
|
.then(app => {
|
||||||
|
run_test(app, '');
|
||||||
|
run_test(app, 'a');
|
||||||
|
run_test(app, 'Hello');
|
||||||
|
run_test(app, 'The quick brown fox jumps over the lazy dog');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
examples/buffer.py
Normal file
7
examples/buffer.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
@exported
|
||||||
|
def index(inp: bytes, idx: u32) -> u8:
|
||||||
|
return inp[idx]
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def length(inp: bytes) -> i32:
|
||||||
|
return len(inp)
|
||||||
@ -2,68 +2,245 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Examples - CRC32</title>
|
<title>Examples - CRC32</title>
|
||||||
<link rel="stylesheet" type="text/css" href="main.css">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Examples - CRC32</h1>
|
<h1>Buffer</h1>
|
||||||
|
|
||||||
<div class="menu">
|
<a href="index.html">List</a> - <a href="crc32.py.html">Source</a> - <a href="crc32.wat.html">WebAssembly</a><br />
|
||||||
<a href="index.html">List</a> - <a href="crc32.py.html">Source</a> - <a href="crc32.wat.html">WebAssembly</a><br />
|
<br />
|
||||||
</div>
|
Note: This tests performs some timing comparison, please wait a few seconds for the results.<br />
|
||||||
|
<div style="white-space: pre;" id="results"></div>
|
||||||
|
|
||||||
<div class="description">
|
<h2>Measurement log</h2>
|
||||||
<p>
|
<h3>AMD Ryzen 7 3700X 8-Core, Ubuntu 20.04, Linux 5.4.0-124-generic</h3>
|
||||||
This example shows a fold implementation of a <a href="https://en.wikipedia.org/wiki/Cyclic_redundancy_check">cyclic redundancy check</a>.
|
<h4>After optimizing fold over bytes by inlineing __subscript_bytes__</h4>
|
||||||
There are actually many variations of these CRCs - this one is specifically know as CRC-32/ISO-HDLC.
|
<table>
|
||||||
</p>
|
<tr>
|
||||||
</div>
|
<td>Test</td>
|
||||||
|
<td>Interpreter</td>
|
||||||
|
<td>Setup</td>
|
||||||
|
<td>WebAssembly</td>
|
||||||
|
<td>Javascript</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Lynx * 65536</td>
|
||||||
|
<td>Chromium 104.0.5112.101</td>
|
||||||
|
<td>DevTools closed</td>
|
||||||
|
<td>5.70</td>
|
||||||
|
<td>12.45</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Lynx * 65536</td>
|
||||||
|
<td>Firefox 103</td>
|
||||||
|
<td>DevTools closed</td>
|
||||||
|
<td>5.16</td>
|
||||||
|
<td>5.72</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Lynx * 1048576</td>
|
||||||
|
<td>Chromium 104.0.5112.101</td>
|
||||||
|
<td>DevTools closed</td>
|
||||||
|
<td>95.65</td>
|
||||||
|
<td>203.60</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Lynx * 1048576</td>
|
||||||
|
<td>Firefox 103</td>
|
||||||
|
<td>DevTools closed</td>
|
||||||
|
<td>83.34</td>
|
||||||
|
<td>92.38</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<h4>Before optimizing fold over bytes by inlineing __subscript_bytes__</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>Test</td>
|
||||||
|
<td>Interpreter</td>
|
||||||
|
<td>Setup</td>
|
||||||
|
<td>WebAssembly</td>
|
||||||
|
<td>Javascript</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Lynx * 65536</td>
|
||||||
|
<td>Chromium 104.0.5112.101</td>
|
||||||
|
<td>DevTools closed</td>
|
||||||
|
<td>9.35</td>
|
||||||
|
<td>12.56</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Lynx * 65536</td>
|
||||||
|
<td>Chromium 104.0.5112.101</td>
|
||||||
|
<td>DevTools open</td>
|
||||||
|
<td>14.71</td>
|
||||||
|
<td>12.72</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Lynx * 65536</td>
|
||||||
|
<td>Chromium 104.0.5112.101</td>
|
||||||
|
<td>Record page load</td>
|
||||||
|
<td>9.44</td>
|
||||||
|
<td>12.69</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Lynx * 65536</td>
|
||||||
|
<td>Firefox 103</td>
|
||||||
|
<td>DevTools closed</td>
|
||||||
|
<td>9.02</td>
|
||||||
|
<td>5.86</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Lynx * 65536</td>
|
||||||
|
<td>Firefox 103</td>
|
||||||
|
<td>DevTools open</td>
|
||||||
|
<td>9.01</td>
|
||||||
|
<td>5.83</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Lynx * 65536</td>
|
||||||
|
<td>Firefox 103</td>
|
||||||
|
<td>Record page load</td>
|
||||||
|
<td>72.41</td>
|
||||||
|
<td>5.85</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<h3>Try it out!</h3>
|
<tr>
|
||||||
<div class="example">
|
<td>Lynx * 1048576</td>
|
||||||
<textarea id="example-data" style="width: 75%; height: 4em;">The quick brown fox jumps over the lazy dog</textarea><br />
|
<td>Chromium 104.0.5112.101</td>
|
||||||
<button type="click" id="example-click" disabled>Calculate</button>
|
<td>DevTools closed</td>
|
||||||
<input type="text" id="example-crc32" />
|
<td>149.24</td>
|
||||||
</div>
|
<td>202.36</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Lynx * 1048576</td>
|
||||||
|
<td>Firefox 103</td>
|
||||||
|
<td>DevTools closed</td>
|
||||||
|
<td>145.01</td>
|
||||||
|
<td>91.44</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
<div class="example-list">
|
<h4>Notes</h4>
|
||||||
<ul>
|
- Firefox seems faster than Chromium in my setup for Javascript, WebAssembly seems about the same.<br />
|
||||||
<li><a href="#" data-n="123456789">crc32("123456789")</a> = cbf43926</li>
|
- Having DevTools open in Chromium seems to slow down the WebAssembly by about 30%, but not when doing a recording of the page load.<br />
|
||||||
<li><a href="#" data-n="Hello, world!">crc32("Hello, world!")</a> = ebe6c6e6</li>
|
- WebAssembly in Firefox seems to slow down when doing a recording of the page load, which makes sense, but the Javascript does not.<br />
|
||||||
<li><a href="#" data-n="The quick brown fox jumps over the lazy dog">crc32("The quick brown fox jumps over the lazy dog")</a> = 414fa339</li>
|
|
||||||
<li><a href="#" data-n="CRC-32/ISO-HDLC
|
|
||||||
Also referred to as ISO 3309, ITU-T V.42, CRC-32-IEEE, and many other names.
|
|
||||||
|
|
||||||
The CRC of ASCII "123456789" is 0xcbf43926.
|
<script type="text/javascript" src="./include.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
let importObject = {};
|
||||||
|
|
||||||
Examples of formats that use CRC-32/ISO-HDLC: ZIP, PNG, Gzip, ARJ.">crc32("CRC-32/ISO-HDLC...")</a> = 126afcf</li>
|
// Build up a JS version
|
||||||
</ul>
|
var makeCRCTable = function(){
|
||||||
</div>
|
var c;
|
||||||
|
var crcTable = [];
|
||||||
<!-- We'll need to use some interface glue - WebAssembly doesn't let us pass strings directly. -->
|
for(var n =0; n < 256; n++){
|
||||||
<script type="text/javascript" src="./include.js"></script>
|
c = n;
|
||||||
<script>
|
for(var k =0; k < 8; k++){
|
||||||
let importObject = {};
|
c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
|
||||||
let exampleN = document.querySelector('#example-data');
|
|
||||||
let exampleClick = document.querySelector('#example-click');
|
|
||||||
let exampleCrc32 = document.querySelector('#example-crc32');
|
|
||||||
|
|
||||||
WebAssembly.instantiateStreaming(fetch('crc32.wasm'), importObject)
|
|
||||||
.then(app => {
|
|
||||||
exampleClick.addEventListener('click', event => {
|
|
||||||
let in_put = exampleN.value;
|
|
||||||
let in_put_addr = alloc_bytes(app, in_put);
|
|
||||||
let result = app.instance.exports.crc32(in_put_addr);
|
|
||||||
exampleCrc32.value = i32_to_u32(result).toString(16);
|
|
||||||
});
|
|
||||||
exampleClick.removeAttribute('disabled');
|
|
||||||
});
|
|
||||||
|
|
||||||
for(let exmpl of document.querySelectorAll('a[data-n]') ) {
|
|
||||||
exmpl.addEventListener('click', event => {
|
|
||||||
exampleN.value = exmpl.getAttribute('data-n');
|
|
||||||
exampleClick.click();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
</script>
|
crcTable[n] = c;
|
||||||
|
}
|
||||||
|
return crcTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.crcTable = makeCRCTable();
|
||||||
|
|
||||||
|
var crc32_js = function(i8arr) {
|
||||||
|
// console.log('crc32_js', i8arr.length);
|
||||||
|
|
||||||
|
var crcTable = window.crcTable;
|
||||||
|
var crc = 0 ^ (-1);
|
||||||
|
|
||||||
|
for (var i = 0; i < i8arr.length; i++ ) {
|
||||||
|
crc = (crc >>> 8) ^ crcTable[(crc ^ i8arr[i]) & 0xFF];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (crc ^ (-1)) >>> 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run a single test
|
||||||
|
function run_test(app, str, str_repeat)
|
||||||
|
{
|
||||||
|
// Cast to Uint32 in Javascript
|
||||||
|
let crc32_wasm = function(offset) {
|
||||||
|
// console.log('crc32_wasm', str.length);
|
||||||
|
return app.instance.exports.crc32(offset) >>> 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
let orig_str = str;
|
||||||
|
if( str_repeat ) {
|
||||||
|
str = str.repeat(str_repeat);
|
||||||
|
} else {
|
||||||
|
str_repeat = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = Uint8Array.from(str.split('').map(x => x.charCodeAt()));
|
||||||
|
|
||||||
|
offset = alloc_bytes(app, data);
|
||||||
|
|
||||||
|
let tweak = () => {
|
||||||
|
data[0] = data[0] + 1;
|
||||||
|
|
||||||
|
let i8arr = new Uint8Array(app.instance.exports.memory.buffer, offset + 4, data.length);
|
||||||
|
i8arr[0] = i8arr[0] + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
let tweak_reset = () => {
|
||||||
|
data[0] = 'T'.charCodeAt(0);
|
||||||
|
|
||||||
|
let i8arr = new Uint8Array(app.instance.exports.memory.buffer, offset + 4, data.length);
|
||||||
|
i8arr[0] = 'T'.charCodeAt(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run once to get the result
|
||||||
|
// For some reason, the JS version takes 2ms on the first run
|
||||||
|
// let wasm_result = crc32_wasm(offset);
|
||||||
|
// let js_result = crc32_js(data);
|
||||||
|
|
||||||
|
let wasm_timing = run_times(100, () => crc32_wasm(offset));
|
||||||
|
let js_timing = run_times(100, () => crc32_js(data));
|
||||||
|
|
||||||
|
let wasm_time = wasm_timing.avg;
|
||||||
|
let js_time = js_timing.avg;
|
||||||
|
|
||||||
|
let check = wasm_timing.values.every(function(value, index) {
|
||||||
|
return value.result === js_timing.values[index].result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Don't test speedup for small strings, it varies a lot
|
||||||
|
let speedup = (wasm_timing.min == 0 || js_timing.min == 0)
|
||||||
|
? 1
|
||||||
|
: js_time / wasm_time;
|
||||||
|
|
||||||
|
test_result(check && 0.999 < speedup, { // At least as fast as Javascript
|
||||||
|
'summary': 'crc32(' + (str
|
||||||
|
? (str.length < 64 ? '"' + str + '"' : '"' + str.substring(0, 64) + '..." (' + str.length + ')')
|
||||||
|
: '""') + ')',
|
||||||
|
'attributes': {
|
||||||
|
'str': orig_str,
|
||||||
|
'str_repeat': str_repeat,
|
||||||
|
'wasm_timing': wasm_timing,
|
||||||
|
'js_timing': js_timing,
|
||||||
|
'check': check,
|
||||||
|
'speedup': speedup,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load WebAssembly, and run all tests
|
||||||
|
WebAssembly.instantiateStreaming(fetch('crc32.wasm'), importObject)
|
||||||
|
.then(app => {
|
||||||
|
app.instance.exports.memory.grow(640);
|
||||||
|
|
||||||
|
run_test(app, "");
|
||||||
|
run_test(app, "a");
|
||||||
|
run_test(app, "Z");
|
||||||
|
run_test(app, "ab");
|
||||||
|
run_test(app, "abcdefghijklmnopqrstuvwxyz");
|
||||||
|
run_test(app, "The quick brown fox jumps over the lazy dog");
|
||||||
|
run_test(app, "The quick brown fox jumps over the lazy dog", 1024);
|
||||||
|
run_test(app, "Lynx c.q. vos prikt bh: dag zwemjuf!", 65536);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
# CRC-32/ISO-HDLC
|
|
||||||
#
|
|
||||||
# #include <inttypes.h> // uint32_t, uint8_t
|
# #include <inttypes.h> // uint32_t, uint8_t
|
||||||
#
|
#
|
||||||
# uint32_t CRC32(const uint8_t data[], size_t data_length) {
|
# uint32_t CRC32(const uint8_t data[], size_t data_length) {
|
||||||
@ -51,7 +49,7 @@ _CRC32_Table: u32[256] = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _crc32_f(crc: u32, byt: u8) -> u32:
|
def _crc32_f(crc: u32, byt: u8) -> u32:
|
||||||
return (crc >> 8) ^ _CRC32_Table[(crc & 0xFF) ^ extend(byt)]
|
return (crc >> 8) ^ _CRC32_Table[(crc & 0xFF) ^ u32(byt)]
|
||||||
|
|
||||||
@exported
|
@exported
|
||||||
def crc32(data: bytes) -> u32:
|
def crc32(data: bytes) -> u32:
|
||||||
|
|||||||
@ -1,62 +1,55 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Examples - Fibonacci</title>
|
<title>Examples - Fibonacci</title>
|
||||||
<link rel="stylesheet" type="text/css" href="main.css">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Examples - Fibonacci</h1>
|
<h1>Fibonacci</h1>
|
||||||
|
|
||||||
<div class="menu">
|
<a href="index.html">List</a> - <a href="fib.py.html">Source</a> - <a href="fib.wat.html">WebAssembly</a>
|
||||||
<a href="index.html">List</a> - <a href="fib.py.html">Source</a> - <a href="fib.wat.html">WebAssembly</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="description">
|
<div style="white-space: pre;" id="results"></div>
|
||||||
<p>
|
|
||||||
This example shows a recursive implementation of the <a href="https://en.wikipedia.org/wiki/Fibonacci_sequence">Fibonacci sequence</a>.
|
|
||||||
It makes uses of WebAssembly's support for <a href="https://en.wikipedia.org/wiki/Tail_call">tail calls</a>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Try it out!</h3>
|
|
||||||
<div class="example">
|
|
||||||
<input type="number" id="example-n" value="25" />
|
|
||||||
<button type="click" id="example-click" disabled>Calculate</button>
|
|
||||||
<input type="number" id="example-fib" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="example-list">
|
<script type="text/javascript" src="./include.js"></script>
|
||||||
<ul>
|
<script type="text/javascript">
|
||||||
<li><a href="#" data-n="5">fib(5)</a> = 5</li>
|
let importObject = {};
|
||||||
<li><a href="#" data-n="10">fib(10)</a> = 55</li>
|
|
||||||
<li><a href="#" data-n="25">fib(25)</a> = 75025</li>
|
|
||||||
<li><a href="#" data-n="50">fib(50)</a> = 12586269025</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
function run_test(app, number, expected)
|
||||||
let importObject = {};
|
{
|
||||||
let exampleN = document.querySelector('#example-n');
|
let actual = app.instance.exports.fib(BigInt(number));
|
||||||
let exampleClick = document.querySelector('#example-click');
|
console.log(actual);
|
||||||
let exampleFib = document.querySelector('#example-fib');
|
|
||||||
|
|
||||||
WebAssembly.instantiateStreaming(fetch('fib.wasm'), importObject)
|
test_result(BigInt(expected) == actual, {
|
||||||
|
'summary': 'fib(' + number + ')',
|
||||||
|
'attributes': {
|
||||||
|
'expected': expected,
|
||||||
|
'actual': actual
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
WebAssembly.instantiateStreaming(fetch('fib.wasm'), importObject)
|
||||||
.then(app => {
|
.then(app => {
|
||||||
exampleClick.addEventListener('click', event => {
|
// 92: 7540113804746346429
|
||||||
let in_put = exampleN.value;
|
// i64: 9223372036854775807
|
||||||
let result = app.instance.exports.fib(BigInt(in_put));
|
// 93: 12200160415121876738
|
||||||
exampleFib.value = result;
|
|
||||||
});
|
|
||||||
exampleClick.removeAttribute('disabled');
|
|
||||||
});
|
|
||||||
|
|
||||||
for(let exmpl of document.querySelectorAll('a[data-n]') ) {
|
run_test(app, 1, '1');
|
||||||
exmpl.addEventListener('click', event => {
|
run_test(app, 2, '1');
|
||||||
exampleN.value = exmpl.getAttribute('data-n');
|
run_test(app, 3, '2');
|
||||||
exampleClick.click();
|
run_test(app, 4, '3');
|
||||||
|
run_test(app, 10, '55');
|
||||||
|
run_test(app, 20, '6765');
|
||||||
|
run_test(app, 30, '832040');
|
||||||
|
run_test(app, 40, '102334155');
|
||||||
|
run_test(app, 50, '12586269025');
|
||||||
|
run_test(app, 60, '1548008755920');
|
||||||
|
run_test(app, 70, '190392490709135');
|
||||||
|
run_test(app, 80, '23416728348467685');
|
||||||
|
run_test(app, 90, '2880067194370816120');
|
||||||
|
run_test(app, 92, '7540113804746346429');
|
||||||
});
|
});
|
||||||
}
|
</script>
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -13,3 +13,7 @@ def fib(n: u64) -> u64:
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
return helper(n - 1, 0, 1)
|
return helper(n - 1, 0, 1)
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> u64:
|
||||||
|
return fib(40)
|
||||||
|
|||||||
48
examples/fold.html
Normal file
48
examples/fold.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Examples - Fold</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Fold</h1>
|
||||||
|
|
||||||
|
<a href="index.html">List</a> - <a href="fold.py.html">Source</a> - <a href="fold.wat.html">WebAssembly</a>
|
||||||
|
|
||||||
|
<div style="white-space: pre;" id="results"></div>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="./include.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
let importObject = {};
|
||||||
|
|
||||||
|
function run_test(app, data, expected)
|
||||||
|
{
|
||||||
|
let offset = alloc_bytes(app, data);
|
||||||
|
|
||||||
|
let actual = app.instance.exports.foldl_u8_or_1(offset);
|
||||||
|
|
||||||
|
test_result(expected == actual, {
|
||||||
|
'summary': 'foldl(or, 1, [' + data + ']) == ' + expected,
|
||||||
|
'attributes': {
|
||||||
|
'data': data,
|
||||||
|
'expected': expected,
|
||||||
|
'offset': offset,
|
||||||
|
'actual': actual,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
WebAssembly.instantiateStreaming(fetch('fold.wasm'), importObject)
|
||||||
|
.then(app => {
|
||||||
|
run_test(app, [], 1);
|
||||||
|
run_test(app, [0x20], 0x21);
|
||||||
|
run_test(app, [0x20, 0x10], 0x31);
|
||||||
|
run_test(app, [0x20, 0x10, 0x08], 0x39);
|
||||||
|
run_test(app, [0x20, 0x10, 0x08, 0x04], 0x3D);
|
||||||
|
run_test(app, [0x20, 0x10, 0x08, 0x04, 0x02], 0x3F);
|
||||||
|
run_test(app, [0x20, 0x10, 0x08, 0x04, 0x02, 0x01], 0x3F);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
6
examples/fold.py
Normal file
6
examples/fold.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
def u8_or(l: u8, r: u8) -> u8:
|
||||||
|
return l | r
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def foldl_u8_or_1(b: bytes) -> u8:
|
||||||
|
return foldl(u8_or, 1, b)
|
||||||
60
examples/imported.html
Normal file
60
examples/imported.html
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Examples - Imported</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Imported</h1>
|
||||||
|
|
||||||
|
<a href="index.html">List</a> - <a href="imported.py.html">Source</a> - <a href="imported.wat.html">WebAssembly</a>
|
||||||
|
|
||||||
|
<div style="white-space: pre;" id="results"></div>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="./include.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
let importObject = {
|
||||||
|
'imports': {
|
||||||
|
'log': log,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let log_result = [];
|
||||||
|
|
||||||
|
function log(inp)
|
||||||
|
{
|
||||||
|
log_result.push(inp);
|
||||||
|
}
|
||||||
|
|
||||||
|
function run_test(app, a, b)
|
||||||
|
{
|
||||||
|
log_result = [];
|
||||||
|
|
||||||
|
let expected = a * b;
|
||||||
|
|
||||||
|
app.instance.exports.mult_and_log(a, b);
|
||||||
|
|
||||||
|
let actual = log_result[0];
|
||||||
|
|
||||||
|
test_result(expected == actual, {
|
||||||
|
'summary': 'mult_and_log(' + a + ', ' + b + ') == ' + expected,
|
||||||
|
'attributes': {
|
||||||
|
'a': a,
|
||||||
|
'b': b,
|
||||||
|
'expected': expected,
|
||||||
|
'actual': actual,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
WebAssembly.instantiateStreaming(fetch('imported.wasm'), importObject)
|
||||||
|
.then(app => {
|
||||||
|
run_test(app, 1, 1);
|
||||||
|
run_test(app, 3, 5);
|
||||||
|
run_test(app, 8, 19);
|
||||||
|
run_test(app, 12, 127);
|
||||||
|
run_test(app, 79, 193);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
examples/imported.py
Normal file
7
examples/imported.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
@imported
|
||||||
|
def log(no: i32) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def mult_and_log(a: i32, b: i32) -> None:
|
||||||
|
return log(a * b)
|
||||||
@ -1,6 +1,3 @@
|
|||||||
/***
|
|
||||||
* Allocates the given string in the given application's memory
|
|
||||||
*/
|
|
||||||
function alloc_bytes(app, data)
|
function alloc_bytes(app, data)
|
||||||
{
|
{
|
||||||
let stdlib_types___alloc_bytes__ = app.instance.exports['stdlib.types.__alloc_bytes__']
|
let stdlib_types___alloc_bytes__ = app.instance.exports['stdlib.types.__alloc_bytes__']
|
||||||
@ -17,12 +14,85 @@ function alloc_bytes(app, data)
|
|||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function run_times(times, callback, tweak)
|
||||||
* WebAssembly's interface only gets you signed integers
|
|
||||||
*
|
|
||||||
* Getting unsigned values out requires some work.
|
|
||||||
*/
|
|
||||||
function i32_to_u32(n)
|
|
||||||
{
|
{
|
||||||
return n >>> 0;
|
let sum = 0;
|
||||||
|
let max = 0;
|
||||||
|
let min = 1000000000000000000;
|
||||||
|
let values = [];
|
||||||
|
for(let idx = 0; idx < times; idx += 1) {
|
||||||
|
if( tweak ) {
|
||||||
|
tweak();
|
||||||
|
}
|
||||||
|
|
||||||
|
const t0 = performance.now();
|
||||||
|
let result = callback();
|
||||||
|
const t1 = performance.now();
|
||||||
|
let time = t1 - t0;
|
||||||
|
sum += time;
|
||||||
|
values.push({'time': time, 'result': result});
|
||||||
|
max = max < time ? time : max;
|
||||||
|
min = min > time ? time : min;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'min': min,
|
||||||
|
'avg': sum / times,
|
||||||
|
'max': max,
|
||||||
|
'sum': sum,
|
||||||
|
'values': values,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_result(is_pass, data)
|
||||||
|
{
|
||||||
|
data = data || {};
|
||||||
|
|
||||||
|
let result_details = document.createElement('details');
|
||||||
|
|
||||||
|
let result_summary = document.createElement('summary');
|
||||||
|
result_summary.textContent =
|
||||||
|
(is_pass ? 'Test passed: ' : 'Test failed: ')
|
||||||
|
+ (data.summary ?? '(no summary)')
|
||||||
|
;
|
||||||
|
result_summary.setAttribute('style', is_pass ? 'background: green' : 'background: red');
|
||||||
|
result_details.appendChild(result_summary);
|
||||||
|
|
||||||
|
if( data.attributes ) {
|
||||||
|
result_table(data, result_details);
|
||||||
|
}
|
||||||
|
|
||||||
|
let results = document.getElementById('results');
|
||||||
|
results.appendChild(result_details);
|
||||||
|
}
|
||||||
|
|
||||||
|
function result_table(attributes, parent)
|
||||||
|
{
|
||||||
|
let table = document.createElement('table');
|
||||||
|
|
||||||
|
Object.keys(attributes).forEach(idx => {
|
||||||
|
let td0 = document.createElement('td');
|
||||||
|
td0.setAttribute('style', 'vertical-align: top;');
|
||||||
|
td0.textContent = idx;
|
||||||
|
let td1 = document.createElement('td');
|
||||||
|
if( typeof(attributes[idx]) == 'object' ) {
|
||||||
|
let result_details = document.createElement('details');
|
||||||
|
|
||||||
|
let result_summary = document.createElement('summary');
|
||||||
|
result_summary.textContent = 'Show me';
|
||||||
|
result_details.appendChild(result_summary);
|
||||||
|
|
||||||
|
result_table(attributes[idx], result_details);
|
||||||
|
|
||||||
|
td1.appendChild(result_details);
|
||||||
|
} else {
|
||||||
|
td1.textContent = attributes[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
let tr = document.createElement('tr');
|
||||||
|
tr.appendChild(td0);
|
||||||
|
tr.appendChild(td1);
|
||||||
|
|
||||||
|
table.appendChild(tr);
|
||||||
|
});
|
||||||
|
parent.append(table);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,19 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Examples</title>
|
<title>Examples</title>
|
||||||
<link rel="stylesheet" type="text/css" href="main.css">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Examples</h1>
|
<h1>Examples</h1>
|
||||||
|
<h2>Standard</h2>
|
||||||
<h2>Functions</h2>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="crc32.html">CRC32</a></li>
|
<li><a href="crc32.html">CRC32</a></li>
|
||||||
<li><a href="fib.html">Fibonacci</a></li>
|
<li><a href="fib.html">Fibonacci</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<h2>Technical</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="buffer.html">Buffer</a></li>
|
||||||
|
<li><a href="fold.html">Folding</a></li>
|
||||||
|
<li><a href="imported.html">Imported</a></li>
|
||||||
|
</ul>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
:root {
|
|
||||||
/* CSS HEX */
|
|
||||||
--seasalt: #fcfafaff;
|
|
||||||
--silver: #c8d3d5ff;
|
|
||||||
--powder-blue: #a4b8c4ff;
|
|
||||||
--slate-gray: #6e8387ff;
|
|
||||||
--dark-pastel-green: #0cca4aff;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: var(--seasalt);
|
|
||||||
color: var(--slate-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--dark-pastel-green);
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu {
|
|
||||||
border: 1px solid var(--powder-blue);
|
|
||||||
border-width: 1px 0px;
|
|
||||||
padding: 0.2em;
|
|
||||||
margin: 0.2em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
border-top: 1px solid var(--powder-blue);
|
|
||||||
padding-top: 0.3em;
|
|
||||||
}
|
|
||||||
@ -4,11 +4,8 @@ Functions for using this module from CLI
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from .compiler import phasm_compile
|
|
||||||
from .optimise.removeunusedfuncs import removeunusedfuncs
|
|
||||||
from .parser import phasm_parse
|
from .parser import phasm_parse
|
||||||
from .type5.solver import phasm_type5
|
from .compiler import phasm_compile
|
||||||
|
|
||||||
|
|
||||||
def main(source: str, sink: str) -> int:
|
def main(source: str, sink: str) -> int:
|
||||||
"""
|
"""
|
||||||
@ -19,9 +16,7 @@ def main(source: str, sink: str) -> int:
|
|||||||
code_py = fil.read()
|
code_py = fil.read()
|
||||||
|
|
||||||
our_module = phasm_parse(code_py)
|
our_module = phasm_parse(code_py)
|
||||||
phasm_type5(our_module, verbose=False)
|
|
||||||
wasm_module = phasm_compile(our_module)
|
wasm_module = phasm_compile(our_module)
|
||||||
removeunusedfuncs(wasm_module)
|
|
||||||
code_wat = wasm_module.to_wat()
|
code_wat = wasm_module.to_wat()
|
||||||
|
|
||||||
with open(sink, 'w') as fil:
|
with open(sink, 'w') as fil:
|
||||||
|
|||||||
@ -1,445 +0,0 @@
|
|||||||
"""
|
|
||||||
The base class for build environments.
|
|
||||||
|
|
||||||
Contains nothing but the explicit compiler builtins.
|
|
||||||
"""
|
|
||||||
from typing import NamedTuple, Protocol, Sequence, Type
|
|
||||||
|
|
||||||
from ..type5 import constrainedexpr as type5constrainedexpr
|
|
||||||
from ..type5 import kindexpr as type5kindexpr
|
|
||||||
from ..type5 import record as type5record
|
|
||||||
from ..type5 import typeexpr as type5typeexpr
|
|
||||||
from ..typeclass import TypeClass
|
|
||||||
from ..wasm import WasmType, WasmTypeInt32, WasmTypeNone
|
|
||||||
from .typeclassregistry import TypeClassRegistry
|
|
||||||
from .typerouter import TypeAllocSize, TypeName
|
|
||||||
|
|
||||||
TypeInfo = NamedTuple('TypeInfo', [
|
|
||||||
# Name of the type
|
|
||||||
('typ', str, ),
|
|
||||||
# What WebAssembly type to use when passing this value around
|
|
||||||
# For example in function arguments
|
|
||||||
('wasm_type', Type[WasmType]),
|
|
||||||
# What WebAssembly function to use when loading a value from memory
|
|
||||||
('wasm_load_func', str),
|
|
||||||
# What WebAssembly function to use when storing a value to memory
|
|
||||||
('wasm_store_func', str),
|
|
||||||
# When storing this value in memory, how many bytes do we use?
|
|
||||||
# Only valid for non-constructed types, see calculate_alloc_size
|
|
||||||
# Should match wasm_load_func / wasm_store_func
|
|
||||||
('alloc_size', int),
|
|
||||||
# When storing integers, the values can be stored as natural number
|
|
||||||
# (False) or as integer number (True). For other types, this is None.
|
|
||||||
('signed', bool | None),
|
|
||||||
])
|
|
||||||
|
|
||||||
class MissingImplementationWarning(Warning):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class InternalImplementedMethodProtocol[G](Protocol):
|
|
||||||
def __call__(self, g: G, tv_map: dict[str, type5typeexpr.TypeExpr]) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BuildBase[G]:
|
|
||||||
__slots__ = (
|
|
||||||
'dynamic_array_type5_constructor',
|
|
||||||
'function_type5_constructor',
|
|
||||||
'static_array_type5_constructor',
|
|
||||||
'tuple_type5_constructor_map',
|
|
||||||
|
|
||||||
'none_type5',
|
|
||||||
'unit_type5',
|
|
||||||
'bool_type5',
|
|
||||||
'u8_type5',
|
|
||||||
'u32_type5',
|
|
||||||
'bytes_type5',
|
|
||||||
|
|
||||||
'type_info_map',
|
|
||||||
'type_info_constructed',
|
|
||||||
|
|
||||||
'types',
|
|
||||||
'type_classes',
|
|
||||||
'type_class_instances',
|
|
||||||
'methods',
|
|
||||||
'operators',
|
|
||||||
|
|
||||||
'type5_name',
|
|
||||||
'type5_alloc_size_root',
|
|
||||||
'type5_alloc_size_member',
|
|
||||||
)
|
|
||||||
|
|
||||||
dynamic_array_type5_constructor: type5typeexpr.TypeConstructor
|
|
||||||
"""
|
|
||||||
Constructor for arrays of runtime deterined length.
|
|
||||||
|
|
||||||
See type5_make_dynamic_array and type5_is_dynamic_array.
|
|
||||||
"""
|
|
||||||
|
|
||||||
function_type5_constructor: type5typeexpr.TypeConstructor
|
|
||||||
"""
|
|
||||||
Constructor for functions.
|
|
||||||
|
|
||||||
See type5_make_function and type5_is_function.
|
|
||||||
"""
|
|
||||||
|
|
||||||
static_array_type5_constructor: type5typeexpr.TypeConstructor
|
|
||||||
"""
|
|
||||||
Constructor for arrays of compiled time determined length.
|
|
||||||
|
|
||||||
See type5_make_static_array and type5_is_static_array.
|
|
||||||
"""
|
|
||||||
|
|
||||||
tuple_type5_constructor_map: dict[int, type5typeexpr.TypeConstructor]
|
|
||||||
"""
|
|
||||||
Map for constructors for tuples of each length.
|
|
||||||
|
|
||||||
See type5_make_tuple and type5_is_tuple.
|
|
||||||
"""
|
|
||||||
|
|
||||||
none_type5: type5typeexpr.AtomicType
|
|
||||||
"""
|
|
||||||
The none type.
|
|
||||||
|
|
||||||
TODO: Not sure this should be a buildin (rather than a Maybe type).
|
|
||||||
"""
|
|
||||||
|
|
||||||
unit_type5: type5typeexpr.AtomicType
|
|
||||||
"""
|
|
||||||
The unit type has exactly one value and can always be constructed.
|
|
||||||
|
|
||||||
Use for functions that don't take any arguments or do not produce any result.
|
|
||||||
This only make sense for IO functions.
|
|
||||||
|
|
||||||
TODO: Is this not what Python calls None?
|
|
||||||
"""
|
|
||||||
|
|
||||||
bool_type5: type5typeexpr.AtomicType
|
|
||||||
"""
|
|
||||||
The bool type, either True or False.
|
|
||||||
|
|
||||||
Builtin since functions require a boolean value in their test.
|
|
||||||
"""
|
|
||||||
|
|
||||||
u8_type5: type5typeexpr.AtomicType
|
|
||||||
"""
|
|
||||||
The u8 type, an integer value between 0 and 255.
|
|
||||||
|
|
||||||
Builtin since we can have bytes literals - which are the same as u8[...].
|
|
||||||
"""
|
|
||||||
|
|
||||||
u32_type5: type5typeexpr.AtomicType
|
|
||||||
"""
|
|
||||||
The u32 type, an integer value between 0 and 4 294 967 295.
|
|
||||||
|
|
||||||
Builtin since we can use this for indexing arrays and since
|
|
||||||
we use this for the length prefix on dynamic arrays.
|
|
||||||
"""
|
|
||||||
|
|
||||||
bytes_type5: type5typeexpr.TypeApplication
|
|
||||||
"""
|
|
||||||
The bytes type, a dynamic array with u8 elements.
|
|
||||||
|
|
||||||
Builtin since we can have bytes literals.
|
|
||||||
"""
|
|
||||||
|
|
||||||
type_info_map: dict[str, TypeInfo]
|
|
||||||
"""
|
|
||||||
Map from type name to the info of that type
|
|
||||||
"""
|
|
||||||
|
|
||||||
type_info_constructed: TypeInfo
|
|
||||||
"""
|
|
||||||
By default, constructed types are passed as pointers
|
|
||||||
NOTE: ALLOC SIZE IN THIS STRUCT DOES NOT WORK FOR CONSTRUCTED TYPES
|
|
||||||
USE calculate_alloc_size FOR ACCURATE RESULTS
|
|
||||||
Functions count as constructed types - even though they are
|
|
||||||
not memory pointers but table addresses instead.
|
|
||||||
"""
|
|
||||||
|
|
||||||
types: dict[str, type5typeexpr.TypeExpr]
|
|
||||||
"""
|
|
||||||
Types that are available without explicit import.
|
|
||||||
"""
|
|
||||||
|
|
||||||
type_classes: dict[str, TypeClass]
|
|
||||||
"""
|
|
||||||
Type classes that are available without explicit import.
|
|
||||||
"""
|
|
||||||
|
|
||||||
type_class_instances: dict[str, TypeClassRegistry[bool]]
|
|
||||||
"""
|
|
||||||
Type class instances that are available without explicit import.
|
|
||||||
"""
|
|
||||||
|
|
||||||
methods: dict[str, tuple[type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr, TypeClassRegistry[InternalImplementedMethodProtocol[G]]]]
|
|
||||||
"""
|
|
||||||
Methods that are available without explicit import.
|
|
||||||
"""
|
|
||||||
|
|
||||||
operators: dict[str, tuple[type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr, TypeClassRegistry[InternalImplementedMethodProtocol[G]]]]
|
|
||||||
"""
|
|
||||||
Operators that are available without explicit import.
|
|
||||||
"""
|
|
||||||
|
|
||||||
type5_name: TypeName
|
|
||||||
"""
|
|
||||||
Helper router to turn types into their human readable names.
|
|
||||||
"""
|
|
||||||
|
|
||||||
type5_alloc_size_root: TypeAllocSize
|
|
||||||
"""
|
|
||||||
Helper router to turn types into their allocation sizes.
|
|
||||||
|
|
||||||
This calculates the value when allocated directly.
|
|
||||||
"""
|
|
||||||
|
|
||||||
type5_alloc_size_member: TypeAllocSize
|
|
||||||
"""
|
|
||||||
Helper router to turn types into their allocation sizes.
|
|
||||||
|
|
||||||
This calculates the value when allocated as a member, e.g. in a tuple or struct.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
S = type5kindexpr.Star()
|
|
||||||
N = type5kindexpr.Nat()
|
|
||||||
|
|
||||||
self.dynamic_array_type5_constructor = type5typeexpr.TypeConstructor(kind=S >> S, name="dynamic_array")
|
|
||||||
self.function_type5_constructor = type5typeexpr.TypeConstructor(kind=S >> (S >> S), name="function")
|
|
||||||
self.static_array_type5_constructor = type5typeexpr.TypeConstructor(kind=N >> (S >> S), name='static_array')
|
|
||||||
self.tuple_type5_constructor_map = {}
|
|
||||||
|
|
||||||
self.none_type5 = type5typeexpr.AtomicType('None')
|
|
||||||
self.unit_type5 = type5typeexpr.AtomicType('()')
|
|
||||||
self.bool_type5 = type5typeexpr.AtomicType('bool')
|
|
||||||
self.u8_type5 = type5typeexpr.AtomicType('u8')
|
|
||||||
self.u32_type5 = type5typeexpr.AtomicType('u32')
|
|
||||||
self.bytes_type5 = self.type5_make_dynamic_array(self.u8_type5)
|
|
||||||
|
|
||||||
self.type_info_map = {
|
|
||||||
'None': TypeInfo('None', WasmTypeNone, 'unreachable', 'unreachable', 0, None),
|
|
||||||
'()': TypeInfo('()', WasmTypeNone, 'unreachable', 'unreachable', 0, None),
|
|
||||||
'bool': TypeInfo('bool', WasmTypeInt32, 'unreachable', 'unreachable', 0, None),
|
|
||||||
'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1, False),
|
|
||||||
'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4, False),
|
|
||||||
}
|
|
||||||
self.type_info_constructed = TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4, False)
|
|
||||||
|
|
||||||
self.types = {
|
|
||||||
'None': self.none_type5,
|
|
||||||
'()': self.unit_type5,
|
|
||||||
'bool': self.bool_type5,
|
|
||||||
'u8': self.u8_type5,
|
|
||||||
'u32': self.u32_type5,
|
|
||||||
'bytes': self.bytes_type5,
|
|
||||||
}
|
|
||||||
self.type_classes = {}
|
|
||||||
self.type_class_instances = {}
|
|
||||||
self.methods = {}
|
|
||||||
self.operators = {}
|
|
||||||
|
|
||||||
self.type5_name = TypeName(self)
|
|
||||||
self.type5_alloc_size_root = TypeAllocSize(self, is_member=False)
|
|
||||||
self.type5_alloc_size_member = TypeAllocSize(self, is_member=True)
|
|
||||||
|
|
||||||
def register_type_class(self, cls: TypeClass) -> None:
|
|
||||||
assert cls.name not in self.type_classes, 'Duplicate typeclass name'
|
|
||||||
|
|
||||||
self.type_classes[cls.name] = cls
|
|
||||||
self.type_class_instances[cls.name] = TypeClassRegistry()
|
|
||||||
|
|
||||||
for mtd_nam, mtd_typ in cls.methods.items():
|
|
||||||
assert mtd_nam not in self.methods, 'Duplicate typeclass method name'
|
|
||||||
self.methods[mtd_nam] = (mtd_typ, TypeClassRegistry(), )
|
|
||||||
|
|
||||||
for opr_nam, opr_typ in cls.operators.items():
|
|
||||||
assert opr_nam not in self.operators, 'Duplicate typeclass operator name'
|
|
||||||
self.operators[opr_nam] = (opr_typ, TypeClassRegistry(), )
|
|
||||||
|
|
||||||
def instance_type_class(
|
|
||||||
self,
|
|
||||||
cls: TypeClass,
|
|
||||||
*args: type5typeexpr.TypeExpr,
|
|
||||||
methods: dict[str, InternalImplementedMethodProtocol[G]] = {},
|
|
||||||
operators: dict[str, InternalImplementedMethodProtocol[G]] = {},
|
|
||||||
) -> None:
|
|
||||||
self.type_class_instances[cls.name].add(args, True)
|
|
||||||
|
|
||||||
assert len(cls.variables) == len(args)
|
|
||||||
|
|
||||||
for mtd_nam, mtd_imp in methods.items():
|
|
||||||
mtd_typ, mtd_rtr = self.methods[mtd_nam]
|
|
||||||
|
|
||||||
if isinstance(mtd_typ, type5constrainedexpr.ConstrainedExpr):
|
|
||||||
mtd_typ = mtd_typ.expr
|
|
||||||
|
|
||||||
for var, rep_expr in zip(cls.variables, args, strict=True):
|
|
||||||
mtd_typ = type5typeexpr.replace_variable(mtd_typ, var, rep_expr)
|
|
||||||
|
|
||||||
mtd_rtr.add((mtd_typ, ), mtd_imp)
|
|
||||||
|
|
||||||
for opr_nam, opr_imp in operators.items():
|
|
||||||
mtd_typ, opr_rtr = self.operators[opr_nam]
|
|
||||||
|
|
||||||
if isinstance(mtd_typ, type5constrainedexpr.ConstrainedExpr):
|
|
||||||
mtd_typ = mtd_typ.expr
|
|
||||||
|
|
||||||
for var, rep_expr in zip(cls.variables, args, strict=True):
|
|
||||||
mtd_typ = type5typeexpr.replace_variable(mtd_typ, var, rep_expr)
|
|
||||||
|
|
||||||
opr_rtr.add((mtd_typ, ), opr_imp)
|
|
||||||
|
|
||||||
def type5_make_function(self, args: Sequence[type5typeexpr.TypeExpr]) -> type5typeexpr.TypeExpr:
|
|
||||||
if not args:
|
|
||||||
raise TypeError("Functions must at least have a return type")
|
|
||||||
|
|
||||||
if len(args) == 1:
|
|
||||||
# Functions always take an argument
|
|
||||||
# To distinguish between a function without arguments and a value
|
|
||||||
# of the type, we have a unit type
|
|
||||||
# This type has one value so it can always be called
|
|
||||||
args = [self.unit_type5, *args]
|
|
||||||
|
|
||||||
res_type5 = None
|
|
||||||
|
|
||||||
for arg_type5 in reversed(args):
|
|
||||||
if res_type5 is None:
|
|
||||||
res_type5 = arg_type5
|
|
||||||
continue
|
|
||||||
|
|
||||||
res_type5 = type5typeexpr.TypeApplication(
|
|
||||||
constructor=type5typeexpr.TypeApplication(
|
|
||||||
constructor=self.function_type5_constructor,
|
|
||||||
argument=arg_type5,
|
|
||||||
),
|
|
||||||
argument=res_type5,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert res_type5 is not None # type hint
|
|
||||||
|
|
||||||
return res_type5
|
|
||||||
|
|
||||||
def type5_is_function(self, typeexpr: type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr) -> list[type5typeexpr.TypeExpr] | None:
|
|
||||||
if isinstance(typeexpr, type5constrainedexpr.ConstrainedExpr):
|
|
||||||
typeexpr = typeexpr.expr
|
|
||||||
|
|
||||||
if not isinstance(typeexpr, type5typeexpr.TypeApplication):
|
|
||||||
return None
|
|
||||||
if not isinstance(typeexpr.constructor, type5typeexpr.TypeApplication):
|
|
||||||
return None
|
|
||||||
if typeexpr.constructor.constructor != self.function_type5_constructor:
|
|
||||||
return None
|
|
||||||
|
|
||||||
arg0 = typeexpr.constructor.argument
|
|
||||||
if arg0 is self.unit_type5:
|
|
||||||
my_args = []
|
|
||||||
else:
|
|
||||||
my_args = [arg0]
|
|
||||||
|
|
||||||
arg1 = typeexpr.argument
|
|
||||||
more_args = self.type5_is_function(arg1)
|
|
||||||
if more_args is None:
|
|
||||||
return my_args + [arg1]
|
|
||||||
|
|
||||||
return my_args + more_args
|
|
||||||
|
|
||||||
def type5_make_tuple(self, args: Sequence[type5typeexpr.TypeExpr]) -> type5typeexpr.TypeApplication:
|
|
||||||
if not args:
|
|
||||||
raise TypeError("Tuples must at least one field")
|
|
||||||
|
|
||||||
arlen = len(args)
|
|
||||||
constructor = self.tuple_type5_constructor_map.get(arlen)
|
|
||||||
if constructor is None:
|
|
||||||
star = type5kindexpr.Star()
|
|
||||||
|
|
||||||
kind: type5kindexpr.Arrow = star >> star
|
|
||||||
for _ in range(len(args) - 1):
|
|
||||||
kind = star >> kind
|
|
||||||
constructor = type5typeexpr.TypeConstructor(kind=kind, name=f'tuple_{arlen}')
|
|
||||||
self.tuple_type5_constructor_map[arlen] = constructor
|
|
||||||
|
|
||||||
result: type5typeexpr.TypeApplication | None = None
|
|
||||||
|
|
||||||
for arg in args:
|
|
||||||
if result is None:
|
|
||||||
result = type5typeexpr.TypeApplication(
|
|
||||||
constructor=constructor,
|
|
||||||
argument=arg
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
result = type5typeexpr.TypeApplication(
|
|
||||||
constructor=result,
|
|
||||||
argument=arg
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result is not None # type hint
|
|
||||||
return result
|
|
||||||
|
|
||||||
def type5_is_tuple(self, typeexpr: type5typeexpr.TypeExpr) -> list[type5typeexpr.TypeExpr] | None:
|
|
||||||
arg_list = []
|
|
||||||
|
|
||||||
while isinstance(typeexpr, type5typeexpr.TypeApplication):
|
|
||||||
arg_list.append(typeexpr.argument)
|
|
||||||
typeexpr = typeexpr.constructor
|
|
||||||
|
|
||||||
if not isinstance(typeexpr, type5typeexpr.TypeConstructor):
|
|
||||||
return None
|
|
||||||
|
|
||||||
if typeexpr not in self.tuple_type5_constructor_map.values():
|
|
||||||
return None
|
|
||||||
|
|
||||||
return list(reversed(arg_list))
|
|
||||||
|
|
||||||
def type5_make_struct(self, name: str, fields: tuple[tuple[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication], ...]) -> type5record.Record:
|
|
||||||
return type5record.Record(name, fields)
|
|
||||||
|
|
||||||
def type5_is_struct(self, arg: type5typeexpr.TypeExpr) -> tuple[tuple[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication], ...] | None:
|
|
||||||
if not isinstance(arg, type5record.Record):
|
|
||||||
return None
|
|
||||||
|
|
||||||
return arg.fields
|
|
||||||
|
|
||||||
def type5_make_dynamic_array(self, arg: type5typeexpr.TypeExpr) -> type5typeexpr.TypeApplication:
|
|
||||||
return type5typeexpr.TypeApplication(
|
|
||||||
constructor=self.dynamic_array_type5_constructor,
|
|
||||||
argument=arg,
|
|
||||||
)
|
|
||||||
|
|
||||||
def type5_is_dynamic_array(self, typeexpr: type5typeexpr.TypeExpr) -> type5typeexpr.TypeExpr | None:
|
|
||||||
"""
|
|
||||||
Check if the given type expr is a concrete dynamic array type.
|
|
||||||
|
|
||||||
The element argument type is returned if so. Else, None is returned.
|
|
||||||
"""
|
|
||||||
if not isinstance(typeexpr, type5typeexpr.TypeApplication):
|
|
||||||
return None
|
|
||||||
if typeexpr.constructor != self.dynamic_array_type5_constructor:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return typeexpr.argument
|
|
||||||
|
|
||||||
def type5_make_static_array(self, len: int, arg: type5typeexpr.TypeExpr) -> type5typeexpr.TypeApplication:
|
|
||||||
return type5typeexpr.TypeApplication(
|
|
||||||
constructor=type5typeexpr.TypeApplication(
|
|
||||||
constructor=self.static_array_type5_constructor,
|
|
||||||
argument=type5typeexpr.TypeLevelNat(len),
|
|
||||||
),
|
|
||||||
argument=arg,
|
|
||||||
)
|
|
||||||
|
|
||||||
def type5_is_static_array(self, typeexpr: type5typeexpr.TypeExpr) -> tuple[int, type5typeexpr.TypeExpr] | None:
|
|
||||||
if not isinstance(typeexpr, type5typeexpr.TypeApplication):
|
|
||||||
return None
|
|
||||||
if not isinstance(typeexpr.constructor, type5typeexpr.TypeApplication):
|
|
||||||
return None
|
|
||||||
if typeexpr.constructor.constructor != self.static_array_type5_constructor:
|
|
||||||
return None
|
|
||||||
|
|
||||||
assert isinstance(typeexpr.constructor.argument, type5typeexpr.TypeLevelNat) # type hint
|
|
||||||
|
|
||||||
return (
|
|
||||||
typeexpr.constructor.argument.value,
|
|
||||||
typeexpr.argument,
|
|
||||||
)
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
"""
|
|
||||||
The default class for build environments.
|
|
||||||
|
|
||||||
Contains the compiler builtins as well as some sane defaults.
|
|
||||||
"""
|
|
||||||
from ..type5 import typeexpr as type5typeexpr
|
|
||||||
from ..wasm import (
|
|
||||||
WasmTypeFloat32,
|
|
||||||
WasmTypeFloat64,
|
|
||||||
WasmTypeInt32,
|
|
||||||
WasmTypeInt64,
|
|
||||||
)
|
|
||||||
from ..wasmgenerator import Generator
|
|
||||||
from .base import BuildBase, TypeInfo
|
|
||||||
from .typeclasses import (
|
|
||||||
bits,
|
|
||||||
convertable,
|
|
||||||
eq,
|
|
||||||
extendable,
|
|
||||||
floating,
|
|
||||||
foldable,
|
|
||||||
fractional,
|
|
||||||
integral,
|
|
||||||
intnum,
|
|
||||||
natnum,
|
|
||||||
ord,
|
|
||||||
promotable,
|
|
||||||
reinterpretable,
|
|
||||||
sized,
|
|
||||||
subscriptable,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BuildDefault(BuildBase[Generator]):
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.type_info_map.update({
|
|
||||||
'u16': TypeInfo('u16', WasmTypeInt32, 'i32.load16_u', 'i32.store16', 2, False),
|
|
||||||
'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8, False),
|
|
||||||
'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1, True),
|
|
||||||
'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16', 2, True),
|
|
||||||
'i32': TypeInfo('i32', WasmTypeInt32, 'i32.load', 'i32.store', 4, True),
|
|
||||||
'i64': TypeInfo('i64', WasmTypeInt64, 'i64.load', 'i64.store', 8, True),
|
|
||||||
'f32': TypeInfo('f32', WasmTypeFloat32, 'f32.load', 'f32.store', 4, None),
|
|
||||||
'f64': TypeInfo('f64', WasmTypeFloat64, 'f64.load', 'f64.store', 8, None),
|
|
||||||
})
|
|
||||||
|
|
||||||
self.types.update({
|
|
||||||
'u16': type5typeexpr.AtomicType('u16'),
|
|
||||||
'u64': type5typeexpr.AtomicType('u64'),
|
|
||||||
'i8': type5typeexpr.AtomicType('i8'),
|
|
||||||
'i16': type5typeexpr.AtomicType('i16'),
|
|
||||||
'i32': type5typeexpr.AtomicType('i32'),
|
|
||||||
'i64': type5typeexpr.AtomicType('i64'),
|
|
||||||
'f32': type5typeexpr.AtomicType('f32'),
|
|
||||||
'f64': type5typeexpr.AtomicType('f64'),
|
|
||||||
})
|
|
||||||
|
|
||||||
tc_list = [
|
|
||||||
bits,
|
|
||||||
eq, ord,
|
|
||||||
extendable, promotable,
|
|
||||||
convertable, reinterpretable,
|
|
||||||
natnum, intnum, fractional, floating,
|
|
||||||
integral,
|
|
||||||
foldable, subscriptable,
|
|
||||||
sized,
|
|
||||||
]
|
|
||||||
|
|
||||||
for tc in tc_list:
|
|
||||||
tc.load(self)
|
|
||||||
|
|
||||||
for tc in tc_list:
|
|
||||||
tc.wasm(self)
|
|
||||||
@ -1,214 +0,0 @@
|
|||||||
"""
|
|
||||||
The Bits type class is defined for types that can be bit manipulated.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ...type5.constrainedexpr import ConstrainedExpr
|
|
||||||
from ...type5.kindexpr import Star
|
|
||||||
from ...type5.typeexpr import TypeVariable
|
|
||||||
from ...typeclass import TypeClass, TypeClassConstraint
|
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
|
||||||
from ..base import BuildBase
|
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
|
||||||
u32 = build.types['u32']
|
|
||||||
|
|
||||||
Bits = TypeClass('Bits', (a, ), methods={}, operators={})
|
|
||||||
|
|
||||||
has_bits_a = TypeClassConstraint(Bits, [a])
|
|
||||||
|
|
||||||
fn_a_u32_a = ConstrainedExpr(
|
|
||||||
variables={a},
|
|
||||||
expr=build.type5_make_function([a, u32, a]),
|
|
||||||
constraints=(has_bits_a, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
fn_a_a_a = ConstrainedExpr(
|
|
||||||
variables={a},
|
|
||||||
expr=build.type5_make_function([a, a, a]),
|
|
||||||
constraints=(has_bits_a, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
Bits.methods = {
|
|
||||||
'shl': fn_a_u32_a, # Logical shift left
|
|
||||||
'shr': fn_a_u32_a, # Logical shift right
|
|
||||||
'rotl': fn_a_u32_a, # Rotate bits left
|
|
||||||
'rotr': fn_a_u32_a, # Rotate bits right
|
|
||||||
# FIXME: Do we want to expose clz, ctz, popcnt?
|
|
||||||
}
|
|
||||||
|
|
||||||
Bits.operators = {
|
|
||||||
'&': fn_a_a_a, # Bit-wise and
|
|
||||||
'|': fn_a_a_a, # Bit-wise or
|
|
||||||
'^': fn_a_a_a, # Bit-wise xor
|
|
||||||
}
|
|
||||||
|
|
||||||
build.register_type_class(Bits)
|
|
||||||
|
|
||||||
def wasm_u8_logical_shift_left(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.shl()
|
|
||||||
g.i32.const(0xFF)
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
def wasm_u16_logical_shift_left(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.shl()
|
|
||||||
g.i32.const(0xFFFF)
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
def wasm_u32_logical_shift_left(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.shl()
|
|
||||||
|
|
||||||
def wasm_u64_logical_shift_left(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.extend_i32_u()
|
|
||||||
g.i64.shl()
|
|
||||||
|
|
||||||
def wasm_u8_logical_shift_right(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.shr_u()
|
|
||||||
|
|
||||||
def wasm_u16_logical_shift_right(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.shr_u()
|
|
||||||
|
|
||||||
def wasm_u32_logical_shift_right(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.shr_u()
|
|
||||||
|
|
||||||
def wasm_u64_logical_shift_right(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.extend_i32_u()
|
|
||||||
g.i64.shr_u()
|
|
||||||
|
|
||||||
def wasm_u8_rotate_left(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u8_rotl__')
|
|
||||||
|
|
||||||
def wasm_u16_rotate_left(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u16_rotl__')
|
|
||||||
|
|
||||||
def wasm_u32_rotate_left(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.rotl()
|
|
||||||
|
|
||||||
def wasm_u64_rotate_left(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.extend_i32_u()
|
|
||||||
g.i64.rotl()
|
|
||||||
|
|
||||||
def wasm_u8_rotate_right(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u8_rotr__')
|
|
||||||
|
|
||||||
def wasm_u16_rotate_right(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u16_rotr__')
|
|
||||||
|
|
||||||
def wasm_u32_rotate_right(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.rotr()
|
|
||||||
|
|
||||||
def wasm_u64_rotate_right(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.extend_i32_u()
|
|
||||||
g.i64.rotr()
|
|
||||||
|
|
||||||
def wasm_u8_bitwise_and(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
def wasm_u16_bitwise_and(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
def wasm_u32_bitwise_and(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
def wasm_u64_bitwise_and(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.and_()
|
|
||||||
|
|
||||||
def wasm_u8_bitwise_or(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.or_()
|
|
||||||
|
|
||||||
def wasm_u16_bitwise_or(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.or_()
|
|
||||||
|
|
||||||
def wasm_u32_bitwise_or(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.or_()
|
|
||||||
|
|
||||||
def wasm_u64_bitwise_or(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.or_()
|
|
||||||
|
|
||||||
def wasm_u8_bitwise_xor(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.xor()
|
|
||||||
|
|
||||||
def wasm_u16_bitwise_xor(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.xor()
|
|
||||||
|
|
||||||
def wasm_u32_bitwise_xor(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.xor()
|
|
||||||
|
|
||||||
def wasm_u64_bitwise_xor(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.xor()
|
|
||||||
|
|
||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
Bits = build.type_classes['Bits']
|
|
||||||
|
|
||||||
build.instance_type_class(Bits, build.types['u8'], methods={
|
|
||||||
'shl': wasm_u8_logical_shift_left,
|
|
||||||
'shr': wasm_u8_logical_shift_right,
|
|
||||||
'rotl': wasm_u8_rotate_left,
|
|
||||||
'rotr': wasm_u8_rotate_right,
|
|
||||||
}, operators={
|
|
||||||
'&': wasm_u8_bitwise_and,
|
|
||||||
'|': wasm_u8_bitwise_or,
|
|
||||||
'^': wasm_u8_bitwise_xor,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Bits, build.types['u16'], methods={
|
|
||||||
'shl': wasm_u16_logical_shift_left,
|
|
||||||
'shr': wasm_u16_logical_shift_right,
|
|
||||||
'rotl': wasm_u16_rotate_left,
|
|
||||||
'rotr': wasm_u16_rotate_right,
|
|
||||||
}, operators={
|
|
||||||
'&': wasm_u16_bitwise_and,
|
|
||||||
'|': wasm_u16_bitwise_or,
|
|
||||||
'^': wasm_u16_bitwise_xor,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Bits, build.types['u32'], methods={
|
|
||||||
'shl': wasm_u32_logical_shift_left,
|
|
||||||
'shr': wasm_u32_logical_shift_right,
|
|
||||||
'rotl': wasm_u32_rotate_left,
|
|
||||||
'rotr': wasm_u32_rotate_right,
|
|
||||||
}, operators={
|
|
||||||
'&': wasm_u32_bitwise_and,
|
|
||||||
'|': wasm_u32_bitwise_or,
|
|
||||||
'^': wasm_u32_bitwise_xor,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Bits, build.types['u64'], methods={
|
|
||||||
'shl': wasm_u64_logical_shift_left,
|
|
||||||
'shr': wasm_u64_logical_shift_right,
|
|
||||||
'rotl': wasm_u64_rotate_left,
|
|
||||||
'rotr': wasm_u64_rotate_right,
|
|
||||||
}, operators={
|
|
||||||
'&': wasm_u64_bitwise_and,
|
|
||||||
'|': wasm_u64_bitwise_or,
|
|
||||||
'^': wasm_u64_bitwise_xor,
|
|
||||||
})
|
|
||||||
@ -1,143 +0,0 @@
|
|||||||
"""
|
|
||||||
The Convertable type class is defined for when a value from one type can be
|
|
||||||
converted to another type - but there's no real guarantee about precision or
|
|
||||||
value loss.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ...type5.constrainedexpr import ConstrainedExpr
|
|
||||||
from ...type5.kindexpr import Star
|
|
||||||
from ...type5.typeexpr import TypeVariable
|
|
||||||
from ...typeclass import TypeClass, TypeClassConstraint
|
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
|
||||||
from ..base import BuildBase
|
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
|
||||||
b = TypeVariable(kind=Star(), name='b')
|
|
||||||
|
|
||||||
Convertable = TypeClass('Convertable', (a, b, ), methods={}, operators={})
|
|
||||||
|
|
||||||
has_convertable_a_b = TypeClassConstraint(Convertable, [a, b])
|
|
||||||
|
|
||||||
fn_a_b = ConstrainedExpr(
|
|
||||||
variables={a, b},
|
|
||||||
expr=build.type5_make_function([a, b]),
|
|
||||||
constraints=(has_convertable_a_b, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
fn_b_a = ConstrainedExpr(
|
|
||||||
variables={a, b},
|
|
||||||
expr=build.type5_make_function([b, a]),
|
|
||||||
constraints=(has_convertable_a_b, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
Convertable.methods = {
|
|
||||||
'convert': fn_a_b,
|
|
||||||
'truncate': fn_b_a, # To prevent name clas with Fractional
|
|
||||||
}
|
|
||||||
|
|
||||||
build.register_type_class(Convertable)
|
|
||||||
|
|
||||||
def wasm_u32_f32_convert(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.convert_i32_u()
|
|
||||||
|
|
||||||
def wasm_u32_f64_convert(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.convert_i32_u()
|
|
||||||
|
|
||||||
def wasm_u64_f32_convert(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.convert_i64_u()
|
|
||||||
|
|
||||||
def wasm_u64_f64_convert(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.convert_i64_u()
|
|
||||||
|
|
||||||
def wasm_i32_f32_convert(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.convert_i32_s()
|
|
||||||
|
|
||||||
def wasm_i32_f64_convert(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.convert_i32_s()
|
|
||||||
|
|
||||||
def wasm_i64_f32_convert(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.convert_i64_s()
|
|
||||||
|
|
||||||
def wasm_i64_f64_convert(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.convert_i64_s()
|
|
||||||
|
|
||||||
def wasm_u32_f32_truncate(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.trunc_f32_u()
|
|
||||||
|
|
||||||
def wasm_u32_f64_truncate(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.trunc_f64_u()
|
|
||||||
|
|
||||||
def wasm_u64_f32_truncate(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.trunc_f32_u()
|
|
||||||
|
|
||||||
def wasm_u64_f64_truncate(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.trunc_f64_u()
|
|
||||||
|
|
||||||
def wasm_i32_f32_truncate(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.trunc_f32_s()
|
|
||||||
|
|
||||||
def wasm_i32_f64_truncate(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.trunc_f64_s()
|
|
||||||
|
|
||||||
def wasm_i64_f32_truncate(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.trunc_f32_s()
|
|
||||||
|
|
||||||
def wasm_i64_f64_truncate(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.trunc_f64_s()
|
|
||||||
|
|
||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
Convertable = build.type_classes['Convertable']
|
|
||||||
|
|
||||||
build.instance_type_class(Convertable, build.types['u32'], build.types['f32'], methods={
|
|
||||||
'convert': wasm_u32_f32_convert,
|
|
||||||
'truncate': wasm_u32_f32_truncate,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Convertable, build.types['u32'], build.types['f64'], methods={
|
|
||||||
'convert': wasm_u32_f64_convert,
|
|
||||||
'truncate': wasm_u32_f64_truncate,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Convertable, build.types['u64'], build.types['f32'], methods={
|
|
||||||
'convert': wasm_u64_f32_convert,
|
|
||||||
'truncate': wasm_u64_f32_truncate,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Convertable, build.types['u64'], build.types['f64'], methods={
|
|
||||||
'convert': wasm_u64_f64_convert,
|
|
||||||
'truncate': wasm_u64_f64_truncate,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Convertable, build.types['i32'], build.types['f32'], methods={
|
|
||||||
'convert': wasm_i32_f32_convert,
|
|
||||||
'truncate': wasm_i32_f32_truncate,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Convertable, build.types['i32'], build.types['f64'], methods={
|
|
||||||
'convert': wasm_i32_f64_convert,
|
|
||||||
'truncate': wasm_i32_f64_truncate,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Convertable, build.types['i64'], build.types['f32'], methods={
|
|
||||||
'convert': wasm_i64_f32_convert,
|
|
||||||
'truncate': wasm_i64_f32_truncate,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Convertable, build.types['i64'], build.types['f64'], methods={
|
|
||||||
'convert': wasm_i64_f64_convert,
|
|
||||||
'truncate': wasm_i64_f64_truncate,
|
|
||||||
})
|
|
||||||
@ -1,159 +0,0 @@
|
|||||||
"""
|
|
||||||
The Eq type class is defined for types that can be compered based on equality.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ...type5.constrainedexpr import ConstrainedExpr
|
|
||||||
from ...type5.kindexpr import Star
|
|
||||||
from ...type5.typeexpr import TypeVariable
|
|
||||||
from ...typeclass import TypeClass, TypeClassConstraint
|
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
|
||||||
from ..base import BuildBase
|
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
|
||||||
|
|
||||||
Eq = TypeClass('Eq', (a, ), methods={}, operators={})
|
|
||||||
|
|
||||||
has_eq_a = TypeClassConstraint(Eq, [a])
|
|
||||||
|
|
||||||
fn_a_a_bool = ConstrainedExpr(
|
|
||||||
variables={a},
|
|
||||||
expr=build.type5_make_function([a, a, build.bool_type5]),
|
|
||||||
constraints=(has_eq_a, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
Eq.operators = {
|
|
||||||
'==': fn_a_a_bool,
|
|
||||||
'!=': fn_a_a_bool,
|
|
||||||
# FIXME: Do we want to expose 'eqz'? Or is that a compiler optimization?
|
|
||||||
}
|
|
||||||
|
|
||||||
build.register_type_class(Eq)
|
|
||||||
|
|
||||||
def wasm_u8_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.eq()
|
|
||||||
|
|
||||||
def wasm_u16_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.eq()
|
|
||||||
|
|
||||||
def wasm_u32_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.eq()
|
|
||||||
|
|
||||||
def wasm_u64_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.eq()
|
|
||||||
|
|
||||||
def wasm_i8_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.eq()
|
|
||||||
|
|
||||||
def wasm_i16_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.eq()
|
|
||||||
|
|
||||||
def wasm_i32_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.eq()
|
|
||||||
|
|
||||||
def wasm_i64_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.eq()
|
|
||||||
|
|
||||||
def wasm_f32_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.eq()
|
|
||||||
|
|
||||||
def wasm_f64_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.eq()
|
|
||||||
|
|
||||||
def wasm_u8_not_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.ne()
|
|
||||||
|
|
||||||
def wasm_u16_not_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.ne()
|
|
||||||
|
|
||||||
def wasm_u32_not_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.ne()
|
|
||||||
|
|
||||||
def wasm_u64_not_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.ne()
|
|
||||||
|
|
||||||
def wasm_i8_not_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.ne()
|
|
||||||
|
|
||||||
def wasm_i16_not_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.ne()
|
|
||||||
|
|
||||||
def wasm_i32_not_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.ne()
|
|
||||||
|
|
||||||
def wasm_i64_not_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.ne()
|
|
||||||
|
|
||||||
def wasm_f32_not_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.ne()
|
|
||||||
|
|
||||||
def wasm_f64_not_equals(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.ne()
|
|
||||||
|
|
||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
Eq = build.type_classes['Eq']
|
|
||||||
|
|
||||||
build.instance_type_class(Eq, build.types['u8'], operators={
|
|
||||||
'==': wasm_u8_equals,
|
|
||||||
'!=': wasm_u8_not_equals,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Eq, build.types['u16'], operators={
|
|
||||||
'==': wasm_u16_equals,
|
|
||||||
'!=': wasm_u16_not_equals,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Eq, build.types['u32'], operators={
|
|
||||||
'==': wasm_u32_equals,
|
|
||||||
'!=': wasm_u32_not_equals,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Eq, build.types['u64'], operators={
|
|
||||||
'==': wasm_u64_equals,
|
|
||||||
'!=': wasm_u64_not_equals,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Eq, build.types['i8'], operators={
|
|
||||||
'==': wasm_i8_equals,
|
|
||||||
'!=': wasm_i8_not_equals,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Eq, build.types['i16'], operators={
|
|
||||||
'==': wasm_i16_equals,
|
|
||||||
'!=': wasm_i16_not_equals,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Eq, build.types['i32'], operators={
|
|
||||||
'==': wasm_i32_equals,
|
|
||||||
'!=': wasm_i32_not_equals,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Eq, build.types['i64'], operators={
|
|
||||||
'==': wasm_i64_equals,
|
|
||||||
'!=': wasm_i64_not_equals,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Eq, build.types['f32'], operators={
|
|
||||||
'==': wasm_f32_equals,
|
|
||||||
'!=': wasm_f32_not_equals,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Eq, build.types['f64'], operators={
|
|
||||||
'==': wasm_f64_equals,
|
|
||||||
'!=': wasm_f64_not_equals,
|
|
||||||
})
|
|
||||||
@ -1,216 +0,0 @@
|
|||||||
"""
|
|
||||||
The Extendable type class is defined for types that can safely be extended to a type
|
|
||||||
that can hold strictly more values. Going back will result in some values being lost.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ...type5.constrainedexpr import ConstrainedExpr
|
|
||||||
from ...type5.kindexpr import Star
|
|
||||||
from ...type5.typeexpr import TypeVariable
|
|
||||||
from ...typeclass import TypeClass, TypeClassConstraint
|
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
|
||||||
from ..base import BuildBase
|
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
|
||||||
b = TypeVariable(kind=Star(), name='b')
|
|
||||||
|
|
||||||
Extendable = TypeClass('Extendable', (a, b, ), methods={}, operators={})
|
|
||||||
|
|
||||||
has_extendable_a_b = TypeClassConstraint(Extendable, [a, b])
|
|
||||||
|
|
||||||
fn_a_b = ConstrainedExpr(
|
|
||||||
variables={a, b},
|
|
||||||
expr=build.type5_make_function([a, b]),
|
|
||||||
constraints=(has_extendable_a_b, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
fn_b_a = ConstrainedExpr(
|
|
||||||
variables={a, b},
|
|
||||||
expr=build.type5_make_function([b, a]),
|
|
||||||
constraints=(has_extendable_a_b, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
Extendable.methods = {
|
|
||||||
'extend': fn_a_b,
|
|
||||||
'wrap': fn_b_a,
|
|
||||||
}
|
|
||||||
|
|
||||||
build.register_type_class(Extendable)
|
|
||||||
|
|
||||||
def wasm_u8_u16_extend(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
# No-op
|
|
||||||
# u8 and u16 are both stored as u32
|
|
||||||
pass
|
|
||||||
|
|
||||||
def wasm_u8_u32_extend(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
# No-op
|
|
||||||
# u8 is already stored as u32
|
|
||||||
pass
|
|
||||||
|
|
||||||
def wasm_u8_u64_extend(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.extend_i32_u()
|
|
||||||
|
|
||||||
def wasm_u16_u32_extend(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
# No-op
|
|
||||||
# u16 is already stored as u32
|
|
||||||
pass
|
|
||||||
|
|
||||||
def wasm_u16_u64_extend(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.extend_i32_u()
|
|
||||||
|
|
||||||
def wasm_u32_u64_extend(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.extend_i32_u()
|
|
||||||
|
|
||||||
def wasm_i8_i16_extend(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
# No-op
|
|
||||||
# i8 is already stored as i32
|
|
||||||
pass
|
|
||||||
|
|
||||||
def wasm_i8_i32_extend(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
# No-op
|
|
||||||
# i8 is already stored as i32
|
|
||||||
pass
|
|
||||||
|
|
||||||
def wasm_i8_i64_extend(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.extend_i32_s()
|
|
||||||
|
|
||||||
def wasm_i16_i32_extend(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
# No-op
|
|
||||||
# i16 is already stored as i32
|
|
||||||
pass
|
|
||||||
|
|
||||||
def wasm_i16_i64_extend(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.extend_i32_s()
|
|
||||||
|
|
||||||
def wasm_i32_i64_extend(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.extend_i32_s()
|
|
||||||
|
|
||||||
def wasm_u8_u16_wrap(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.const(0xFF)
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
def wasm_u8_u32_wrap(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.const(0xFF)
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
def wasm_u8_u64_wrap(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.wrap_i64()
|
|
||||||
g.i32.const(0xFF)
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
def wasm_u16_u32_wrap(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.const(0xFFFF)
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
def wasm_u16_u64_wrap(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.wrap_i64()
|
|
||||||
g.i32.const(0xFFFF)
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
def wasm_u32_u64_wrap(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.wrap_i64()
|
|
||||||
|
|
||||||
def wasm_i8_i16_wrap(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.const(0xFF)
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
def wasm_i8_i32_wrap(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.const(0xFF)
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
def wasm_i8_i64_wrap(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.wrap_i64()
|
|
||||||
g.i32.const(0xFF)
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
def wasm_i16_i32_wrap(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.const(0xFFFF)
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
def wasm_i16_i64_wrap(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.wrap_i64()
|
|
||||||
g.i32.const(0xFFFF)
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
def wasm_i32_i64_wrap(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.wrap_i64()
|
|
||||||
|
|
||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
Extendable = build.type_classes['Extendable']
|
|
||||||
|
|
||||||
build.instance_type_class(Extendable, build.types['u8'], build.types['u16'], methods={
|
|
||||||
'extend': wasm_u8_u16_extend,
|
|
||||||
'wrap': wasm_u8_u16_wrap,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Extendable, build.types['u8'], build.types['u32'], methods={
|
|
||||||
'extend': wasm_u8_u32_extend,
|
|
||||||
'wrap': wasm_u8_u32_wrap,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Extendable, build.types['u8'], build.types['u64'], methods={
|
|
||||||
'extend': wasm_u8_u64_extend,
|
|
||||||
'wrap': wasm_u8_u64_wrap,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Extendable, build.types['u16'], build.types['u32'], methods={
|
|
||||||
'extend': wasm_u16_u32_extend,
|
|
||||||
'wrap': wasm_u16_u32_wrap,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Extendable, build.types['u16'], build.types['u64'], methods={
|
|
||||||
'extend': wasm_u16_u64_extend,
|
|
||||||
'wrap': wasm_u16_u64_wrap,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Extendable, build.types['u32'], build.types['u64'], methods={
|
|
||||||
'extend': wasm_u32_u64_extend,
|
|
||||||
'wrap': wasm_u32_u64_wrap,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Extendable, build.types['i8'], build.types['i16'], methods={
|
|
||||||
'extend': wasm_i8_i16_extend,
|
|
||||||
'wrap': wasm_i8_i16_wrap,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Extendable, build.types['i8'], build.types['i32'], methods={
|
|
||||||
'extend': wasm_i8_i32_extend,
|
|
||||||
'wrap': wasm_i8_i32_wrap,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Extendable, build.types['i8'], build.types['i64'], methods={
|
|
||||||
'extend': wasm_i8_i64_extend,
|
|
||||||
'wrap': wasm_i8_i64_wrap,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Extendable, build.types['i16'], build.types['i32'], methods={
|
|
||||||
'extend': wasm_i16_i32_extend,
|
|
||||||
'wrap': wasm_i16_i32_wrap,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Extendable, build.types['i16'], build.types['i64'], methods={
|
|
||||||
'extend': wasm_i16_i64_extend,
|
|
||||||
'wrap': wasm_i16_i64_wrap,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Extendable, build.types['i32'], build.types['i64'], methods={
|
|
||||||
'extend': wasm_i32_i64_extend,
|
|
||||||
'wrap': wasm_i32_i64_wrap,
|
|
||||||
})
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
"""
|
|
||||||
The Floating type class is defined for Real numbers.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ...type5.constrainedexpr import ConstrainedExpr
|
|
||||||
from ...type5.kindexpr import Star
|
|
||||||
from ...type5.typeexpr import TypeVariable
|
|
||||||
from ...typeclass import TypeClass, TypeClassConstraint
|
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
|
||||||
from ..base import BuildBase
|
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
|
||||||
|
|
||||||
Floating = TypeClass('Floating', (a, ), methods={}, operators={})
|
|
||||||
|
|
||||||
has_floating_a = TypeClassConstraint(Floating, [a])
|
|
||||||
|
|
||||||
fn_a_a = ConstrainedExpr(
|
|
||||||
variables={a},
|
|
||||||
expr=build.type5_make_function([a, a]),
|
|
||||||
constraints=(has_floating_a, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
Floating.methods = {
|
|
||||||
'sqrt': fn_a_a
|
|
||||||
}
|
|
||||||
|
|
||||||
# FIXME: inherited_classes=[Fractional]
|
|
||||||
# FIXME: Do we want to expose copysign?
|
|
||||||
|
|
||||||
build.register_type_class(Floating)
|
|
||||||
|
|
||||||
def wasm_f32_sqrt(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('f32.sqrt')
|
|
||||||
|
|
||||||
def wasm_f64_sqrt(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('f64.sqrt')
|
|
||||||
|
|
||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
Floating = build.type_classes['Floating']
|
|
||||||
|
|
||||||
build.instance_type_class(Floating, build.types['f32'], methods={
|
|
||||||
'sqrt': wasm_f32_sqrt,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Floating, build.types['f64'], methods={
|
|
||||||
'sqrt': wasm_f64_sqrt,
|
|
||||||
})
|
|
||||||
@ -1,643 +0,0 @@
|
|||||||
"""
|
|
||||||
The Foldable type class is defined for when a value iterated over.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ...type5.constrainedexpr import ConstrainedExpr
|
|
||||||
from ...type5.kindexpr import Nat, Star
|
|
||||||
from ...type5.typeexpr import (
|
|
||||||
TypeApplication,
|
|
||||||
TypeExpr,
|
|
||||||
TypeLevelNat,
|
|
||||||
TypeVariable,
|
|
||||||
replace_variable,
|
|
||||||
)
|
|
||||||
from ...typeclass import TypeClass, TypeClassConstraint
|
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
|
||||||
from ..base import BuildBase, InternalImplementedMethodProtocol
|
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
|
||||||
b = TypeVariable(kind=Star(), name='b')
|
|
||||||
t = TypeVariable(kind=Star() >> Star(), name='t')
|
|
||||||
t_a = TypeApplication(constructor=t, argument=a)
|
|
||||||
|
|
||||||
NatNum = build.type_classes['NatNum']
|
|
||||||
|
|
||||||
Foldable = TypeClass('Foldable', (t, ), methods={}, operators={})
|
|
||||||
|
|
||||||
has_foldable_t = TypeClassConstraint(Foldable, [t])
|
|
||||||
has_natnum_a = TypeClassConstraint(NatNum, [a])
|
|
||||||
|
|
||||||
fn_sum = ConstrainedExpr(
|
|
||||||
variables={t, a},
|
|
||||||
expr=build.type5_make_function([t_a, a]),
|
|
||||||
constraints=(has_foldable_t, has_natnum_a, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
fn_b_a_b = build.type5_make_function([b, a, b])
|
|
||||||
fn_foldl = ConstrainedExpr(
|
|
||||||
variables={t, a, b},
|
|
||||||
expr=build.type5_make_function([fn_b_a_b, b, t_a, b]),
|
|
||||||
constraints=(has_foldable_t, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
fn_a_b_b = build.type5_make_function([a, b, b])
|
|
||||||
fn_foldr = ConstrainedExpr(
|
|
||||||
variables={t, a, b},
|
|
||||||
expr=build.type5_make_function([fn_a_b_b, b, t_a, b]),
|
|
||||||
constraints=(has_foldable_t, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
Foldable.methods = {
|
|
||||||
'sum': fn_sum,
|
|
||||||
'foldl': fn_foldl,
|
|
||||||
'foldr': fn_foldr,
|
|
||||||
}
|
|
||||||
|
|
||||||
build.register_type_class(Foldable)
|
|
||||||
|
|
||||||
class FoldableCodeGenerator:
|
|
||||||
def __init__(self, build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
self.build = build
|
|
||||||
|
|
||||||
def get_natnum(self, sa_type: TypeExpr) -> tuple[dict[str, TypeExpr], InternalImplementedMethodProtocol[WasmGenerator]]:
|
|
||||||
natnum_type, natnum_router = self.build.operators['+']
|
|
||||||
assert isinstance(natnum_type, ConstrainedExpr)
|
|
||||||
assert len(natnum_type.variables) == 1
|
|
||||||
natnum_a = next(iter(natnum_type.variables))
|
|
||||||
natnum_type = replace_variable(natnum_type.expr, natnum_a, sa_type)
|
|
||||||
impl_lookup = natnum_router.get((natnum_type, ))
|
|
||||||
assert impl_lookup is not None
|
|
||||||
return impl_lookup
|
|
||||||
|
|
||||||
def wasm_dynamic_array_sum(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
|
|
||||||
sa_type = tv_map['a']
|
|
||||||
|
|
||||||
ptr_type_info = self.build.type_info_constructed
|
|
||||||
sa_type_info = self.build.type_info_map.get(sa_type.name)
|
|
||||||
if sa_type_info is None:
|
|
||||||
sa_type_info = ptr_type_info
|
|
||||||
|
|
||||||
natnum_kwargs, natnum_impl = self.get_natnum(sa_type)
|
|
||||||
|
|
||||||
# Definitions
|
|
||||||
sum_adr = g.temp_var_t(ptr_type_info.wasm_type, 'sum_adr')
|
|
||||||
sum_stop = g.temp_var_t(ptr_type_info.wasm_type, 'sum_stop')
|
|
||||||
|
|
||||||
with g.block(params=['i32'], result=sa_type_info.wasm_type):
|
|
||||||
# Stack: [adr] -> [] ; sum_adr=ard
|
|
||||||
g.local.set(sum_adr)
|
|
||||||
|
|
||||||
# Stack: [] ; sum_stop = adr + 4 + len(adr) * sa_type_info.alloc_size
|
|
||||||
g.nop(comment='Calculate address at which to stop looping')
|
|
||||||
g.local.get(sum_adr)
|
|
||||||
g.i32.load()
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
g.i32.mul()
|
|
||||||
g.local.get(sum_adr)
|
|
||||||
g.i32.add()
|
|
||||||
|
|
||||||
g.i32.const(4)
|
|
||||||
g.i32.add()
|
|
||||||
g.local.set(sum_stop)
|
|
||||||
|
|
||||||
# Stack: [] -> [sum] ; sum_adr += 4
|
|
||||||
g.nop(comment='Get the first array value as starting point')
|
|
||||||
g.local.get(sum_adr)
|
|
||||||
g.i32.const(4)
|
|
||||||
g.i32.add()
|
|
||||||
g.local.tee(sum_adr)
|
|
||||||
g.add_statement(sa_type_info.wasm_load_func)
|
|
||||||
|
|
||||||
# Since we did the first one, increase adr
|
|
||||||
# Stack: [sum] -> [sum] ; sum_adr = sum_adr + sa_type_info.alloc_size
|
|
||||||
g.local.get(sum_adr)
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
g.i32.add()
|
|
||||||
g.local.set(sum_adr)
|
|
||||||
|
|
||||||
g.local.get(sum_adr)
|
|
||||||
g.local.get(sum_stop)
|
|
||||||
g.i32.lt_u()
|
|
||||||
with g.if_(params=[sa_type_info.wasm_type], result=sa_type_info.wasm_type):
|
|
||||||
with g.loop(params=[sa_type_info.wasm_type], result=sa_type_info.wasm_type):
|
|
||||||
# sum = sum + *adr
|
|
||||||
# Stack: [sum] -> [sum + *adr]
|
|
||||||
g.nop(comment='Add array value')
|
|
||||||
g.local.get(sum_adr)
|
|
||||||
g.add_statement(sa_type_info.wasm_load_func)
|
|
||||||
natnum_impl(g, natnum_kwargs)
|
|
||||||
|
|
||||||
# adr = adr + sa_type_info.alloc_size
|
|
||||||
# Stack: [sum] -> [sum]
|
|
||||||
g.nop(comment='Calculate address of the next value')
|
|
||||||
g.local.get(sum_adr)
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
g.i32.add()
|
|
||||||
g.local.tee(sum_adr)
|
|
||||||
|
|
||||||
# loop if adr < stop
|
|
||||||
g.nop(comment='Check if address exceeds array bounds')
|
|
||||||
g.local.get(sum_stop)
|
|
||||||
g.i32.lt_u()
|
|
||||||
g.br_if(0)
|
|
||||||
# else: sum x[1] === x => so we don't need to loop
|
|
||||||
|
|
||||||
# End result: [sum]
|
|
||||||
|
|
||||||
def wasm_static_array_sum(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
|
|
||||||
sa_type = tv_map['a']
|
|
||||||
sa_len = tv_map['n']
|
|
||||||
|
|
||||||
assert isinstance(sa_len, TypeLevelNat)
|
|
||||||
|
|
||||||
if sa_len.value < 1:
|
|
||||||
raise NotImplementedError('Default value in case sum is empty')
|
|
||||||
|
|
||||||
ptr_type_info = self.build.type_info_constructed
|
|
||||||
sa_type_info = self.build.type_info_map.get(sa_type.name)
|
|
||||||
if sa_type_info is None:
|
|
||||||
sa_type_info = ptr_type_info
|
|
||||||
|
|
||||||
natnum_kwargs, natnum_impl = self.get_natnum(sa_type)
|
|
||||||
|
|
||||||
# Definitions
|
|
||||||
sum_adr = g.temp_var_t(ptr_type_info.wasm_type, 'sum_adr')
|
|
||||||
sum_stop = g.temp_var_t(ptr_type_info.wasm_type, 'sum_stop')
|
|
||||||
|
|
||||||
# Stack before: [adr]
|
|
||||||
# Stack after: [sum]
|
|
||||||
|
|
||||||
# adr = {address of what's currently on stack}
|
|
||||||
# Stack: [adr] -> []
|
|
||||||
g.nop(comment=f'Start sum for {sa_type.name}[{sa_len.value}]')
|
|
||||||
g.local.set(sum_adr)
|
|
||||||
|
|
||||||
# stop = adr + ar_len * sa_type_info.alloc_size
|
|
||||||
# Stack: []
|
|
||||||
g.nop(comment='Calculate address at which to stop looping')
|
|
||||||
g.local.get(sum_adr)
|
|
||||||
g.i32.const(sa_len.value * sa_type_info.alloc_size)
|
|
||||||
g.i32.add()
|
|
||||||
g.local.set(sum_stop)
|
|
||||||
|
|
||||||
# sum = *adr
|
|
||||||
# Stack: [] -> [sum]
|
|
||||||
g.nop(comment='Get the first array value as starting point')
|
|
||||||
g.local.get(sum_adr)
|
|
||||||
g.add_statement(sa_type_info.wasm_load_func)
|
|
||||||
|
|
||||||
# Since we did the first one, increase adr
|
|
||||||
# adr = adr + sa_type_info.alloc_size
|
|
||||||
# Stack: [sum] -> [sum]
|
|
||||||
g.local.get(sum_adr)
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
g.i32.add()
|
|
||||||
g.local.set(sum_adr)
|
|
||||||
|
|
||||||
if sa_len.value > 1:
|
|
||||||
with g.loop(params=[sa_type_info.wasm_type], result=sa_type_info.wasm_type):
|
|
||||||
# sum = sum + *adr
|
|
||||||
# Stack: [sum] -> [sum + *adr]
|
|
||||||
g.nop(comment='Add array value')
|
|
||||||
g.local.get(sum_adr)
|
|
||||||
g.add_statement(sa_type_info.wasm_load_func)
|
|
||||||
natnum_impl(g, natnum_kwargs)
|
|
||||||
|
|
||||||
# adr = adr + sa_type_info.alloc_size
|
|
||||||
# Stack: [sum] -> [sum]
|
|
||||||
g.nop(comment='Calculate address of the next value')
|
|
||||||
g.local.get(sum_adr)
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
g.i32.add()
|
|
||||||
g.local.tee(sum_adr)
|
|
||||||
|
|
||||||
# loop if adr < stop
|
|
||||||
g.nop(comment='Check if address exceeds array bounds')
|
|
||||||
g.local.get(sum_stop)
|
|
||||||
g.i32.lt_u()
|
|
||||||
g.br_if(0)
|
|
||||||
# else: sum x[1] === x => so we don't need to loop
|
|
||||||
|
|
||||||
g.nop(comment=f'Completed sum for {sa_type.name}[{sa_len.value}]')
|
|
||||||
# End result: [sum]
|
|
||||||
|
|
||||||
def wasm_dynamic_array_foldl(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
|
|
||||||
sa_type = tv_map['a']
|
|
||||||
res_type = tv_map['b']
|
|
||||||
|
|
||||||
ptr_type_info = self.build.type_info_constructed
|
|
||||||
u32_type_info = self.build.type_info_map['u32']
|
|
||||||
|
|
||||||
sa_type_info = self.build.type_info_map.get(sa_type.name)
|
|
||||||
if sa_type_info is None:
|
|
||||||
sa_type_info = ptr_type_info
|
|
||||||
res_type_info = self.build.type_info_map.get(res_type.name)
|
|
||||||
if res_type_info is None:
|
|
||||||
res_type_info = ptr_type_info
|
|
||||||
|
|
||||||
# Definitions
|
|
||||||
fold_adr = g.temp_var_t(ptr_type_info.wasm_type, 'fold_adr')
|
|
||||||
fold_stop = g.temp_var_t(ptr_type_info.wasm_type, 'fold_stop')
|
|
||||||
fold_init = g.temp_var_t(res_type_info.wasm_type, 'fold_init')
|
|
||||||
fold_func = g.temp_var_t(ptr_type_info.wasm_type, 'fold_func')
|
|
||||||
fold_len = g.temp_var_t(u32_type_info.wasm_type, 'fold_len')
|
|
||||||
|
|
||||||
with g.block(params=['i32', res_type_info.wasm_type, 'i32'], result=res_type_info.wasm_type, comment=f'foldl a={sa_type.name} b={res_type.name}'):
|
|
||||||
# Stack: [fn*, b, sa*] -> [fn*, b]
|
|
||||||
g.local.tee(fold_adr) # Store address, but also keep it for loading the length
|
|
||||||
g.i32.load() # Load the length
|
|
||||||
g.local.set(fold_len) # Store the length
|
|
||||||
|
|
||||||
# Stack: [fn*, b] -> [fn*]
|
|
||||||
g.local.set(fold_init)
|
|
||||||
# Stack: [fn*] -> []
|
|
||||||
g.local.set(fold_func)
|
|
||||||
|
|
||||||
# Stack: [] -> [b]
|
|
||||||
g.nop(comment='No applications if array is empty')
|
|
||||||
g.local.get(fold_init)
|
|
||||||
g.local.get(fold_len)
|
|
||||||
g.i32.eqz() # If the array is empty
|
|
||||||
g.br_if(0) # Then the base value is the result
|
|
||||||
|
|
||||||
# Stack: [b] -> [b] ; fold_adr=fold_adr + 4
|
|
||||||
g.nop(comment='Skip the header')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.i32.const(4)
|
|
||||||
g.i32.add()
|
|
||||||
g.local.set(fold_adr)
|
|
||||||
|
|
||||||
# Stack: [b] -> [b]
|
|
||||||
g.nop(comment='Apply the first function call')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.add_statement(sa_type_info.wasm_load_func)
|
|
||||||
g.local.get(fold_func)
|
|
||||||
g.call_indirect([res_type_info.wasm_type, sa_type_info.wasm_type], res_type_info.wasm_type)
|
|
||||||
|
|
||||||
# Stack: [b] -> [b]
|
|
||||||
g.nop(comment='No loop if there is only one item')
|
|
||||||
g.local.get(fold_len)
|
|
||||||
g.i32.const(1)
|
|
||||||
g.i32.eq()
|
|
||||||
g.br_if(0) # just one value, don't need to loop
|
|
||||||
|
|
||||||
# Stack: [b] -> [b] ; fold_stop=fold_adr + (sa_len.value * sa_type_info.alloc_size)
|
|
||||||
g.nop(comment='Calculate address at which to stop looping')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.local.get(fold_len)
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
g.i32.mul()
|
|
||||||
g.i32.add()
|
|
||||||
g.local.set(fold_stop)
|
|
||||||
|
|
||||||
# Stack: [b] -> [b] ; fold_adr = fold_adr + sa_type_info.alloc_size
|
|
||||||
g.nop(comment='Calculate address of the next value')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
g.i32.add()
|
|
||||||
g.local.set(fold_adr)
|
|
||||||
|
|
||||||
with g.loop(params=[res_type_info.wasm_type], result=res_type_info.wasm_type):
|
|
||||||
# Stack: [b] -> [b]
|
|
||||||
g.nop(comment='Apply function call')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.add_statement(sa_type_info.wasm_load_func)
|
|
||||||
g.local.get(fold_func)
|
|
||||||
g.call_indirect([res_type_info.wasm_type, sa_type_info.wasm_type], res_type_info.wasm_type)
|
|
||||||
|
|
||||||
# Stack: [b] -> [b] ; fold_adr = fold_adr + sa_type_info.alloc_size
|
|
||||||
g.nop(comment='Calculate address of the next value')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
g.i32.add()
|
|
||||||
g.local.tee(fold_adr)
|
|
||||||
|
|
||||||
# loop if adr > stop
|
|
||||||
# Stack: [b] -> [b]
|
|
||||||
g.nop(comment='Check if address exceeds array bounds')
|
|
||||||
g.local.get(fold_stop)
|
|
||||||
g.i32.lt_u()
|
|
||||||
g.br_if(0)
|
|
||||||
|
|
||||||
# Stack: [b]
|
|
||||||
|
|
||||||
def wasm_static_array_foldl(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
|
|
||||||
sa_type = tv_map['a']
|
|
||||||
sa_len = tv_map['n']
|
|
||||||
res_type = tv_map['b']
|
|
||||||
|
|
||||||
assert isinstance(sa_len, TypeLevelNat)
|
|
||||||
|
|
||||||
ptr_type_info = self.build.type_info_constructed
|
|
||||||
|
|
||||||
sa_type_info = self.build.type_info_map.get(sa_type.name)
|
|
||||||
if sa_type_info is None:
|
|
||||||
sa_type_info = ptr_type_info
|
|
||||||
res_type_info = self.build.type_info_map.get(res_type.name)
|
|
||||||
if res_type_info is None:
|
|
||||||
res_type_info = ptr_type_info
|
|
||||||
|
|
||||||
# Definitions
|
|
||||||
fold_adr = g.temp_var_t(ptr_type_info.wasm_type, 'fold_adr')
|
|
||||||
fold_stop = g.temp_var_t(ptr_type_info.wasm_type, 'fold_stop')
|
|
||||||
fold_init = g.temp_var_t(res_type_info.wasm_type, 'fold_init')
|
|
||||||
fold_func = g.temp_var_t(ptr_type_info.wasm_type, 'fold_func')
|
|
||||||
|
|
||||||
with g.block(params=['i32', res_type_info.wasm_type, 'i32'], result=res_type_info.wasm_type, comment=f'foldl a={sa_type.name} n={sa_len.value} b={res_type.name}'):
|
|
||||||
# Stack: [fn*, b, sa*] -> [fn*, b]
|
|
||||||
g.local.set(fold_adr)
|
|
||||||
# Stack: [fn*, b] -> [fn*]
|
|
||||||
g.local.set(fold_init)
|
|
||||||
# Stack: [fn*] -> []
|
|
||||||
g.local.set(fold_func)
|
|
||||||
|
|
||||||
if sa_len.value < 1:
|
|
||||||
g.local.get(fold_init)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Stack: [] -> [b]
|
|
||||||
g.nop(comment='Apply the first function call')
|
|
||||||
g.local.get(fold_init)
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.add_statement(sa_type_info.wasm_load_func)
|
|
||||||
g.local.get(fold_func)
|
|
||||||
g.call_indirect([res_type_info.wasm_type, sa_type_info.wasm_type], res_type_info.wasm_type)
|
|
||||||
|
|
||||||
if sa_len.value > 1:
|
|
||||||
# Stack: [b] -> [b] ; fold_stop=fold_adr + (sa_len.value * sa_type_info.alloc_size)
|
|
||||||
g.nop(comment='Calculate address at which to stop looping')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.i32.const(sa_len.value * sa_type_info.alloc_size)
|
|
||||||
g.i32.add()
|
|
||||||
g.local.set(fold_stop)
|
|
||||||
|
|
||||||
# Stack: [b] -> [b] ; fold_adr = fold_adr + sa_type_info.alloc_size
|
|
||||||
g.nop(comment='Calculate address of the next value')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
g.i32.add()
|
|
||||||
g.local.set(fold_adr)
|
|
||||||
|
|
||||||
with g.loop(params=[res_type_info.wasm_type], result=res_type_info.wasm_type):
|
|
||||||
# Stack: [b] -> [b]
|
|
||||||
g.nop(comment='Apply function call')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.add_statement(sa_type_info.wasm_load_func)
|
|
||||||
g.local.get(fold_func)
|
|
||||||
g.call_indirect([res_type_info.wasm_type, sa_type_info.wasm_type], res_type_info.wasm_type)
|
|
||||||
|
|
||||||
# Stack: [b] -> [b] ; fold_adr = fold_adr + sa_type_info.alloc_size
|
|
||||||
g.nop(comment='Calculate address of the next value')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
g.i32.add()
|
|
||||||
g.local.tee(fold_adr)
|
|
||||||
|
|
||||||
# loop if adr > stop
|
|
||||||
# Stack: [b] -> [b]
|
|
||||||
g.nop(comment='Check if address exceeds array bounds')
|
|
||||||
g.local.get(fold_stop)
|
|
||||||
g.i32.lt_u()
|
|
||||||
g.br_if(0)
|
|
||||||
# else: just one value, don't need to loop
|
|
||||||
|
|
||||||
# Stack: [b]
|
|
||||||
|
|
||||||
def wasm_dynamic_array_foldr(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
|
|
||||||
sa_type = tv_map['a']
|
|
||||||
res_type = tv_map['b']
|
|
||||||
|
|
||||||
ptr_type_info = self.build.type_info_constructed
|
|
||||||
u32_type_info = self.build.type_info_map['u32']
|
|
||||||
|
|
||||||
sa_type_info = self.build.type_info_map.get(sa_type.name)
|
|
||||||
if sa_type_info is None:
|
|
||||||
sa_type_info = ptr_type_info
|
|
||||||
res_type_info = self.build.type_info_map.get(res_type.name)
|
|
||||||
if res_type_info is None:
|
|
||||||
res_type_info = ptr_type_info
|
|
||||||
|
|
||||||
# Definitions
|
|
||||||
fold_adr = g.temp_var_t(ptr_type_info.wasm_type, 'fold_adr')
|
|
||||||
fold_stop = g.temp_var_t(ptr_type_info.wasm_type, 'fold_stop')
|
|
||||||
fold_tmp = g.temp_var_t(res_type_info.wasm_type, 'fold_tmp')
|
|
||||||
fold_func = g.temp_var_t(ptr_type_info.wasm_type, 'fold_func')
|
|
||||||
fold_len = g.temp_var_t(u32_type_info.wasm_type, 'fold_len')
|
|
||||||
|
|
||||||
with g.block(params=['i32', res_type_info.wasm_type, 'i32'], result=res_type_info.wasm_type, comment=f'foldr a={sa_type.name} b={res_type.name}'):
|
|
||||||
# Stack: [fn*, b, sa*] -> [fn*, b] ; fold_adr=fn*, fold_tmp=b, fold_func=fn*, fold_len=*sa
|
|
||||||
g.local.tee(fold_adr) # Store address, but also keep it for loading the length
|
|
||||||
g.i32.load() # Load the length
|
|
||||||
g.local.set(fold_len) # Store the length
|
|
||||||
# Stack: [fn*, b] -> [fn*]
|
|
||||||
g.local.set(fold_tmp)
|
|
||||||
# Stack: [fn*] -> []
|
|
||||||
g.local.set(fold_func)
|
|
||||||
|
|
||||||
# Stack: [] -> []
|
|
||||||
g.nop(comment='No applications if array is empty')
|
|
||||||
g.local.get(fold_tmp)
|
|
||||||
g.local.get(fold_len)
|
|
||||||
g.i32.eqz() # If the array is empty
|
|
||||||
g.br_if(0) # Then the base value is the result
|
|
||||||
g.drop() # Else drop the value for now
|
|
||||||
|
|
||||||
# Stack: [b] -> [b] ; fold_adr=fold_adr + 4
|
|
||||||
g.nop(comment='Skip the header')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.i32.const(4)
|
|
||||||
g.i32.add()
|
|
||||||
g.local.set(fold_adr)
|
|
||||||
|
|
||||||
# Stack: [] -> [] ; fold_stop=fold_adr
|
|
||||||
g.nop(comment='Calculate address at which to stop looping')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.local.set(fold_stop)
|
|
||||||
|
|
||||||
# Stack: [] -> [] ; fold_adr=fold_adr + (sa_len.value - 1) * sa_type_info.alloc_size
|
|
||||||
g.nop(comment='Calculate address at which to start looping')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.local.get(fold_len)
|
|
||||||
g.i32.const(1)
|
|
||||||
g.i32.sub()
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
g.i32.mul()
|
|
||||||
g.i32.add()
|
|
||||||
g.local.set(fold_adr)
|
|
||||||
|
|
||||||
# Stack: [] -> [b]
|
|
||||||
g.nop(comment='Apply the first function call')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.add_statement(sa_type_info.wasm_load_func)
|
|
||||||
g.local.get(fold_tmp)
|
|
||||||
g.local.get(fold_func)
|
|
||||||
g.call_indirect([sa_type_info.wasm_type, res_type_info.wasm_type], res_type_info.wasm_type)
|
|
||||||
|
|
||||||
# Stack: [b] -> [b]
|
|
||||||
g.nop(comment='Check if more than one entry')
|
|
||||||
g.local.get(fold_len)
|
|
||||||
g.i32.const(1)
|
|
||||||
g.i32.eq() # If the array has only item
|
|
||||||
g.br_if(0) # Then the the first application is sufficient
|
|
||||||
|
|
||||||
# Stack: [b] -> [b] ; fold_adr = fold_adr - sa_type_info.alloc_size
|
|
||||||
g.nop(comment='Calculate address of the next value')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
g.i32.sub()
|
|
||||||
g.local.set(fold_adr)
|
|
||||||
|
|
||||||
with g.loop(params=[res_type_info.wasm_type], result=res_type_info.wasm_type):
|
|
||||||
g.nop(comment='Apply function call')
|
|
||||||
|
|
||||||
# Stack [b] since we don't have proper stack switching opcodes
|
|
||||||
# Stack: [b] -> []
|
|
||||||
g.local.set(fold_tmp)
|
|
||||||
|
|
||||||
# Stack: [] -> [a]
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.add_statement(sa_type_info.wasm_load_func)
|
|
||||||
|
|
||||||
# Stack [a] -> [a, b]
|
|
||||||
g.local.get(fold_tmp)
|
|
||||||
|
|
||||||
# Stack [a, b] -> [b]
|
|
||||||
g.local.get(fold_func)
|
|
||||||
g.call_indirect([sa_type_info.wasm_type, res_type_info.wasm_type], res_type_info.wasm_type)
|
|
||||||
|
|
||||||
# Stack: [b] -> [b] ; fold_adr = fold_adr - sa_type_info.alloc_size
|
|
||||||
g.nop(comment='Calculate address of the next value')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
g.i32.sub()
|
|
||||||
g.local.tee(fold_adr)
|
|
||||||
|
|
||||||
# loop if adr >= stop
|
|
||||||
# Stack: [b] -> [b]
|
|
||||||
g.nop(comment='Check if address exceeds array bounds')
|
|
||||||
g.local.get(fold_stop)
|
|
||||||
g.i32.ge_u()
|
|
||||||
g.br_if(0)
|
|
||||||
|
|
||||||
# Stack: [b]
|
|
||||||
|
|
||||||
def wasm_static_array_foldr(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
|
|
||||||
sa_type = tv_map['a']
|
|
||||||
sa_len = tv_map['n']
|
|
||||||
res_type = tv_map['b']
|
|
||||||
|
|
||||||
assert isinstance(sa_len, TypeLevelNat)
|
|
||||||
|
|
||||||
ptr_type_info = self.build.type_info_constructed
|
|
||||||
|
|
||||||
sa_type_info = self.build.type_info_map.get(sa_type.name)
|
|
||||||
if sa_type_info is None:
|
|
||||||
sa_type_info = ptr_type_info
|
|
||||||
res_type_info = self.build.type_info_map.get(res_type.name)
|
|
||||||
if res_type_info is None:
|
|
||||||
res_type_info = ptr_type_info
|
|
||||||
|
|
||||||
# Definitions
|
|
||||||
fold_adr = g.temp_var_t(ptr_type_info.wasm_type, 'fold_adr')
|
|
||||||
fold_stop = g.temp_var_t(ptr_type_info.wasm_type, 'fold_stop')
|
|
||||||
fold_tmp = g.temp_var_t(res_type_info.wasm_type, 'fold_tmp')
|
|
||||||
fold_func = g.temp_var_t(ptr_type_info.wasm_type, 'fold_func')
|
|
||||||
|
|
||||||
with g.block(params=['i32', res_type_info.wasm_type, 'i32'], result=res_type_info.wasm_type, comment=f'foldr a={sa_type.name} n={sa_len.value} b={res_type.name}'):
|
|
||||||
# Stack: [fn*, b, sa*] -> [fn*, b] ; fold_adr=fn*, fold_tmp=b, fold_func=fn*
|
|
||||||
g.local.set(fold_adr)
|
|
||||||
# Stack: [fn*, b] -> [fn*]
|
|
||||||
g.local.set(fold_tmp)
|
|
||||||
# Stack: [fn*] -> []
|
|
||||||
g.local.set(fold_func)
|
|
||||||
|
|
||||||
if sa_len.value < 1:
|
|
||||||
g.local.get(fold_tmp)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Stack: [] -> [] ; fold_stop=fold_adr
|
|
||||||
g.nop(comment='Calculate address at which to stop looping')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.local.set(fold_stop)
|
|
||||||
|
|
||||||
# Stack: [] -> [] ; fold_adr=fold_adr + (sa_len.value - 1) * sa_type_info.alloc_size
|
|
||||||
g.nop(comment='Calculate address at which to start looping')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.i32.const((sa_len.value - 1) * sa_type_info.alloc_size)
|
|
||||||
g.i32.add()
|
|
||||||
g.local.set(fold_adr)
|
|
||||||
|
|
||||||
# Stack: [] -> [b]
|
|
||||||
g.nop(comment='Get the init value and first array value as starting point')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.add_statement(sa_type_info.wasm_load_func)
|
|
||||||
g.local.get(fold_tmp)
|
|
||||||
g.local.get(fold_func)
|
|
||||||
g.call_indirect([sa_type_info.wasm_type, res_type_info.wasm_type], res_type_info.wasm_type)
|
|
||||||
|
|
||||||
if sa_len.value > 1:
|
|
||||||
# Stack: [b] -> [b] ; fold_adr = fold_adr - sa_type_info.alloc_size
|
|
||||||
g.nop(comment='Calculate address of the next value')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
g.i32.sub()
|
|
||||||
g.local.set(fold_adr)
|
|
||||||
|
|
||||||
with g.loop(params=[res_type_info.wasm_type], result=res_type_info.wasm_type):
|
|
||||||
g.nop(comment='Apply function call')
|
|
||||||
|
|
||||||
# Stack [b] since we don't have proper stack switching opcodes
|
|
||||||
# Stack: [b] -> []
|
|
||||||
g.local.set(fold_tmp)
|
|
||||||
|
|
||||||
# Stack: [] -> [a]
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.add_statement(sa_type_info.wasm_load_func)
|
|
||||||
|
|
||||||
# Stack [a] -> [a, b]
|
|
||||||
g.local.get(fold_tmp)
|
|
||||||
|
|
||||||
# Stack [a, b] -> [b]
|
|
||||||
g.local.get(fold_func)
|
|
||||||
g.call_indirect([sa_type_info.wasm_type, res_type_info.wasm_type], res_type_info.wasm_type)
|
|
||||||
|
|
||||||
# Stack: [b] -> [b] ; fold_adr = fold_adr - sa_type_info.alloc_size
|
|
||||||
g.nop(comment='Calculate address of the next value')
|
|
||||||
g.local.get(fold_adr)
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
g.i32.sub()
|
|
||||||
g.local.tee(fold_adr)
|
|
||||||
|
|
||||||
# loop if adr >= stop
|
|
||||||
# Stack: [b] -> [b]
|
|
||||||
g.nop(comment='Check if address exceeds array bounds')
|
|
||||||
g.local.get(fold_stop)
|
|
||||||
g.i32.ge_u()
|
|
||||||
g.br_if(0)
|
|
||||||
# else: just one value, don't need to loop
|
|
||||||
|
|
||||||
# Stack: [b]
|
|
||||||
|
|
||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
Foldable = build.type_classes['Foldable']
|
|
||||||
|
|
||||||
n = TypeVariable(kind=Nat(), name='n')
|
|
||||||
|
|
||||||
gen = FoldableCodeGenerator(build)
|
|
||||||
|
|
||||||
build.instance_type_class(Foldable, build.dynamic_array_type5_constructor, methods={
|
|
||||||
'sum': gen.wasm_dynamic_array_sum,
|
|
||||||
'foldl': gen.wasm_dynamic_array_foldl,
|
|
||||||
'foldr': gen.wasm_dynamic_array_foldr,
|
|
||||||
})
|
|
||||||
foo = TypeApplication(constructor=build.static_array_type5_constructor, argument=n)
|
|
||||||
build.instance_type_class(Foldable, foo, methods={
|
|
||||||
'sum': gen.wasm_static_array_sum,
|
|
||||||
'foldl': gen.wasm_static_array_foldl,
|
|
||||||
'foldr': gen.wasm_static_array_foldr,
|
|
||||||
})
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
"""
|
|
||||||
The Fractional type class is defined for numeric types that can be (precisely) divided.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ...type5.constrainedexpr import ConstrainedExpr
|
|
||||||
from ...type5.kindexpr import Star
|
|
||||||
from ...type5.typeexpr import TypeVariable
|
|
||||||
from ...typeclass import TypeClass, TypeClassConstraint
|
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
|
||||||
from ..base import BuildBase
|
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
|
||||||
|
|
||||||
Fractional = TypeClass('Fractional', (a, ), methods={}, operators={})
|
|
||||||
|
|
||||||
has_fractional_a = TypeClassConstraint(Fractional, [a])
|
|
||||||
|
|
||||||
fn_a_a = ConstrainedExpr(
|
|
||||||
variables={a},
|
|
||||||
expr=build.type5_make_function([a, a]),
|
|
||||||
constraints=(has_fractional_a, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
fn_a_a_a = ConstrainedExpr(
|
|
||||||
variables={a},
|
|
||||||
expr=build.type5_make_function([a, a, a]),
|
|
||||||
constraints=(has_fractional_a, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
Fractional.methods = {
|
|
||||||
'ceil': fn_a_a,
|
|
||||||
'floor': fn_a_a,
|
|
||||||
'trunc': fn_a_a,
|
|
||||||
'nearest': fn_a_a,
|
|
||||||
}
|
|
||||||
Fractional.operators = {
|
|
||||||
'/': fn_a_a_a,
|
|
||||||
}
|
|
||||||
|
|
||||||
# FIXME: inherited_classes=[NatNum])
|
|
||||||
|
|
||||||
build.register_type_class(Fractional)
|
|
||||||
|
|
||||||
def wasm_f32_ceil(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.ceil()
|
|
||||||
|
|
||||||
def wasm_f64_ceil(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.ceil()
|
|
||||||
|
|
||||||
def wasm_f32_floor(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.floor()
|
|
||||||
|
|
||||||
def wasm_f64_floor(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.floor()
|
|
||||||
|
|
||||||
def wasm_f32_trunc(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.trunc()
|
|
||||||
|
|
||||||
def wasm_f64_trunc(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.trunc()
|
|
||||||
|
|
||||||
def wasm_f32_nearest(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.nearest()
|
|
||||||
|
|
||||||
def wasm_f64_nearest(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.nearest()
|
|
||||||
|
|
||||||
def wasm_f32_div(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.div()
|
|
||||||
|
|
||||||
def wasm_f64_div(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.div()
|
|
||||||
|
|
||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
Fractional = build.type_classes['Fractional']
|
|
||||||
|
|
||||||
build.instance_type_class(Fractional, build.types['f32'], methods={
|
|
||||||
'ceil': wasm_f32_ceil,
|
|
||||||
'floor': wasm_f32_floor,
|
|
||||||
'trunc': wasm_f32_trunc,
|
|
||||||
'nearest': wasm_f32_nearest,
|
|
||||||
}, operators={
|
|
||||||
'/': wasm_f32_div,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Fractional, build.types['f64'], methods={
|
|
||||||
'ceil': wasm_f64_ceil,
|
|
||||||
'floor': wasm_f64_floor,
|
|
||||||
'trunc': wasm_f64_trunc,
|
|
||||||
'nearest': wasm_f64_nearest,
|
|
||||||
}, operators={
|
|
||||||
'/': wasm_f64_div,
|
|
||||||
})
|
|
||||||
@ -1,88 +0,0 @@
|
|||||||
"""
|
|
||||||
The Integral type class is defined for types that can only be approximately divided.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ...type5.constrainedexpr import ConstrainedExpr
|
|
||||||
from ...type5.kindexpr import Star
|
|
||||||
from ...type5.typeexpr import TypeVariable
|
|
||||||
from ...typeclass import TypeClass, TypeClassConstraint
|
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
|
||||||
from ..base import BuildBase
|
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
|
||||||
|
|
||||||
Integral = TypeClass('Integral', (a, ), methods={}, operators={})
|
|
||||||
|
|
||||||
has_integral_a = TypeClassConstraint(Integral, [a])
|
|
||||||
|
|
||||||
fn_a_a_a = ConstrainedExpr(
|
|
||||||
variables={a},
|
|
||||||
expr=build.type5_make_function([a, a, a]),
|
|
||||||
constraints=(has_integral_a, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
Integral.operators = {
|
|
||||||
'//': fn_a_a_a,
|
|
||||||
'%': fn_a_a_a,
|
|
||||||
}
|
|
||||||
|
|
||||||
# FIXME: inherited_classes=[NatNum]
|
|
||||||
|
|
||||||
build.register_type_class(Integral)
|
|
||||||
|
|
||||||
def wasm_u32_div(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i32.div_u')
|
|
||||||
|
|
||||||
def wasm_u64_div(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i64.div_u')
|
|
||||||
|
|
||||||
def wasm_i32_div(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i32.div_s')
|
|
||||||
|
|
||||||
def wasm_i64_div(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i64.div_s')
|
|
||||||
|
|
||||||
def wasm_u32_rem(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i32.rem_u')
|
|
||||||
|
|
||||||
def wasm_u64_rem(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i64.rem_u')
|
|
||||||
|
|
||||||
def wasm_i32_rem(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i32.rem_s')
|
|
||||||
|
|
||||||
def wasm_i64_rem(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i64.rem_s')
|
|
||||||
|
|
||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
Integral = build.type_classes['Integral']
|
|
||||||
|
|
||||||
build.instance_type_class(Integral, build.types['u32'], operators={
|
|
||||||
'//': wasm_u32_div,
|
|
||||||
'%': wasm_u32_rem,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Integral, build.types['u64'], operators={
|
|
||||||
'//': wasm_u64_div,
|
|
||||||
'%': wasm_u64_rem,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Integral, build.types['i32'], operators={
|
|
||||||
'//': wasm_i32_div,
|
|
||||||
'%': wasm_i32_rem,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Integral, build.types['i64'], operators={
|
|
||||||
'//': wasm_i64_div,
|
|
||||||
'%': wasm_i64_rem,
|
|
||||||
})
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
"""
|
|
||||||
The IntNum type class is defined for Integer Number types.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ...type5.constrainedexpr import ConstrainedExpr
|
|
||||||
from ...type5.kindexpr import Star
|
|
||||||
from ...type5.typeexpr import TypeVariable
|
|
||||||
from ...typeclass import TypeClass, TypeClassConstraint
|
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
|
||||||
from ..base import BuildBase
|
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
|
||||||
|
|
||||||
IntNum = TypeClass('IntNum', (a, ), methods={}, operators={})
|
|
||||||
|
|
||||||
has_intnum_a = TypeClassConstraint(IntNum, [a])
|
|
||||||
|
|
||||||
fn_a_a = ConstrainedExpr(
|
|
||||||
variables={a},
|
|
||||||
expr=build.type5_make_function([a, a]),
|
|
||||||
constraints=(has_intnum_a, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
IntNum.methods = {
|
|
||||||
'abs': fn_a_a,
|
|
||||||
'neg': fn_a_a,
|
|
||||||
}
|
|
||||||
# FIXME: inherited_classes=[NatNum])
|
|
||||||
|
|
||||||
build.register_type_class(IntNum)
|
|
||||||
|
|
||||||
def wasm_i32_abs(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__i32_abs__')
|
|
||||||
|
|
||||||
def wasm_i64_abs(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__i64_abs__')
|
|
||||||
|
|
||||||
def wasm_f32_abs(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.abs()
|
|
||||||
|
|
||||||
def wasm_f64_abs(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.abs()
|
|
||||||
|
|
||||||
def wasm_i32_neg(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.const(-1)
|
|
||||||
g.i32.mul()
|
|
||||||
|
|
||||||
def wasm_i64_neg(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.const(-1)
|
|
||||||
g.i64.mul()
|
|
||||||
|
|
||||||
def wasm_f32_neg(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.neg()
|
|
||||||
|
|
||||||
def wasm_f64_neg(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.neg()
|
|
||||||
|
|
||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
IntNum = build.type_classes['IntNum']
|
|
||||||
|
|
||||||
build.instance_type_class(IntNum, build.types['i32'], methods={
|
|
||||||
'abs': wasm_i32_abs,
|
|
||||||
'neg': wasm_i32_neg,
|
|
||||||
})
|
|
||||||
build.instance_type_class(IntNum, build.types['i64'], methods={
|
|
||||||
'abs': wasm_i64_abs,
|
|
||||||
'neg': wasm_i64_neg,
|
|
||||||
})
|
|
||||||
build.instance_type_class(IntNum, build.types['f32'], methods={
|
|
||||||
'abs': wasm_f32_abs,
|
|
||||||
'neg': wasm_f32_neg,
|
|
||||||
})
|
|
||||||
build.instance_type_class(IntNum, build.types['f64'], methods={
|
|
||||||
'abs': wasm_f64_abs,
|
|
||||||
'neg': wasm_f64_neg,
|
|
||||||
})
|
|
||||||
@ -1,227 +0,0 @@
|
|||||||
"""
|
|
||||||
The NatNum type class is defined for Natural Number types.
|
|
||||||
|
|
||||||
These cannot be negative so functions like abs and neg make no sense.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ...type5.constrainedexpr import ConstrainedExpr
|
|
||||||
from ...type5.kindexpr import Star
|
|
||||||
from ...type5.typeexpr import TypeVariable
|
|
||||||
from ...typeclass import TypeClass, TypeClassConstraint
|
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
|
||||||
from ..base import BuildBase
|
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
|
||||||
u32 = build.types['u32']
|
|
||||||
|
|
||||||
NatNum = TypeClass('NatNum', (a, ), methods={}, operators={})
|
|
||||||
|
|
||||||
has_natnum_a = TypeClassConstraint(NatNum, [a])
|
|
||||||
|
|
||||||
fn_a_a_a = ConstrainedExpr(
|
|
||||||
variables={a},
|
|
||||||
expr=build.type5_make_function([a, a, a]),
|
|
||||||
constraints=(has_natnum_a, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
fn_a_u32_a = ConstrainedExpr(
|
|
||||||
variables={a},
|
|
||||||
expr=build.type5_make_function([a, u32, a]),
|
|
||||||
constraints=(has_natnum_a, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
NatNum.operators = {
|
|
||||||
'+': fn_a_a_a,
|
|
||||||
'-': fn_a_a_a,
|
|
||||||
'*': fn_a_a_a,
|
|
||||||
'<<': fn_a_u32_a, # Arithmic shift left
|
|
||||||
'>>': fn_a_u32_a, # Arithmic shift right
|
|
||||||
}
|
|
||||||
|
|
||||||
build.register_type_class(NatNum)
|
|
||||||
|
|
||||||
## ###
|
|
||||||
## class NatNum
|
|
||||||
|
|
||||||
def wasm_u32_add(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i32.add')
|
|
||||||
|
|
||||||
def wasm_u64_add(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i64.add')
|
|
||||||
|
|
||||||
def wasm_i32_add(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i32.add')
|
|
||||||
|
|
||||||
def wasm_i64_add(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i64.add')
|
|
||||||
|
|
||||||
def wasm_f32_add(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('f32.add')
|
|
||||||
|
|
||||||
def wasm_f64_add(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('f64.add')
|
|
||||||
|
|
||||||
def wasm_u32_sub(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i32.sub')
|
|
||||||
|
|
||||||
def wasm_u64_sub(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i64.sub')
|
|
||||||
|
|
||||||
def wasm_i32_sub(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i32.sub')
|
|
||||||
|
|
||||||
def wasm_i64_sub(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i64.sub')
|
|
||||||
|
|
||||||
def wasm_f32_sub(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('f32.sub')
|
|
||||||
|
|
||||||
def wasm_f64_sub(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('f64.sub')
|
|
||||||
|
|
||||||
def wasm_u32_mul(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i32.mul')
|
|
||||||
|
|
||||||
def wasm_u64_mul(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i64.mul')
|
|
||||||
|
|
||||||
def wasm_i32_mul(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i32.mul')
|
|
||||||
|
|
||||||
def wasm_i64_mul(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('i64.mul')
|
|
||||||
|
|
||||||
def wasm_f32_mul(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('f32.mul')
|
|
||||||
|
|
||||||
def wasm_f64_mul(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.add_statement('f64.mul')
|
|
||||||
|
|
||||||
def wasm_u32_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.shl()
|
|
||||||
|
|
||||||
def wasm_u64_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.extend_i32_u()
|
|
||||||
g.i64.shl()
|
|
||||||
|
|
||||||
def wasm_i32_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.shl()
|
|
||||||
|
|
||||||
def wasm_i64_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.extend_i32_u()
|
|
||||||
g.i64.shl()
|
|
||||||
|
|
||||||
def wasm_f32_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u32_pow2__')
|
|
||||||
g.f32.convert_i32_u()
|
|
||||||
g.f32.mul()
|
|
||||||
|
|
||||||
def wasm_f64_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u32_pow2__')
|
|
||||||
g.f64.convert_i32_u()
|
|
||||||
g.f64.mul()
|
|
||||||
|
|
||||||
def wasm_u32_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.shr_u()
|
|
||||||
|
|
||||||
def wasm_u64_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.extend_i32_u()
|
|
||||||
g.i64.shr_u()
|
|
||||||
|
|
||||||
def wasm_i32_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.shr_s()
|
|
||||||
|
|
||||||
def wasm_i64_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.extend_i32_u()
|
|
||||||
g.i64.shr_s()
|
|
||||||
|
|
||||||
def wasm_f32_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u32_pow2__')
|
|
||||||
g.f32.convert_i32_u()
|
|
||||||
g.f32.div()
|
|
||||||
|
|
||||||
def wasm_f64_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u32_pow2__')
|
|
||||||
g.f64.convert_i32_u()
|
|
||||||
g.f64.div()
|
|
||||||
|
|
||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
NatNum = build.type_classes['NatNum']
|
|
||||||
|
|
||||||
build.instance_type_class(NatNum, build.types['u32'], operators={
|
|
||||||
'+': wasm_u32_add,
|
|
||||||
'-': wasm_u32_sub,
|
|
||||||
'*': wasm_u32_mul,
|
|
||||||
'<<': wasm_u32_arithmic_shift_left,
|
|
||||||
'>>': wasm_u32_arithmic_shift_right,
|
|
||||||
})
|
|
||||||
build.instance_type_class(NatNum, build.types['u64'], operators={
|
|
||||||
'+': wasm_u64_add,
|
|
||||||
'-': wasm_u64_sub,
|
|
||||||
'*': wasm_u64_mul,
|
|
||||||
'<<': wasm_u64_arithmic_shift_left,
|
|
||||||
'>>': wasm_u64_arithmic_shift_right,
|
|
||||||
})
|
|
||||||
build.instance_type_class(NatNum, build.types['i32'], operators={
|
|
||||||
'+': wasm_i32_add,
|
|
||||||
'-': wasm_i32_sub,
|
|
||||||
'*': wasm_i32_mul,
|
|
||||||
'<<': wasm_i32_arithmic_shift_left,
|
|
||||||
'>>': wasm_i32_arithmic_shift_right,
|
|
||||||
})
|
|
||||||
build.instance_type_class(NatNum, build.types['i64'], operators={
|
|
||||||
'+': wasm_i64_add,
|
|
||||||
'-': wasm_i64_sub,
|
|
||||||
'*': wasm_i64_mul,
|
|
||||||
'<<': wasm_i64_arithmic_shift_left,
|
|
||||||
'>>': wasm_i64_arithmic_shift_right,
|
|
||||||
})
|
|
||||||
build.instance_type_class(NatNum, build.types['f32'], operators={
|
|
||||||
'+': wasm_f32_add,
|
|
||||||
'-': wasm_f32_sub,
|
|
||||||
'*': wasm_f32_mul,
|
|
||||||
'<<': wasm_f32_arithmic_shift_left,
|
|
||||||
'>>': wasm_f32_arithmic_shift_right,
|
|
||||||
})
|
|
||||||
build.instance_type_class(NatNum, build.types['f64'], operators={
|
|
||||||
'+': wasm_f64_add,
|
|
||||||
'-': wasm_f64_sub,
|
|
||||||
'*': wasm_f64_mul,
|
|
||||||
'<<': wasm_f64_arithmic_shift_left,
|
|
||||||
'>>': wasm_f64_arithmic_shift_right,
|
|
||||||
})
|
|
||||||
@ -1,383 +0,0 @@
|
|||||||
"""
|
|
||||||
The Ord type class is defined for totally ordered datatypes.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ...type5.constrainedexpr import ConstrainedExpr
|
|
||||||
from ...type5.kindexpr import Star
|
|
||||||
from ...type5.typeexpr import TypeVariable
|
|
||||||
from ...typeclass import TypeClass, TypeClassConstraint
|
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
|
||||||
from ..base import BuildBase
|
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
|
||||||
|
|
||||||
Ord = TypeClass('Ord', (a, ), methods={}, operators={})
|
|
||||||
|
|
||||||
has_ord_a = TypeClassConstraint(Ord, [a])
|
|
||||||
|
|
||||||
fn_a_a_a = ConstrainedExpr(
|
|
||||||
variables={a},
|
|
||||||
expr=build.type5_make_function([a, a, a]),
|
|
||||||
constraints=(has_ord_a, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
fn_a_a_bool = ConstrainedExpr(
|
|
||||||
variables={a},
|
|
||||||
expr=build.type5_make_function([a, a, build.bool_type5]),
|
|
||||||
constraints=(has_ord_a, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
Ord.methods = {
|
|
||||||
'min': fn_a_a_a,
|
|
||||||
'max': fn_a_a_a,
|
|
||||||
}
|
|
||||||
|
|
||||||
Ord.operators = {
|
|
||||||
'<': fn_a_a_bool,
|
|
||||||
'<=': fn_a_a_bool,
|
|
||||||
'>': fn_a_a_bool,
|
|
||||||
'>=': fn_a_a_bool,
|
|
||||||
}
|
|
||||||
#FIXME: }, inherited_classes=[Eq])
|
|
||||||
|
|
||||||
build.register_type_class(Ord)
|
|
||||||
|
|
||||||
def wasm_u8_min(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u32_min__')
|
|
||||||
|
|
||||||
def wasm_u16_min(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u32_min__')
|
|
||||||
|
|
||||||
def wasm_u32_min(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u32_min__')
|
|
||||||
|
|
||||||
def wasm_u64_min(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u64_min__')
|
|
||||||
|
|
||||||
def wasm_i8_min(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__i32_min__')
|
|
||||||
|
|
||||||
def wasm_i16_min(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__i32_min__')
|
|
||||||
|
|
||||||
def wasm_i32_min(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__i32_min__')
|
|
||||||
|
|
||||||
def wasm_i64_min(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__i64_min__')
|
|
||||||
|
|
||||||
def wasm_f32_min(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.min()
|
|
||||||
|
|
||||||
def wasm_f64_min(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.min()
|
|
||||||
|
|
||||||
def wasm_u8_max(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u32_max__')
|
|
||||||
|
|
||||||
def wasm_u16_max(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u32_max__')
|
|
||||||
|
|
||||||
def wasm_u32_max(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u32_max__')
|
|
||||||
|
|
||||||
def wasm_u64_max(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__u64_max__')
|
|
||||||
|
|
||||||
def wasm_i8_max(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__i32_max__')
|
|
||||||
|
|
||||||
def wasm_i16_max(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__i32_max__')
|
|
||||||
|
|
||||||
def wasm_i32_max(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__i32_max__')
|
|
||||||
|
|
||||||
def wasm_i64_max(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.call('stdlib.types.__i64_max__')
|
|
||||||
|
|
||||||
def wasm_f32_max(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.max()
|
|
||||||
|
|
||||||
def wasm_f64_max(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.max()
|
|
||||||
|
|
||||||
|
|
||||||
def wasm_u8_less_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.lt_u()
|
|
||||||
|
|
||||||
def wasm_u16_less_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.lt_u()
|
|
||||||
|
|
||||||
def wasm_u32_less_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.lt_u()
|
|
||||||
|
|
||||||
def wasm_u64_less_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.lt_u()
|
|
||||||
|
|
||||||
def wasm_i8_less_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.lt_s()
|
|
||||||
|
|
||||||
def wasm_i16_less_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.lt_s()
|
|
||||||
|
|
||||||
def wasm_i32_less_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.lt_s()
|
|
||||||
|
|
||||||
def wasm_i64_less_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.lt_s()
|
|
||||||
|
|
||||||
def wasm_f32_less_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.lt()
|
|
||||||
|
|
||||||
def wasm_f64_less_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.lt()
|
|
||||||
|
|
||||||
def wasm_u8_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.le_u()
|
|
||||||
|
|
||||||
def wasm_u16_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.le_u()
|
|
||||||
|
|
||||||
def wasm_u32_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.le_u()
|
|
||||||
|
|
||||||
def wasm_u64_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.le_u()
|
|
||||||
|
|
||||||
def wasm_i8_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.le_s()
|
|
||||||
|
|
||||||
def wasm_i16_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.le_s()
|
|
||||||
|
|
||||||
def wasm_i32_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.le_s()
|
|
||||||
|
|
||||||
def wasm_i64_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.le_s()
|
|
||||||
|
|
||||||
def wasm_f32_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.le()
|
|
||||||
|
|
||||||
def wasm_f64_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.le()
|
|
||||||
|
|
||||||
def wasm_u8_greater_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.gt_u()
|
|
||||||
|
|
||||||
def wasm_u16_greater_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.gt_u()
|
|
||||||
|
|
||||||
def wasm_u32_greater_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.gt_u()
|
|
||||||
|
|
||||||
def wasm_u64_greater_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.gt_u()
|
|
||||||
|
|
||||||
def wasm_i8_greater_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.gt_s()
|
|
||||||
|
|
||||||
def wasm_i16_greater_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.gt_s()
|
|
||||||
|
|
||||||
def wasm_i32_greater_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.gt_s()
|
|
||||||
|
|
||||||
def wasm_i64_greater_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.gt_s()
|
|
||||||
|
|
||||||
def wasm_f32_greater_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.gt()
|
|
||||||
|
|
||||||
def wasm_f64_greater_than(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.gt()
|
|
||||||
|
|
||||||
def wasm_u8_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.ge_u()
|
|
||||||
|
|
||||||
def wasm_u16_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.ge_u()
|
|
||||||
|
|
||||||
def wasm_u32_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.ge_u()
|
|
||||||
|
|
||||||
def wasm_u64_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.ge_u()
|
|
||||||
|
|
||||||
def wasm_i8_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.ge_s()
|
|
||||||
|
|
||||||
def wasm_i16_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.ge_s()
|
|
||||||
|
|
||||||
def wasm_i32_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.ge_s()
|
|
||||||
|
|
||||||
def wasm_i64_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.ge_s()
|
|
||||||
|
|
||||||
def wasm_f32_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.ge()
|
|
||||||
|
|
||||||
def wasm_f64_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.ge()
|
|
||||||
|
|
||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
Ord = build.type_classes['Ord']
|
|
||||||
|
|
||||||
build.instance_type_class(Ord, build.types['u8'], methods={
|
|
||||||
'min': wasm_u8_min,
|
|
||||||
'max': wasm_u8_max,
|
|
||||||
}, operators={
|
|
||||||
'<': wasm_u8_less_than,
|
|
||||||
'<=': wasm_u8_less_than_or_equal,
|
|
||||||
'>': wasm_u8_greater_than,
|
|
||||||
'>=': wasm_u8_greater_than_or_equal,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Ord, build.types['u16'], methods={
|
|
||||||
'min': wasm_u16_min,
|
|
||||||
'max': wasm_u16_max,
|
|
||||||
}, operators={
|
|
||||||
'<': wasm_u16_less_than,
|
|
||||||
'<=': wasm_u16_less_than_or_equal,
|
|
||||||
'>': wasm_u16_greater_than,
|
|
||||||
'>=': wasm_u16_greater_than_or_equal,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Ord, build.types['u32'], methods={
|
|
||||||
'min': wasm_u32_min,
|
|
||||||
'max': wasm_u32_max,
|
|
||||||
}, operators={
|
|
||||||
'<': wasm_u32_less_than,
|
|
||||||
'<=': wasm_u32_less_than_or_equal,
|
|
||||||
'>': wasm_u32_greater_than,
|
|
||||||
'>=': wasm_u32_greater_than_or_equal,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Ord, build.types['u64'], methods={
|
|
||||||
'min': wasm_u64_min,
|
|
||||||
'max': wasm_u64_max,
|
|
||||||
}, operators={
|
|
||||||
'<': wasm_u64_less_than,
|
|
||||||
'<=': wasm_u64_less_than_or_equal,
|
|
||||||
'>': wasm_u64_greater_than,
|
|
||||||
'>=': wasm_u64_greater_than_or_equal,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Ord, build.types['i8'], methods={
|
|
||||||
'min': wasm_i8_min,
|
|
||||||
'max': wasm_i8_max,
|
|
||||||
}, operators={
|
|
||||||
'<': wasm_i8_less_than,
|
|
||||||
'<=': wasm_i8_less_than_or_equal,
|
|
||||||
'>': wasm_i8_greater_than,
|
|
||||||
'>=': wasm_i8_greater_than_or_equal,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Ord, build.types['i16'], methods={
|
|
||||||
'min': wasm_i16_min,
|
|
||||||
'max': wasm_i16_max,
|
|
||||||
}, operators={
|
|
||||||
'<': wasm_i16_less_than,
|
|
||||||
'<=': wasm_i16_less_than_or_equal,
|
|
||||||
'>': wasm_i16_greater_than,
|
|
||||||
'>=': wasm_i16_greater_than_or_equal,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Ord, build.types['i32'], methods={
|
|
||||||
'min': wasm_i32_min,
|
|
||||||
'max': wasm_i32_max,
|
|
||||||
}, operators={
|
|
||||||
'<': wasm_i32_less_than,
|
|
||||||
'<=': wasm_i32_less_than_or_equal,
|
|
||||||
'>': wasm_i32_greater_than,
|
|
||||||
'>=': wasm_i32_greater_than_or_equal,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Ord, build.types['i64'], methods={
|
|
||||||
'min': wasm_i64_min,
|
|
||||||
'max': wasm_i64_max,
|
|
||||||
}, operators={
|
|
||||||
'<': wasm_i64_less_than,
|
|
||||||
'<=': wasm_i64_less_than_or_equal,
|
|
||||||
'>': wasm_i64_greater_than,
|
|
||||||
'>=': wasm_i64_greater_than_or_equal,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Ord, build.types['f32'], methods={
|
|
||||||
'min': wasm_f32_min,
|
|
||||||
'max': wasm_f32_max,
|
|
||||||
}, operators={
|
|
||||||
'<': wasm_f32_less_than,
|
|
||||||
'<=': wasm_f32_less_than_or_equal,
|
|
||||||
'>': wasm_f32_greater_than,
|
|
||||||
'>=': wasm_f32_greater_than_or_equal,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Ord, build.types['f64'], methods={
|
|
||||||
'min': wasm_f64_min,
|
|
||||||
'max': wasm_f64_max,
|
|
||||||
}, operators={
|
|
||||||
'<': wasm_f64_less_than,
|
|
||||||
'<=': wasm_f64_less_than_or_equal,
|
|
||||||
'>': wasm_f64_greater_than,
|
|
||||||
'>=': wasm_f64_greater_than_or_equal,
|
|
||||||
})
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
"""
|
|
||||||
The Promotable type class is defined for types that can safely be promoted to a type
|
|
||||||
that can hold strictly more values. Going back will result in some precision being lost.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ...type5.constrainedexpr import ConstrainedExpr
|
|
||||||
from ...type5.kindexpr import Star
|
|
||||||
from ...type5.typeexpr import TypeVariable
|
|
||||||
from ...typeclass import TypeClass, TypeClassConstraint
|
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
|
||||||
from ..base import BuildBase
|
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
|
||||||
b = TypeVariable(kind=Star(), name='b')
|
|
||||||
|
|
||||||
Promotable = TypeClass('Promotable', (a, b, ), methods={}, operators={})
|
|
||||||
|
|
||||||
has_Promotable_a_b = TypeClassConstraint(Promotable, [a, b])
|
|
||||||
|
|
||||||
fn_a_b = ConstrainedExpr(
|
|
||||||
variables={a, b},
|
|
||||||
expr=build.type5_make_function([a, b]),
|
|
||||||
constraints=(has_Promotable_a_b, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
fn_b_a = ConstrainedExpr(
|
|
||||||
variables={a, b},
|
|
||||||
expr=build.type5_make_function([b, a]),
|
|
||||||
constraints=(has_Promotable_a_b, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
Promotable.methods = {
|
|
||||||
'promote': fn_a_b,
|
|
||||||
'demote': fn_b_a,
|
|
||||||
}
|
|
||||||
|
|
||||||
build.register_type_class(Promotable)
|
|
||||||
|
|
||||||
def wasm_f32_f64_promote(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.promote_f32()
|
|
||||||
|
|
||||||
def wasm_f32_f64_demote(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.demote_f64()
|
|
||||||
|
|
||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
Promotable = build.type_classes['Promotable']
|
|
||||||
|
|
||||||
build.instance_type_class(Promotable, build.types['f32'], build.types['f64'], methods={
|
|
||||||
'promote': wasm_f32_f64_promote,
|
|
||||||
'demote': wasm_f32_f64_demote,
|
|
||||||
})
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
"""
|
|
||||||
The Reinterpretable type class is defined for when the data for a type can be reinterpreted
|
|
||||||
to hold a value for another type.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ...type5.constrainedexpr import ConstrainedExpr
|
|
||||||
from ...type5.kindexpr import Star
|
|
||||||
from ...type5.typeexpr import TypeVariable
|
|
||||||
from ...typeclass import TypeClass, TypeClassConstraint
|
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
|
||||||
from ..base import BuildBase
|
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
|
||||||
b = TypeVariable(kind=Star(), name='b')
|
|
||||||
|
|
||||||
Reinterpretable = TypeClass('Reinterpretable', (a, b, ), methods={}, operators={})
|
|
||||||
|
|
||||||
has_reinterpretable_a_b = TypeClassConstraint(Reinterpretable, [a, b])
|
|
||||||
|
|
||||||
fn_a_b = ConstrainedExpr(
|
|
||||||
variables={a, b},
|
|
||||||
expr=build.type5_make_function([a, b]),
|
|
||||||
constraints=(has_reinterpretable_a_b, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
Reinterpretable.methods = {
|
|
||||||
'reinterpret': fn_a_b,
|
|
||||||
}
|
|
||||||
|
|
||||||
build.register_type_class(Reinterpretable)
|
|
||||||
|
|
||||||
def wasm_i32_f32_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.reinterpret_i32()
|
|
||||||
|
|
||||||
def wasm_u32_f32_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f32.reinterpret_i32()
|
|
||||||
|
|
||||||
def wasm_i64_f64_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.reinterpret_i64()
|
|
||||||
|
|
||||||
def wasm_u64_f64_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.f64.reinterpret_i64()
|
|
||||||
|
|
||||||
def wasm_f32_i32_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.reinterpret_f32()
|
|
||||||
|
|
||||||
def wasm_f32_u32_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i32.reinterpret_f32()
|
|
||||||
|
|
||||||
def wasm_f64_i64_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.reinterpret_f64()
|
|
||||||
|
|
||||||
def wasm_f64_u64_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
|
|
||||||
del tv_map
|
|
||||||
g.i64.reinterpret_f64()
|
|
||||||
|
|
||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
Reinterpretable = build.type_classes['Reinterpretable']
|
|
||||||
|
|
||||||
build.instance_type_class(Reinterpretable, build.types['u32'], build.types['f32'], methods={
|
|
||||||
'reinterpret': wasm_u32_f32_reinterpret,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Reinterpretable, build.types['u64'], build.types['f64'], methods={
|
|
||||||
'reinterpret': wasm_u64_f64_reinterpret,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Reinterpretable, build.types['i32'], build.types['f32'], methods={
|
|
||||||
'reinterpret': wasm_i32_f32_reinterpret,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Reinterpretable, build.types['i64'], build.types['f64'], methods={
|
|
||||||
'reinterpret': wasm_i64_f64_reinterpret,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Reinterpretable, build.types['f32'], build.types['u32'], methods={
|
|
||||||
'reinterpret': wasm_f32_u32_reinterpret,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Reinterpretable, build.types['f64'], build.types['u64'], methods={
|
|
||||||
'reinterpret': wasm_f64_u64_reinterpret,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Reinterpretable, build.types['f32'], build.types['i32'], methods={
|
|
||||||
'reinterpret': wasm_f32_i32_reinterpret,
|
|
||||||
})
|
|
||||||
build.instance_type_class(Reinterpretable, build.types['f64'], build.types['i64'], methods={
|
|
||||||
'reinterpret': wasm_f64_i64_reinterpret,
|
|
||||||
})
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
"""
|
|
||||||
The Sized type class is defined for when a value can be considered to have a length.
|
|
||||||
|
|
||||||
The length is always in number of items, and never in number of bytes (unless an item is a byte).
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ...type5.constrainedexpr import ConstrainedExpr
|
|
||||||
from ...type5.kindexpr import Nat, Star
|
|
||||||
from ...type5.typeexpr import TypeApplication, TypeExpr, TypeLevelNat, TypeVariable
|
|
||||||
from ...typeclass import TypeClass, TypeClassConstraint
|
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
|
||||||
from ..base import BuildBase
|
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
|
||||||
t = TypeVariable(kind=Star() >> Star(), name='t')
|
|
||||||
t_a = TypeApplication(constructor=t, argument=a)
|
|
||||||
u32 = build.types['u32']
|
|
||||||
|
|
||||||
Sized = TypeClass('Sized', (t, ), methods={}, operators={})
|
|
||||||
|
|
||||||
has_sized_t = TypeClassConstraint(Sized, [t])
|
|
||||||
|
|
||||||
fn_t_a_u32 = ConstrainedExpr(
|
|
||||||
variables={t, a},
|
|
||||||
expr=build.type5_make_function([t_a, u32]),
|
|
||||||
constraints=(has_sized_t, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
Sized.methods = {
|
|
||||||
'len': fn_t_a_u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
build.register_type_class(Sized)
|
|
||||||
|
|
||||||
def wasm_dynamic_array_len(g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
|
|
||||||
del tv_map
|
|
||||||
# The length is stored in the first 4 bytes
|
|
||||||
g.i32.load()
|
|
||||||
|
|
||||||
def wasm_static_array_len(g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
|
|
||||||
sa_len = tv_map['n']
|
|
||||||
assert isinstance(sa_len, TypeLevelNat)
|
|
||||||
g.i32.const(sa_len.value)
|
|
||||||
|
|
||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
Sized = build.type_classes['Sized']
|
|
||||||
|
|
||||||
n = TypeVariable(kind=Nat(), name='n')
|
|
||||||
|
|
||||||
build.instance_type_class(Sized, build.dynamic_array_type5_constructor, methods={
|
|
||||||
'len': wasm_dynamic_array_len,
|
|
||||||
})
|
|
||||||
foo = TypeApplication(constructor=build.static_array_type5_constructor, argument=n)
|
|
||||||
build.instance_type_class(Sized, foo, methods={
|
|
||||||
'len': wasm_static_array_len,
|
|
||||||
})
|
|
||||||
@ -1,144 +0,0 @@
|
|||||||
"""
|
|
||||||
The Eq type class is defined for types that can be compered based on equality.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ...type5.constrainedexpr import ConstrainedExpr
|
|
||||||
from ...type5.kindexpr import Nat, Star
|
|
||||||
from ...type5.typeexpr import TypeApplication, TypeExpr, TypeLevelNat, TypeVariable
|
|
||||||
from ...typeclass import TypeClass, TypeClassConstraint
|
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
|
||||||
from ..base import BuildBase
|
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
|
||||||
t = TypeVariable(kind=Star() >> Star(), name='t')
|
|
||||||
t_a = TypeApplication(constructor=t, argument=a)
|
|
||||||
u32 = build.types['u32']
|
|
||||||
|
|
||||||
Subscriptable = TypeClass('Subscriptable', (t, ), methods={}, operators={})
|
|
||||||
|
|
||||||
has_subscriptable_t = TypeClassConstraint(Subscriptable, [t])
|
|
||||||
|
|
||||||
fn_t_a_u32_a = ConstrainedExpr(
|
|
||||||
variables={t, a},
|
|
||||||
expr=build.type5_make_function([t_a, u32, a]),
|
|
||||||
constraints=(has_subscriptable_t, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
Subscriptable.operators = {
|
|
||||||
'[]': fn_t_a_u32_a,
|
|
||||||
}
|
|
||||||
|
|
||||||
build.register_type_class(Subscriptable)
|
|
||||||
|
|
||||||
class SubscriptableCodeGenerator:
|
|
||||||
def __init__(self, build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
self.build = build
|
|
||||||
|
|
||||||
def wasm_dynamic_array_getitem(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
|
|
||||||
sa_type = tv_map['a']
|
|
||||||
|
|
||||||
u32_type_info = self.build.type_info_map['u32']
|
|
||||||
|
|
||||||
sa_type_info = self.build.type_info_map.get(sa_type.name)
|
|
||||||
if sa_type_info is None:
|
|
||||||
sa_type_info = self.build.type_info_constructed
|
|
||||||
|
|
||||||
getitem_adr = g.temp_var_t(u32_type_info.wasm_type, 'getitem_adr')
|
|
||||||
getitem_idx = g.temp_var_t(u32_type_info.wasm_type, 'getitem_idx')
|
|
||||||
|
|
||||||
# Stack: [varref: *ard, idx: u32]
|
|
||||||
g.local.set(getitem_idx)
|
|
||||||
# Stack: [varref: *ard]
|
|
||||||
g.local.set(getitem_adr)
|
|
||||||
# Stack: []
|
|
||||||
|
|
||||||
# Out of bounds check based on memory stored length
|
|
||||||
# Stack: []
|
|
||||||
g.local.get(getitem_idx)
|
|
||||||
# Stack: [idx: u32]
|
|
||||||
g.local.get(getitem_adr)
|
|
||||||
# Stack: [idx: u32, varref: *ard]
|
|
||||||
g.i32.load()
|
|
||||||
# Stack: [idx: u32, len: u32]
|
|
||||||
g.i32.ge_u()
|
|
||||||
# Stack: [res: bool]
|
|
||||||
with g.if_():
|
|
||||||
g.unreachable(comment='Out of bounds')
|
|
||||||
|
|
||||||
# Stack: []
|
|
||||||
g.local.get(getitem_adr)
|
|
||||||
# Stack: [varref: *ard]
|
|
||||||
g.i32.const(4)
|
|
||||||
# Stack: [varref: *ard, 4]
|
|
||||||
g.i32.add()
|
|
||||||
# Stack: [firstel: *ard]
|
|
||||||
g.local.get(getitem_idx)
|
|
||||||
# Stack: [firstel: *ard, idx: u32]
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
# Stack: [firstel: *ard, idx: u32, as: u32]
|
|
||||||
g.i32.mul()
|
|
||||||
# Stack: [firstel: *ard, offset: u32]
|
|
||||||
g.i32.add()
|
|
||||||
# Stack: [eladr: *ard]
|
|
||||||
g.add_statement(sa_type_info.wasm_load_func)
|
|
||||||
# Stack: [el]
|
|
||||||
|
|
||||||
def wasm_static_array_getitem(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None:
|
|
||||||
sa_type = tv_map['a']
|
|
||||||
sa_len = tv_map['n']
|
|
||||||
|
|
||||||
assert isinstance(sa_len, TypeLevelNat)
|
|
||||||
|
|
||||||
u32_type_info = self.build.type_info_map['u32']
|
|
||||||
|
|
||||||
sa_type_info = self.build.type_info_map.get(sa_type.name)
|
|
||||||
if sa_type_info is None:
|
|
||||||
sa_type_info = self.build.type_info_constructed
|
|
||||||
|
|
||||||
# OPTIMIZE: If index is a constant, we can use offset instead of multiply
|
|
||||||
# and we don't need to do the out of bounds check
|
|
||||||
getitem_idx = g.temp_var_t(u32_type_info.wasm_type, 'getitem_idx')
|
|
||||||
|
|
||||||
# Stack: [varref: *ard, idx: u32]
|
|
||||||
g.local.tee(getitem_idx)
|
|
||||||
|
|
||||||
# Stack: [varref: *ard, idx: u32]
|
|
||||||
# Out of bounds check based on sa_len.value
|
|
||||||
g.i32.const(sa_len.value)
|
|
||||||
# Stack: [varref: *ard, idx: u32, len: u32]
|
|
||||||
g.i32.ge_u()
|
|
||||||
# Stack: [varref: *ard, res: bool]
|
|
||||||
with g.if_():
|
|
||||||
g.unreachable(comment='Out of bounds')
|
|
||||||
|
|
||||||
# Stack: [varref: *ard]
|
|
||||||
g.local.get(getitem_idx)
|
|
||||||
# Stack: [varref: *ard, idx: u32]
|
|
||||||
g.i32.const(sa_type_info.alloc_size)
|
|
||||||
# Stack: [varref: *ard, idx: u32, as: u32]
|
|
||||||
g.i32.mul()
|
|
||||||
# Stack: [varref: *ard, offset: u32]
|
|
||||||
g.i32.add()
|
|
||||||
# Stack: [eladr: *ard]
|
|
||||||
g.add_statement(sa_type_info.wasm_load_func)
|
|
||||||
# Stack: [el]
|
|
||||||
|
|
||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
|
||||||
Subscriptable = build.type_classes['Subscriptable']
|
|
||||||
|
|
||||||
n = TypeVariable(kind=Nat(), name='n')
|
|
||||||
|
|
||||||
gen = SubscriptableCodeGenerator(build)
|
|
||||||
|
|
||||||
build.instance_type_class(Subscriptable, build.dynamic_array_type5_constructor, operators={
|
|
||||||
'[]': gen.wasm_dynamic_array_getitem,
|
|
||||||
})
|
|
||||||
foo = TypeApplication(constructor=build.static_array_type5_constructor, argument=n)
|
|
||||||
build.instance_type_class(Subscriptable, foo, operators={
|
|
||||||
'[]': gen.wasm_static_array_getitem,
|
|
||||||
})
|
|
||||||
@ -1,86 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Iterable
|
|
||||||
|
|
||||||
from ..type5.typeexpr import (
|
|
||||||
AtomicType,
|
|
||||||
TypeApplication,
|
|
||||||
TypeConstructor,
|
|
||||||
TypeExpr,
|
|
||||||
TypeVariable,
|
|
||||||
is_concrete,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TypeClassRegistry[T]:
|
|
||||||
__slots__ = ('data', )
|
|
||||||
|
|
||||||
data: list[tuple[tuple[TypeExpr, ...], T]]
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.data = []
|
|
||||||
|
|
||||||
def add(self, arg_list: Iterable[TypeExpr], val: T) -> None:
|
|
||||||
self.data.append((tuple(arg_list), val))
|
|
||||||
|
|
||||||
def __contains__(self, arg_list: tuple[TypeExpr, ...]) -> bool:
|
|
||||||
for candidate, _ in self.data:
|
|
||||||
subsitutes = _matches(arg_list, candidate)
|
|
||||||
if subsitutes is not None:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get(self, arg_list: tuple[TypeExpr, ...]) -> tuple[dict[str, TypeExpr], T] | None:
|
|
||||||
assert all(is_concrete(x) for x in arg_list)
|
|
||||||
for candidate, val in self.data:
|
|
||||||
subsitutes = _matches(arg_list, candidate)
|
|
||||||
if subsitutes is not None:
|
|
||||||
return (subsitutes, val)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _matches(lft_tpl: tuple[TypeExpr, ...], rgt_tpl: tuple[TypeExpr, ...]) -> dict[str, TypeExpr] | None:
|
|
||||||
subsitutes: dict[str, TypeExpr] = {}
|
|
||||||
|
|
||||||
for lft, rgt in zip(lft_tpl, rgt_tpl, strict=True):
|
|
||||||
new_substitutes = _matches_one(lft, rgt)
|
|
||||||
if new_substitutes is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
subsitutes.update(new_substitutes)
|
|
||||||
|
|
||||||
return subsitutes
|
|
||||||
|
|
||||||
def _matches_one(lft: TypeExpr, rgt: TypeExpr) -> dict[str, TypeExpr] | None:
|
|
||||||
if lft.kind != rgt.kind:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if isinstance(rgt, TypeVariable):
|
|
||||||
assert not isinstance(lft, TypeVariable)
|
|
||||||
return _matches_one(rgt, lft)
|
|
||||||
|
|
||||||
if isinstance(lft, TypeVariable):
|
|
||||||
return {lft.name: rgt}
|
|
||||||
|
|
||||||
if isinstance(lft, (AtomicType, TypeConstructor, )):
|
|
||||||
if lft == rgt:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
if isinstance(lft, TypeApplication):
|
|
||||||
if not isinstance(rgt, TypeApplication):
|
|
||||||
return None
|
|
||||||
|
|
||||||
con_result = _matches_one(lft.constructor, rgt.constructor)
|
|
||||||
if con_result is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
arg_result = _matches_one(lft.argument, rgt.argument)
|
|
||||||
if arg_result is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return con_result | arg_result
|
|
||||||
|
|
||||||
raise NotImplementedError(lft, rgt)
|
|
||||||
@ -1,154 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Any
|
|
||||||
|
|
||||||
from ..type5.record import Record
|
|
||||||
from ..type5.typeexpr import (
|
|
||||||
AtomicType,
|
|
||||||
TypeApplication,
|
|
||||||
TypeConstructor,
|
|
||||||
TypeExpr,
|
|
||||||
TypeLevelNat,
|
|
||||||
TypeVariable,
|
|
||||||
)
|
|
||||||
from ..type5.typerouter import TypeRouter
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .base import BuildBase
|
|
||||||
|
|
||||||
class BuildTypeRouter[T](TypeRouter[T]):
|
|
||||||
"""
|
|
||||||
Extends the general type router with phasm builtin types.
|
|
||||||
|
|
||||||
Like functions, tuples, static and dynamic arrays.
|
|
||||||
"""
|
|
||||||
__slots__ = ('build', )
|
|
||||||
|
|
||||||
def __init__(self, build: BuildBase[Any]) -> None:
|
|
||||||
self.build = build
|
|
||||||
|
|
||||||
def when_application(self, typ: TypeApplication) -> T:
|
|
||||||
da_arg = self.build.type5_is_dynamic_array(typ)
|
|
||||||
if da_arg is not None:
|
|
||||||
return self.when_dynamic_array(da_arg)
|
|
||||||
|
|
||||||
fn_args = self.build.type5_is_function(typ)
|
|
||||||
if fn_args is not None:
|
|
||||||
return self.when_function(fn_args)
|
|
||||||
|
|
||||||
sa_args = self.build.type5_is_static_array(typ)
|
|
||||||
if sa_args is not None:
|
|
||||||
sa_len, sa_typ = sa_args
|
|
||||||
return self.when_static_array(sa_len, sa_typ)
|
|
||||||
|
|
||||||
tp_args = self.build.type5_is_tuple(typ)
|
|
||||||
if tp_args is not None:
|
|
||||||
return self.when_tuple(tp_args)
|
|
||||||
|
|
||||||
return self.when_application_other(typ)
|
|
||||||
|
|
||||||
def when_record(self, typ: Record) -> T:
|
|
||||||
return self.when_struct(typ)
|
|
||||||
|
|
||||||
def when_application_other(self, typ: TypeApplication) -> T:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def when_dynamic_array(self, da_arg: TypeExpr) -> T:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def when_function(self, fn_args: list[TypeExpr]) -> T:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def when_struct(self, typ: Record) -> T:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> T:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def when_tuple(self, tp_args: list[TypeExpr]) -> T:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
class TypeName(BuildTypeRouter[str]):
|
|
||||||
"""
|
|
||||||
Router to generate a type's name.
|
|
||||||
|
|
||||||
Also serves an example implementation.
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def when_application_other(self, typ: TypeApplication) -> str:
|
|
||||||
return typ.name
|
|
||||||
|
|
||||||
def when_atomic(self, typ: AtomicType) -> str:
|
|
||||||
return typ.name
|
|
||||||
|
|
||||||
def when_constructor(self, typ: TypeConstructor) -> str:
|
|
||||||
return typ.name
|
|
||||||
|
|
||||||
def when_dynamic_array(self, da_arg: TypeExpr) -> str:
|
|
||||||
if da_arg == self.build.u8_type5:
|
|
||||||
return 'bytes'
|
|
||||||
|
|
||||||
return self(da_arg) + '[...]'
|
|
||||||
|
|
||||||
def when_function(self, fn_args: list[TypeExpr]) -> str:
|
|
||||||
return 'Callable[' + ', '.join(map(self, fn_args)) + ']'
|
|
||||||
|
|
||||||
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> str:
|
|
||||||
return f'{self(sa_typ)}[{sa_len}]'
|
|
||||||
|
|
||||||
def when_struct(self, typ: Record) -> str:
|
|
||||||
return typ.name
|
|
||||||
|
|
||||||
def when_tuple(self, tp_args: list[TypeExpr]) -> str:
|
|
||||||
return '(' + ', '.join(map(self, tp_args)) + ', )'
|
|
||||||
|
|
||||||
def when_type_level_nat(self, typ: TypeLevelNat) -> str:
|
|
||||||
return str(typ.value)
|
|
||||||
|
|
||||||
def when_variable(self, typ: TypeVariable) -> str:
|
|
||||||
return typ.name
|
|
||||||
|
|
||||||
class TypeAllocSize(BuildTypeRouter[int]):
|
|
||||||
"""
|
|
||||||
Router to generate a type's allocation size.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = ('is_member', )
|
|
||||||
|
|
||||||
is_member: bool
|
|
||||||
|
|
||||||
def __init__(self, build: BuildBase[Any], is_member: bool) -> None:
|
|
||||||
super().__init__(build)
|
|
||||||
self.is_member = is_member
|
|
||||||
|
|
||||||
def when_atomic(self, typ: AtomicType) -> int:
|
|
||||||
typ_info = self.build.type_info_map.get(typ.name)
|
|
||||||
if typ_info is None:
|
|
||||||
raise NotImplementedError(typ)
|
|
||||||
|
|
||||||
return typ_info.alloc_size
|
|
||||||
|
|
||||||
def when_dynamic_array(self, da_arg: TypeExpr) -> int:
|
|
||||||
if self.is_member:
|
|
||||||
return self.build.type_info_constructed.alloc_size
|
|
||||||
|
|
||||||
raise RuntimeError("Cannot know size of dynamic array at type level")
|
|
||||||
|
|
||||||
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> int:
|
|
||||||
if self.is_member:
|
|
||||||
return self.build.type_info_constructed.alloc_size
|
|
||||||
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def when_struct(self, typ: Record) -> int:
|
|
||||||
if self.is_member:
|
|
||||||
return self.build.type_info_constructed.alloc_size
|
|
||||||
|
|
||||||
return sum(map(self.build.type5_alloc_size_member, (x[1] for x in typ.fields)))
|
|
||||||
|
|
||||||
def when_tuple(self, tp_args: list[TypeExpr]) -> int:
|
|
||||||
if self.is_member:
|
|
||||||
return self.build.type_info_constructed.alloc_size
|
|
||||||
|
|
||||||
return sum(map(self.build.type5_alloc_size_member, tp_args))
|
|
||||||
@ -3,41 +3,54 @@ This module generates source code based on the parsed AST
|
|||||||
|
|
||||||
It's intented to be a "any color, as long as it's black" kind of renderer
|
It's intented to be a "any color, as long as it's black" kind of renderer
|
||||||
"""
|
"""
|
||||||
from typing import Any, Generator
|
from typing import Generator, Optional
|
||||||
|
|
||||||
from . import ourlang
|
from . import ourlang
|
||||||
from .type5 import typeexpr as type5typeexpr
|
from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder
|
||||||
|
|
||||||
|
def phasm_render(inp: ourlang.Module) -> str:
|
||||||
def phasm_render(inp: ourlang.Module[Any]) -> str:
|
|
||||||
"""
|
"""
|
||||||
Public method for rendering a Phasm module into Phasm code
|
Public method for rendering a Phasm module into Phasm code
|
||||||
"""
|
"""
|
||||||
return module(inp)
|
return module(inp)
|
||||||
|
|
||||||
Statements = Generator[str, None, None]
|
Statements = Generator[str, None, None]
|
||||||
|
#
|
||||||
|
# def type_var(inp: Optional[typing.TypeVar]) -> str:
|
||||||
|
# """
|
||||||
|
# Render: type's name
|
||||||
|
# """
|
||||||
|
# assert inp is not None, typing.ASSERTION_ERROR
|
||||||
|
#
|
||||||
|
# mtyp = typing.simplify(inp)
|
||||||
|
# if mtyp is None:
|
||||||
|
# raise NotImplementedError(f'Rendering type {inp}')
|
||||||
|
#
|
||||||
|
# return mtyp
|
||||||
|
|
||||||
def type5(mod: ourlang.Module[Any], inp: type5typeexpr.TypeExpr) -> str:
|
def type3(inp: Type3OrPlaceholder) -> str:
|
||||||
"""
|
"""
|
||||||
Render: type's name
|
Render: type's name
|
||||||
"""
|
"""
|
||||||
return mod.build.type5_name(inp)
|
assert isinstance(inp, Type3), TYPE3_ASSERTION_ERROR
|
||||||
|
|
||||||
def struct_definition(mod: ourlang.Module[Any], inp: ourlang.StructDefinition) -> str:
|
return inp.name
|
||||||
|
|
||||||
|
def struct_definition(inp: ourlang.StructDefinition) -> str:
|
||||||
"""
|
"""
|
||||||
Render: TypeStruct's definition
|
Render: TypeStruct's definition
|
||||||
"""
|
"""
|
||||||
result = f'class {inp.struct_type5.name}:\n'
|
result = f'class {inp.struct_type3.name}:\n'
|
||||||
for mem, typ in inp.struct_type5.fields:
|
for mem, typ in inp.struct_type3.members.items():
|
||||||
result += f' {mem}: {type5(mod, typ)}\n'
|
result += f' {mem}: {type3(typ)}\n'
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def constant_definition(mod: ourlang.Module[Any], inp: ourlang.ModuleConstantDef) -> str:
|
def constant_definition(inp: ourlang.ModuleConstantDef) -> str:
|
||||||
"""
|
"""
|
||||||
Render: Module Constant's definition
|
Render: Module Constant's definition
|
||||||
"""
|
"""
|
||||||
return f'{inp.name}: {type5(mod, inp.type5)} = {expression(inp.constant)}\n'
|
return f'{inp.name}: {type3(inp.type3)} = {expression(inp.constant)}\n'
|
||||||
|
|
||||||
def expression(inp: ourlang.Expression) -> str:
|
def expression(inp: ourlang.Expression) -> str:
|
||||||
"""
|
"""
|
||||||
@ -48,26 +61,32 @@ def expression(inp: ourlang.Expression) -> str:
|
|||||||
# could not fit in the given float type
|
# could not fit in the given float type
|
||||||
return str(inp.value)
|
return str(inp.value)
|
||||||
|
|
||||||
if isinstance(inp, ourlang.ConstantBytes):
|
|
||||||
return repr(inp.value)
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.ConstantTuple):
|
if isinstance(inp, ourlang.ConstantTuple):
|
||||||
return '(' + ', '.join(
|
return '(' + ', '.join(
|
||||||
expression(x)
|
expression(x)
|
||||||
for x in inp.value
|
for x in inp.value
|
||||||
) + ', )'
|
) + ', )'
|
||||||
|
|
||||||
if isinstance(inp, ourlang.ConstantStruct):
|
|
||||||
return inp.struct_type5.name + '(' + ', '.join(
|
|
||||||
expression(x)
|
|
||||||
for x in inp.value
|
|
||||||
) + ')'
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.VariableReference):
|
if isinstance(inp, ourlang.VariableReference):
|
||||||
return str(inp.variable.name)
|
return str(inp.variable.name)
|
||||||
|
|
||||||
|
if isinstance(inp, ourlang.UnaryOp):
|
||||||
|
if (
|
||||||
|
inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS
|
||||||
|
or inp.operator in ourlang.WEBASSEMBLY_BUILTIN_BYTES_OPS):
|
||||||
|
return f'{inp.operator}({expression(inp.right)})'
|
||||||
|
|
||||||
|
if inp.operator == 'cast':
|
||||||
|
mtyp = type3(inp.type3)
|
||||||
|
if mtyp is None:
|
||||||
|
raise NotImplementedError(f'Casting to type {inp.type_var}')
|
||||||
|
|
||||||
|
return f'{mtyp}({expression(inp.right)})'
|
||||||
|
|
||||||
|
return f'{inp.operator}{expression(inp.right)}'
|
||||||
|
|
||||||
if isinstance(inp, ourlang.BinaryOp):
|
if isinstance(inp, ourlang.BinaryOp):
|
||||||
return f'{expression(inp.left)} {inp.operator.name} {expression(inp.right)}'
|
return f'{expression(inp.left)} {inp.operator} {expression(inp.right)}'
|
||||||
|
|
||||||
if isinstance(inp, ourlang.FunctionCall):
|
if isinstance(inp, ourlang.FunctionCall):
|
||||||
args = ', '.join(
|
args = ', '.join(
|
||||||
@ -76,21 +95,14 @@ def expression(inp: ourlang.Expression) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(inp.function, ourlang.StructConstructor):
|
if isinstance(inp.function, ourlang.StructConstructor):
|
||||||
return f'{inp.function.struct_type5.name}({args})'
|
return f'{inp.function.struct_type3.name}({args})'
|
||||||
|
|
||||||
|
# TODO: Broken after new type system
|
||||||
|
# if isinstance(inp.function, ourlang.TupleConstructor):
|
||||||
|
# return f'({args}, )'
|
||||||
|
|
||||||
return f'{inp.function.name}({args})'
|
return f'{inp.function.name}({args})'
|
||||||
|
|
||||||
if isinstance(inp, ourlang.FunctionReference):
|
|
||||||
return str(inp.function.name)
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.TupleInstantiation):
|
|
||||||
args = ', '.join(
|
|
||||||
expression(arg)
|
|
||||||
for arg in inp.elements
|
|
||||||
)
|
|
||||||
|
|
||||||
return f'({args}, )'
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.Subscript):
|
if isinstance(inp, ourlang.Subscript):
|
||||||
varref = expression(inp.varref)
|
varref = expression(inp.varref)
|
||||||
index = expression(inp.index)
|
index = expression(inp.index)
|
||||||
@ -100,6 +112,10 @@ def expression(inp: ourlang.Expression) -> str:
|
|||||||
if isinstance(inp, ourlang.AccessStructMember):
|
if isinstance(inp, ourlang.AccessStructMember):
|
||||||
return f'{expression(inp.varref)}.{inp.member}'
|
return f'{expression(inp.varref)}.{inp.member}'
|
||||||
|
|
||||||
|
if isinstance(inp, ourlang.Fold):
|
||||||
|
fold_name = 'foldl' if ourlang.Fold.Direction.LEFT == inp.dir else 'foldr'
|
||||||
|
return f'{fold_name}({inp.func.name}, {expression(inp.base)}, {expression(inp.iter)})'
|
||||||
|
|
||||||
raise NotImplementedError(expression, inp)
|
raise NotImplementedError(expression, inp)
|
||||||
|
|
||||||
def statement(inp: ourlang.Statement) -> Statements:
|
def statement(inp: ourlang.Statement) -> Statements:
|
||||||
@ -126,7 +142,7 @@ def statement(inp: ourlang.Statement) -> Statements:
|
|||||||
|
|
||||||
raise NotImplementedError(statement, inp)
|
raise NotImplementedError(statement, inp)
|
||||||
|
|
||||||
def function(mod: ourlang.Module[Any], inp: ourlang.Function) -> str:
|
def function(inp: ourlang.Function) -> str:
|
||||||
"""
|
"""
|
||||||
Render: Function body
|
Render: Function body
|
||||||
|
|
||||||
@ -139,17 +155,12 @@ def function(mod: ourlang.Module[Any], inp: ourlang.Function) -> str:
|
|||||||
if inp.imported:
|
if inp.imported:
|
||||||
result += '@imported\n'
|
result += '@imported\n'
|
||||||
|
|
||||||
assert inp.type5 is not None
|
|
||||||
fn_args = mod.build.type5_is_function(inp.type5)
|
|
||||||
assert fn_args is not None
|
|
||||||
ret_type5 = fn_args.pop()
|
|
||||||
|
|
||||||
args = ', '.join(
|
args = ', '.join(
|
||||||
f'{arg_name}: {type5(mod, arg_type)}'
|
f'{p.name}: {type3(p.type3)}'
|
||||||
for arg_name, arg_type in zip(inp.arg_names, fn_args, strict=True)
|
for p in inp.posonlyargs
|
||||||
)
|
)
|
||||||
|
|
||||||
result += f'def {inp.name}({args}) -> {type5(mod, ret_type5)}:\n'
|
result += f'def {inp.name}({args}) -> {type3(inp.returns_type3)}:\n'
|
||||||
|
|
||||||
if inp.imported:
|
if inp.imported:
|
||||||
result += ' pass\n'
|
result += ' pass\n'
|
||||||
@ -161,7 +172,7 @@ def function(mod: ourlang.Module[Any], inp: ourlang.Function) -> str:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def module(inp: ourlang.Module[Any]) -> str:
|
def module(inp: ourlang.Module) -> str:
|
||||||
"""
|
"""
|
||||||
Render: Module
|
Render: Module
|
||||||
"""
|
"""
|
||||||
@ -170,20 +181,20 @@ def module(inp: ourlang.Module[Any]) -> str:
|
|||||||
for struct in inp.struct_definitions.values():
|
for struct in inp.struct_definitions.values():
|
||||||
if result:
|
if result:
|
||||||
result += '\n'
|
result += '\n'
|
||||||
result += struct_definition(inp, struct)
|
result += struct_definition(struct)
|
||||||
|
|
||||||
for cdef in inp.constant_defs.values():
|
for cdef in inp.constant_defs.values():
|
||||||
if result:
|
if result:
|
||||||
result += '\n'
|
result += '\n'
|
||||||
result += constant_definition(inp, cdef)
|
result += constant_definition(cdef)
|
||||||
|
|
||||||
for func in inp.functions.values():
|
for func in inp.functions.values():
|
||||||
if isinstance(func, ourlang.StructConstructor):
|
if func.lineno < 0:
|
||||||
# Auto generated
|
# Builtin (-2) or auto generated (-1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
result += '\n'
|
result += '\n'
|
||||||
result += function(inp, func)
|
result += function(func)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -6,3 +6,8 @@ class StaticError(Exception):
|
|||||||
"""
|
"""
|
||||||
An error found during static analysis
|
An error found during static analysis
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class TypingError(Exception):
|
||||||
|
"""
|
||||||
|
An error found during the typing phase
|
||||||
|
"""
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
# This folder contains some basic optimisations
|
|
||||||
# If you really want to optimise your stuff,
|
|
||||||
# checkout https://github.com/WebAssembly/binaryen
|
|
||||||
# It's wasm-opt tool can be run on the wat or wasm files.
|
|
||||||
# And they have a ton of options to optimize.
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
from phasm import wasm
|
|
||||||
|
|
||||||
|
|
||||||
def removeunusedfuncs(inp: wasm.Module) -> None:
|
|
||||||
"""
|
|
||||||
Removes functions that aren't exported and aren't
|
|
||||||
called by exported functions
|
|
||||||
"""
|
|
||||||
# First make a handy lookup table
|
|
||||||
function_map = {
|
|
||||||
x.name: x
|
|
||||||
for x in inp.functions
|
|
||||||
}
|
|
||||||
|
|
||||||
# Keep a list of all functions to retain
|
|
||||||
retain_functions = set()
|
|
||||||
|
|
||||||
# Keep a queue (stack) of the funtions we need to check
|
|
||||||
# The exported functions as well as the tabled functions are the
|
|
||||||
# first we know of to keep
|
|
||||||
to_check_functions = [
|
|
||||||
x
|
|
||||||
for x in inp.functions
|
|
||||||
if x.exported_name is not None
|
|
||||||
] + [
|
|
||||||
function_map[x]
|
|
||||||
for x in inp.table.values()
|
|
||||||
]
|
|
||||||
|
|
||||||
while to_check_functions:
|
|
||||||
# Check the next function. If it's on the list, we need to retain it
|
|
||||||
to_check_func = to_check_functions.pop()
|
|
||||||
retain_functions.add(to_check_func)
|
|
||||||
|
|
||||||
# Check all functions calls by this retaining function
|
|
||||||
to_check_functions.extend(
|
|
||||||
func
|
|
||||||
for stmt in to_check_func.statements
|
|
||||||
if isinstance(stmt, wasm.StatementCall)
|
|
||||||
# The function_map can not have the named function if it is an import
|
|
||||||
if (func := function_map.get(stmt.func_name)) is not None
|
|
||||||
and func not in retain_functions
|
|
||||||
)
|
|
||||||
|
|
||||||
inp.functions = [
|
|
||||||
func
|
|
||||||
for func in inp.functions
|
|
||||||
if func in retain_functions
|
|
||||||
]
|
|
||||||
377
phasm/ourlang.py
377
phasm/ourlang.py
@ -1,46 +1,28 @@
|
|||||||
"""
|
"""
|
||||||
Contains the syntax tree for ourlang
|
Contains the syntax tree for ourlang
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from typing import Dict, List, Optional, Union
|
||||||
|
|
||||||
from typing import Dict, Iterable, List, Optional, Union
|
import enum
|
||||||
|
|
||||||
from .build.base import BuildBase
|
from typing_extensions import Final
|
||||||
from .type5 import constrainedexpr as type5constrainedexpr
|
|
||||||
from .type5 import record as type5record
|
|
||||||
from .type5 import typeexpr as type5typeexpr
|
|
||||||
|
|
||||||
|
WEBASSEMBLY_BUILTIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', )
|
||||||
|
WEBASSEMBLY_BUILTIN_BYTES_OPS: Final = ('len', )
|
||||||
|
|
||||||
class SourceRef:
|
from .type3 import types as type3types
|
||||||
__slots__ = ('filename', 'lineno', 'colno', )
|
from .type3.types import Type3, Type3OrPlaceholder, PlaceholderForType, StructType3
|
||||||
|
|
||||||
filename: str | None
|
|
||||||
lineno: int | None
|
|
||||||
colno: int | None
|
|
||||||
|
|
||||||
def __init__(self, filename: str | None, lineno: int | None = None, colno: int | None = None) -> None:
|
|
||||||
self.filename = filename
|
|
||||||
self.lineno = lineno
|
|
||||||
self.colno = colno
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"SourceRef({self.filename!r}, {self.lineno!r}, {self.colno!r})"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f"{self.filename}:{self.lineno:>4}:{self.colno:<3}"
|
|
||||||
|
|
||||||
class Expression:
|
class Expression:
|
||||||
"""
|
"""
|
||||||
An expression within a statement
|
An expression within a statement
|
||||||
"""
|
"""
|
||||||
__slots__ = ('type5', 'sourceref', )
|
__slots__ = ('type3', )
|
||||||
|
|
||||||
sourceref: SourceRef
|
type3: Type3OrPlaceholder
|
||||||
type5: type5typeexpr.TypeExpr | None
|
|
||||||
|
|
||||||
def __init__(self, *, sourceref: SourceRef) -> None:
|
def __init__(self) -> None:
|
||||||
self.sourceref = sourceref
|
self.type3 = PlaceholderForType([self])
|
||||||
self.type5 = None
|
|
||||||
|
|
||||||
class Constant(Expression):
|
class Constant(Expression):
|
||||||
"""
|
"""
|
||||||
@ -56,90 +38,29 @@ class ConstantPrimitive(Constant):
|
|||||||
"""
|
"""
|
||||||
__slots__ = ('value', )
|
__slots__ = ('value', )
|
||||||
|
|
||||||
value: int | float
|
value: Union[int, float]
|
||||||
|
|
||||||
def __init__(self, value: int | float, sourceref: SourceRef) -> None:
|
def __init__(self, value: Union[int, float]) -> None:
|
||||||
super().__init__(sourceref=sourceref)
|
super().__init__()
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'ConstantPrimitive({repr(self.value)})'
|
return f'ConstantPrimitive({repr(self.value)})'
|
||||||
|
|
||||||
class ConstantMemoryStored(Constant):
|
class ConstantTuple(Constant):
|
||||||
"""
|
|
||||||
An constant value expression within a statement
|
|
||||||
|
|
||||||
# FIXME: Rename to literal
|
|
||||||
"""
|
|
||||||
__slots__ = ('data_block', )
|
|
||||||
|
|
||||||
data_block: 'ModuleDataBlock'
|
|
||||||
|
|
||||||
def __init__(self, data_block: 'ModuleDataBlock', sourceref: SourceRef) -> None:
|
|
||||||
super().__init__(sourceref=sourceref)
|
|
||||||
self.data_block = data_block
|
|
||||||
|
|
||||||
class ConstantBytes(ConstantMemoryStored):
|
|
||||||
"""
|
|
||||||
A bytes constant value expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('value', )
|
|
||||||
|
|
||||||
value: bytes
|
|
||||||
|
|
||||||
def __init__(self, value: bytes, data_block: 'ModuleDataBlock', sourceref: SourceRef) -> None:
|
|
||||||
super().__init__(data_block, sourceref=sourceref)
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
# Do not repr the whole ModuleDataBlock
|
|
||||||
# As this has a reference back to this constant for its data
|
|
||||||
# which it needs to compile the data into the program
|
|
||||||
return f'ConstantBytes({repr(self.value)}, @{repr(self.data_block.address)})'
|
|
||||||
|
|
||||||
class ConstantTuple(ConstantMemoryStored):
|
|
||||||
"""
|
"""
|
||||||
A Tuple constant value expression within a statement
|
A Tuple constant value expression within a statement
|
||||||
"""
|
"""
|
||||||
__slots__ = ('value', )
|
__slots__ = ('value', )
|
||||||
|
|
||||||
value: List[Union[ConstantPrimitive, ConstantBytes, 'ConstantTuple', 'ConstantStruct']]
|
value: List[ConstantPrimitive]
|
||||||
|
|
||||||
def __init__(self, value: List[Union[ConstantPrimitive, ConstantBytes, 'ConstantTuple', 'ConstantStruct']], data_block: 'ModuleDataBlock', sourceref: SourceRef) -> None:
|
def __init__(self, value: List[ConstantPrimitive]) -> None: # FIXME: Tuple of tuples?
|
||||||
super().__init__(data_block, sourceref=sourceref)
|
super().__init__()
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
# Do not repr the whole ModuleDataBlock
|
return f'ConstantTuple({repr(self.value)})'
|
||||||
# As this has a reference back to this constant for its data
|
|
||||||
# which it needs to compile the data into the program
|
|
||||||
return f'ConstantTuple({repr(self.value)}, @{repr(self.data_block.address)})'
|
|
||||||
|
|
||||||
class ConstantStruct(ConstantMemoryStored):
|
|
||||||
"""
|
|
||||||
A Struct constant value expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('struct_type5', 'value', )
|
|
||||||
|
|
||||||
struct_type5: type5record.Record
|
|
||||||
value: list[ConstantPrimitive | ConstantBytes | ConstantTuple | ConstantStruct]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
struct_type5: type5record.Record,
|
|
||||||
value: list[ConstantPrimitive | ConstantBytes | ConstantTuple | ConstantStruct],
|
|
||||||
data_block: 'ModuleDataBlock',
|
|
||||||
sourceref: SourceRef
|
|
||||||
) -> None:
|
|
||||||
super().__init__(data_block, sourceref=sourceref)
|
|
||||||
self.struct_type5 = struct_type5
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
# Do not repr the whole ModuleDataBlock
|
|
||||||
# As this has a reference back to this constant for its data
|
|
||||||
# which it needs to compile the data into the program
|
|
||||||
return f'ConstantStruct({self.struct_type5!r}, {self.value!r}, @{self.data_block.address!r})'
|
|
||||||
|
|
||||||
class VariableReference(Expression):
|
class VariableReference(Expression):
|
||||||
"""
|
"""
|
||||||
@ -149,73 +70,56 @@ class VariableReference(Expression):
|
|||||||
|
|
||||||
variable: Union['ModuleConstantDef', 'FunctionParam'] # also possibly local
|
variable: Union['ModuleConstantDef', 'FunctionParam'] # also possibly local
|
||||||
|
|
||||||
def __init__(self, variable: Union['ModuleConstantDef', 'FunctionParam'], sourceref: SourceRef) -> None:
|
def __init__(self, variable: Union['ModuleConstantDef', 'FunctionParam']) -> None:
|
||||||
super().__init__(sourceref=sourceref)
|
super().__init__()
|
||||||
self.variable = variable
|
self.variable = variable
|
||||||
|
|
||||||
|
class UnaryOp(Expression):
|
||||||
|
"""
|
||||||
|
A unary operator expression within a statement
|
||||||
|
"""
|
||||||
|
__slots__ = ('operator', 'right', )
|
||||||
|
|
||||||
|
operator: str
|
||||||
|
right: Expression
|
||||||
|
|
||||||
|
def __init__(self, operator: str, right: Expression) -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.operator = operator
|
||||||
|
self.right = right
|
||||||
|
|
||||||
class BinaryOp(Expression):
|
class BinaryOp(Expression):
|
||||||
"""
|
"""
|
||||||
A binary operator expression within a statement
|
A binary operator expression within a statement
|
||||||
"""
|
"""
|
||||||
__slots__ = ('operator', 'polytype_substitutions', 'left', 'right', )
|
__slots__ = ('operator', 'left', 'right', )
|
||||||
|
|
||||||
operator: Function | FunctionParam
|
operator: str
|
||||||
polytype_substitutions: dict[type5typeexpr.TypeVariable, type5typeexpr.TypeExpr]
|
|
||||||
left: Expression
|
left: Expression
|
||||||
right: Expression
|
right: Expression
|
||||||
|
|
||||||
def __init__(self, operator: Function | FunctionParam, left: Expression, right: Expression, sourceref: SourceRef) -> None:
|
def __init__(self, operator: str, left: Expression, right: Expression) -> None:
|
||||||
super().__init__(sourceref=sourceref)
|
super().__init__()
|
||||||
|
|
||||||
self.operator = operator
|
self.operator = operator
|
||||||
self.polytype_substitutions = {}
|
|
||||||
self.left = left
|
self.left = left
|
||||||
self.right = right
|
self.right = right
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f'BinaryOp({repr(self.operator)}, {repr(self.left)}, {repr(self.right)})'
|
|
||||||
|
|
||||||
class FunctionCall(Expression):
|
class FunctionCall(Expression):
|
||||||
"""
|
"""
|
||||||
A function call expression within a statement
|
A function call expression within a statement
|
||||||
"""
|
"""
|
||||||
__slots__ = ('function', 'polytype_substitutions', 'arguments', )
|
__slots__ = ('function', 'arguments', )
|
||||||
|
|
||||||
function: Function | FunctionParam
|
|
||||||
polytype_substitutions: dict[type5typeexpr.TypeVariable, type5typeexpr.TypeExpr]
|
|
||||||
arguments: List[Expression]
|
|
||||||
|
|
||||||
def __init__(self, function: Function | FunctionParam, sourceref: SourceRef) -> None:
|
|
||||||
super().__init__(sourceref=sourceref)
|
|
||||||
|
|
||||||
self.function = function
|
|
||||||
self.polytype_substitutions = {}
|
|
||||||
self.arguments = []
|
|
||||||
|
|
||||||
class FunctionReference(Expression):
|
|
||||||
"""
|
|
||||||
An function reference expression within a statement
|
|
||||||
"""
|
|
||||||
__slots__ = ('function', )
|
|
||||||
|
|
||||||
function: 'Function'
|
function: 'Function'
|
||||||
|
arguments: List[Expression]
|
||||||
|
|
||||||
|
def __init__(self, function: 'Function') -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
def __init__(self, function: 'Function', sourceref: SourceRef) -> None:
|
|
||||||
super().__init__(sourceref=sourceref)
|
|
||||||
self.function = function
|
self.function = function
|
||||||
|
self.arguments = []
|
||||||
class TupleInstantiation(Expression):
|
|
||||||
"""
|
|
||||||
Instantiation a tuple
|
|
||||||
"""
|
|
||||||
__slots__ = ('elements', )
|
|
||||||
|
|
||||||
elements: List[Expression]
|
|
||||||
|
|
||||||
def __init__(self, elements: List[Expression], sourceref: SourceRef) -> None:
|
|
||||||
super().__init__(sourceref=sourceref)
|
|
||||||
|
|
||||||
self.elements = elements
|
|
||||||
|
|
||||||
class Subscript(Expression):
|
class Subscript(Expression):
|
||||||
"""
|
"""
|
||||||
@ -227,8 +131,8 @@ class Subscript(Expression):
|
|||||||
varref: VariableReference
|
varref: VariableReference
|
||||||
index: Expression
|
index: Expression
|
||||||
|
|
||||||
def __init__(self, varref: VariableReference, index: Expression, sourceref: SourceRef) -> None:
|
def __init__(self, varref: VariableReference, index: Expression) -> None:
|
||||||
super().__init__(sourceref=sourceref)
|
super().__init__()
|
||||||
|
|
||||||
self.varref = varref
|
self.varref = varref
|
||||||
self.index = index
|
self.index = index
|
||||||
@ -237,27 +141,54 @@ class AccessStructMember(Expression):
|
|||||||
"""
|
"""
|
||||||
Access a struct member for reading of writing
|
Access a struct member for reading of writing
|
||||||
"""
|
"""
|
||||||
__slots__ = ('varref', 'member', )
|
__slots__ = ('varref', 'struct_type3', 'member', )
|
||||||
|
|
||||||
varref: VariableReference
|
varref: VariableReference
|
||||||
|
struct_type3: StructType3
|
||||||
member: str
|
member: str
|
||||||
|
|
||||||
def __init__(self, varref: VariableReference, member: str, sourceref: SourceRef) -> None:
|
def __init__(self, varref: VariableReference, struct_type3: StructType3, member: str) -> None:
|
||||||
super().__init__(sourceref=sourceref)
|
super().__init__()
|
||||||
|
|
||||||
self.varref = varref
|
self.varref = varref
|
||||||
|
self.struct_type3 = struct_type3
|
||||||
self.member = member
|
self.member = member
|
||||||
|
|
||||||
|
class Fold(Expression):
|
||||||
|
"""
|
||||||
|
A (left or right) fold
|
||||||
|
"""
|
||||||
|
class Direction(enum.Enum):
|
||||||
|
"""
|
||||||
|
Which direction to fold in
|
||||||
|
"""
|
||||||
|
LEFT = 0
|
||||||
|
RIGHT = 1
|
||||||
|
|
||||||
|
dir: Direction
|
||||||
|
func: 'Function'
|
||||||
|
base: Expression
|
||||||
|
iter: Expression
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
dir_: Direction,
|
||||||
|
func: 'Function',
|
||||||
|
base: Expression,
|
||||||
|
iter_: Expression,
|
||||||
|
) -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.dir = dir_
|
||||||
|
self.func = func
|
||||||
|
self.base = base
|
||||||
|
self.iter = iter_
|
||||||
|
|
||||||
class Statement:
|
class Statement:
|
||||||
"""
|
"""
|
||||||
A statement within a function
|
A statement within a function
|
||||||
"""
|
"""
|
||||||
__slots__ = ("sourceref", )
|
__slots__ = ()
|
||||||
|
|
||||||
sourceref: SourceRef
|
|
||||||
|
|
||||||
def __init__(self, *, sourceref: SourceRef) -> None:
|
|
||||||
self.sourceref = sourceref
|
|
||||||
|
|
||||||
class StatementPass(Statement):
|
class StatementPass(Statement):
|
||||||
"""
|
"""
|
||||||
@ -265,23 +196,15 @@ class StatementPass(Statement):
|
|||||||
"""
|
"""
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
def __init__(self, sourceref: SourceRef) -> None:
|
|
||||||
super().__init__(sourceref=sourceref)
|
|
||||||
|
|
||||||
class StatementReturn(Statement):
|
class StatementReturn(Statement):
|
||||||
"""
|
"""
|
||||||
A return statement within a function
|
A return statement within a function
|
||||||
"""
|
"""
|
||||||
__slots__ = ('value', )
|
__slots__ = ('value', )
|
||||||
|
|
||||||
def __init__(self, value: Expression, sourceref: SourceRef) -> None:
|
def __init__(self, value: Expression) -> None:
|
||||||
super().__init__(sourceref=sourceref)
|
|
||||||
|
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f'StatementReturn({repr(self.value)})'
|
|
||||||
|
|
||||||
class StatementIf(Statement):
|
class StatementIf(Statement):
|
||||||
"""
|
"""
|
||||||
An if statement within a function
|
An if statement within a function
|
||||||
@ -301,60 +224,50 @@ class FunctionParam:
|
|||||||
"""
|
"""
|
||||||
A parameter for a Function
|
A parameter for a Function
|
||||||
"""
|
"""
|
||||||
__slots__ = ('name', 'type5', )
|
__slots__ = ('name', 'type3', )
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
type5: type5typeexpr.TypeExpr
|
type3: Type3OrPlaceholder
|
||||||
|
|
||||||
def __init__(self, name: str, type5: type5typeexpr.TypeExpr) -> None:
|
|
||||||
assert type5typeexpr.is_concrete(type5)
|
|
||||||
|
|
||||||
|
def __init__(self, name: str, type3: Optional[Type3]) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.type5 = type5
|
self.type3 = PlaceholderForType([self]) if type3 is None else type3
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f'FunctionParam({self.name!r}, {self.type5!r})'
|
|
||||||
|
|
||||||
class Function:
|
class Function:
|
||||||
"""
|
"""
|
||||||
A function processes input and produces output
|
A function processes input and produces output
|
||||||
"""
|
"""
|
||||||
__slots__ = ('name', 'sourceref', 'exported', 'imported', 'statements', 'type5', 'arg_names', )
|
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns_type3', 'posonlyargs', )
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
sourceref: SourceRef
|
lineno: int
|
||||||
exported: bool
|
exported: bool
|
||||||
imported: Optional[str]
|
imported: bool
|
||||||
statements: List[Statement]
|
statements: List[Statement]
|
||||||
type5: type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr | None
|
returns_type3: Type3
|
||||||
arg_names: list[str]
|
posonlyargs: List[FunctionParam]
|
||||||
|
|
||||||
def __init__(self, name: str, sourceref: SourceRef) -> None:
|
def __init__(self, name: str, lineno: int) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.sourceref = sourceref
|
self.lineno = lineno
|
||||||
self.exported = False
|
self.exported = False
|
||||||
self.imported = None
|
self.imported = False
|
||||||
self.statements = []
|
self.statements = []
|
||||||
self.type5 = None
|
self.returns_type3 = type3types.none # FIXME: This could be a placeholder
|
||||||
self.arg_names = []
|
self.posonlyargs = []
|
||||||
|
|
||||||
class BuiltinFunction(Function):
|
|
||||||
def __init__(self, name: str, type5: type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr) -> None:
|
|
||||||
super().__init__(name, SourceRef("/", 0, 0))
|
|
||||||
self.type5 = type5
|
|
||||||
|
|
||||||
class StructDefinition:
|
class StructDefinition:
|
||||||
"""
|
"""
|
||||||
The definition for a struct
|
The definition for a struct
|
||||||
"""
|
"""
|
||||||
__slots__ = ('struct_type5', 'sourceref', )
|
__slots__ = ('struct_type3', 'lineno', )
|
||||||
|
|
||||||
struct_type5: type5record.Record
|
struct_type3: StructType3
|
||||||
sourceref: SourceRef
|
lineno: int
|
||||||
|
|
||||||
def __init__(self, struct_type5: type5record.Record, sourceref: SourceRef) -> None:
|
def __init__(self, struct_type3: StructType3, lineno: int) -> None:
|
||||||
self.struct_type5 = struct_type5
|
self.struct_type3 = struct_type3
|
||||||
self.sourceref = sourceref
|
self.lineno = lineno
|
||||||
|
|
||||||
class StructConstructor(Function):
|
class StructConstructor(Function):
|
||||||
"""
|
"""
|
||||||
@ -363,33 +276,59 @@ class StructConstructor(Function):
|
|||||||
A function will generated to instantiate a struct. The arguments
|
A function will generated to instantiate a struct. The arguments
|
||||||
will be the defaults
|
will be the defaults
|
||||||
"""
|
"""
|
||||||
__slots__ = ('struct_type5', )
|
__slots__ = ('struct_type3', )
|
||||||
|
|
||||||
struct_type5: type5record.Record
|
struct_type3: StructType3
|
||||||
|
|
||||||
def __init__(self, struct_type5: type5record.Record, sourceref: SourceRef) -> None:
|
def __init__(self, struct_type3: StructType3) -> None:
|
||||||
super().__init__(f'@{struct_type5.name}@__init___@', sourceref)
|
super().__init__(f'@{struct_type3.name}@__init___@', -1)
|
||||||
self.struct_type5 = struct_type5
|
|
||||||
|
|
||||||
for mem, typ in struct_type5.fields:
|
self.returns_type3 = struct_type3
|
||||||
self.arg_names.append(mem)
|
|
||||||
|
for mem, typ in struct_type3.members.items():
|
||||||
|
self.posonlyargs.append(FunctionParam(mem, typ, ))
|
||||||
|
|
||||||
|
self.struct_type3 = struct_type3
|
||||||
|
|
||||||
|
# TODO: Broken after new type system
|
||||||
|
# class TupleConstructor(Function):
|
||||||
|
# """
|
||||||
|
# The constructor method for a tuple
|
||||||
|
# """
|
||||||
|
# __slots__ = ('tuple', )
|
||||||
|
#
|
||||||
|
# tuple: TypeTuple
|
||||||
|
#
|
||||||
|
# def __init__(self, tuple_: TypeTuple) -> None:
|
||||||
|
# name = tuple_.render_internal_name()
|
||||||
|
#
|
||||||
|
# super().__init__(f'@{name}@__init___@', -1)
|
||||||
|
#
|
||||||
|
# self.returns = tuple_
|
||||||
|
#
|
||||||
|
# for mem in tuple_.members:
|
||||||
|
# self.posonlyargs.append(FunctionParam(f'arg{mem.idx}', mem.type, ))
|
||||||
|
#
|
||||||
|
# self.tuple = tuple_
|
||||||
|
|
||||||
class ModuleConstantDef:
|
class ModuleConstantDef:
|
||||||
"""
|
"""
|
||||||
A constant definition within a module
|
A constant definition within a module
|
||||||
"""
|
"""
|
||||||
__slots__ = ('name', 'sourceref', 'type5', 'constant', )
|
__slots__ = ('name', 'lineno', 'type3', 'constant', 'data_block', )
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
sourceref: SourceRef
|
lineno: int
|
||||||
type5: type5typeexpr.TypeExpr
|
type3: Type3
|
||||||
constant: Constant
|
constant: Constant
|
||||||
|
data_block: Optional['ModuleDataBlock']
|
||||||
|
|
||||||
def __init__(self, name: str, sourceref: SourceRef, type5: type5typeexpr.TypeExpr, constant: Constant) -> None:
|
def __init__(self, name: str, lineno: int, type3: Type3, constant: Constant, data_block: Optional['ModuleDataBlock']) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.sourceref = sourceref
|
self.lineno = lineno
|
||||||
self.type5 = type5
|
self.type3 = type3
|
||||||
self.constant = constant
|
self.constant = constant
|
||||||
|
self.data_block = data_block
|
||||||
|
|
||||||
class ModuleDataBlock:
|
class ModuleDataBlock:
|
||||||
"""
|
"""
|
||||||
@ -397,16 +336,13 @@ class ModuleDataBlock:
|
|||||||
"""
|
"""
|
||||||
__slots__ = ('data', 'address', )
|
__slots__ = ('data', 'address', )
|
||||||
|
|
||||||
data: List[Union[ConstantPrimitive, ConstantMemoryStored]]
|
data: List[ConstantPrimitive]
|
||||||
address: Optional[int]
|
address: Optional[int]
|
||||||
|
|
||||||
def __init__(self, data: Iterable[Union[ConstantPrimitive, ConstantMemoryStored]]) -> None:
|
def __init__(self, data: List[ConstantPrimitive]) -> None:
|
||||||
self.data = [*data]
|
self.data = data
|
||||||
self.address = None
|
self.address = None
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f'ModuleDataBlock({self.data!r}, {self.address!r})'
|
|
||||||
|
|
||||||
class ModuleData:
|
class ModuleData:
|
||||||
"""
|
"""
|
||||||
The data for when a module is loaded into memory
|
The data for when a module is loaded into memory
|
||||||
@ -418,32 +354,19 @@ class ModuleData:
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.blocks = []
|
self.blocks = []
|
||||||
|
|
||||||
class Module[G]:
|
class Module:
|
||||||
"""
|
"""
|
||||||
A module is a file and consists of functions
|
A module is a file and consists of functions
|
||||||
"""
|
"""
|
||||||
__slots__ = ('build', 'filename', 'data', 'types', 'type5s', 'struct_definitions', 'constant_defs', 'functions', 'methods', 'operators', 'functions_table', )
|
__slots__ = ('data', 'types', 'struct_definitions', 'constant_defs', 'functions',)
|
||||||
|
|
||||||
build: BuildBase[G]
|
|
||||||
filename: str
|
|
||||||
data: ModuleData
|
data: ModuleData
|
||||||
types: dict[str, type5typeexpr.TypeExpr]
|
|
||||||
struct_definitions: Dict[str, StructDefinition]
|
struct_definitions: Dict[str, StructDefinition]
|
||||||
constant_defs: Dict[str, ModuleConstantDef]
|
constant_defs: Dict[str, ModuleConstantDef]
|
||||||
functions: Dict[str, Function]
|
functions: Dict[str, Function]
|
||||||
methods: Dict[str, type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr]
|
|
||||||
operators: Dict[str, type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr]
|
|
||||||
functions_table: dict[Function, int]
|
|
||||||
|
|
||||||
def __init__(self, build: BuildBase[G], filename: str) -> None:
|
|
||||||
self.build = build
|
|
||||||
self.filename = filename
|
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
self.data = ModuleData()
|
self.data = ModuleData()
|
||||||
self.types = {}
|
|
||||||
self.struct_definitions = {}
|
self.struct_definitions = {}
|
||||||
self.constant_defs = {}
|
self.constant_defs = {}
|
||||||
self.functions = {}
|
self.functions = {}
|
||||||
self.methods = {}
|
|
||||||
self.operators = {}
|
|
||||||
self.functions_table = {}
|
|
||||||
|
|||||||
677
phasm/parser.py
677
phasm/parser.py
@ -1,84 +1,49 @@
|
|||||||
"""
|
"""
|
||||||
Parses the source code from the plain text into a syntax tree
|
Parses the source code from the plain text into a syntax tree
|
||||||
"""
|
"""
|
||||||
import ast
|
|
||||||
from typing import Any, Dict, NoReturn, Union
|
from typing import Any, Dict, NoReturn, Union
|
||||||
|
|
||||||
from .build.base import BuildBase
|
import ast
|
||||||
from .build.default import BuildDefault
|
|
||||||
|
from .type3 import types as type3types
|
||||||
|
|
||||||
from .exceptions import StaticError
|
from .exceptions import StaticError
|
||||||
from .ourlang import (
|
from .ourlang import (
|
||||||
AccessStructMember,
|
WEBASSEMBLY_BUILTIN_FLOAT_OPS,
|
||||||
BinaryOp,
|
|
||||||
BuiltinFunction,
|
Module, ModuleDataBlock,
|
||||||
ConstantBytes,
|
|
||||||
ConstantPrimitive,
|
|
||||||
ConstantStruct,
|
|
||||||
ConstantTuple,
|
|
||||||
Expression,
|
|
||||||
Function,
|
Function,
|
||||||
FunctionCall,
|
|
||||||
FunctionParam,
|
Expression,
|
||||||
FunctionReference,
|
BinaryOp,
|
||||||
Module,
|
ConstantPrimitive, ConstantTuple,
|
||||||
ModuleConstantDef,
|
|
||||||
ModuleDataBlock,
|
FunctionCall, AccessStructMember, Subscript,
|
||||||
SourceRef,
|
StructDefinition, StructConstructor,
|
||||||
|
# TupleConstructor,
|
||||||
|
UnaryOp, VariableReference,
|
||||||
|
|
||||||
|
Fold,
|
||||||
|
|
||||||
Statement,
|
Statement,
|
||||||
StatementIf,
|
StatementIf, StatementPass, StatementReturn,
|
||||||
StatementPass,
|
|
||||||
StatementReturn,
|
FunctionParam,
|
||||||
StructConstructor,
|
ModuleConstantDef,
|
||||||
StructDefinition,
|
|
||||||
Subscript,
|
|
||||||
TupleInstantiation,
|
|
||||||
VariableReference,
|
|
||||||
)
|
)
|
||||||
from .type5 import typeexpr as type5typeexpr
|
|
||||||
from .wasmgenerator import Generator
|
|
||||||
|
|
||||||
|
def phasm_parse(source: str) -> Module:
|
||||||
def phasm_parse(source: str) -> Module[Generator]:
|
|
||||||
"""
|
"""
|
||||||
Public method for parsing Phasm code into a Phasm Module
|
Public method for parsing Phasm code into a Phasm Module
|
||||||
"""
|
"""
|
||||||
res = ast.parse(source, '')
|
res = ast.parse(source, '')
|
||||||
|
|
||||||
res = OptimizerTransformer().visit(res)
|
our_visitor = OurVisitor()
|
||||||
|
|
||||||
build = BuildDefault()
|
|
||||||
our_visitor = OurVisitor(build)
|
|
||||||
return our_visitor.visit_Module(res)
|
return our_visitor.visit_Module(res)
|
||||||
|
|
||||||
OurLocals = Dict[str, Union[FunctionParam]] # FIXME: Does it become easier if we add ModuleConstantDef to this dict?
|
OurLocals = Dict[str, Union[FunctionParam]] # Also local variable and module constants?
|
||||||
|
|
||||||
class OptimizerTransformer(ast.NodeTransformer):
|
class OurVisitor:
|
||||||
"""
|
|
||||||
This class optimizes the Python AST, to prepare it for parsing
|
|
||||||
by the OurVisitor class below.
|
|
||||||
"""
|
|
||||||
def visit_UnaryOp(self, node: ast.UnaryOp) -> Union[ast.UnaryOp, ast.Constant]:
|
|
||||||
"""
|
|
||||||
UnaryOp optimizations
|
|
||||||
|
|
||||||
In the given example:
|
|
||||||
```py
|
|
||||||
x = -4
|
|
||||||
```
|
|
||||||
Python will parse it as a unary minus operation on the constant four.
|
|
||||||
For Phasm purposes, this counts as a literal -4.
|
|
||||||
"""
|
|
||||||
if (
|
|
||||||
isinstance(node.op, (ast.UAdd, ast.USub, ))
|
|
||||||
and isinstance(node.operand, ast.Constant)
|
|
||||||
and isinstance(node.operand.value, (int, float, ))
|
|
||||||
):
|
|
||||||
if isinstance(node.op, ast.USub):
|
|
||||||
node.operand.value = -node.operand.value
|
|
||||||
return node.operand
|
|
||||||
return node
|
|
||||||
|
|
||||||
class OurVisitor[G]:
|
|
||||||
"""
|
"""
|
||||||
Class to visit a Python syntax tree and create an ourlang syntax tree
|
Class to visit a Python syntax tree and create an ourlang syntax tree
|
||||||
|
|
||||||
@ -86,22 +51,15 @@ class OurVisitor[G]:
|
|||||||
|
|
||||||
At some point, we may deviate from Python syntax. If nothing else,
|
At some point, we may deviate from Python syntax. If nothing else,
|
||||||
we probably won't keep up with the Python syntax changes.
|
we probably won't keep up with the Python syntax changes.
|
||||||
|
|
||||||
See OptimizerTransformer for the changes we make after the Python
|
|
||||||
parsing is done but before the phasm parsing is done.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=C0103,C0116,C0301,R0201,R0912
|
# pylint: disable=C0103,C0116,C0301,R0201,R0912
|
||||||
|
|
||||||
def __init__(self, build: BuildBase[G]) -> None:
|
def __init__(self) -> None:
|
||||||
self.build = build
|
pass
|
||||||
|
|
||||||
def visit_Module(self, node: ast.Module) -> Module[G]:
|
def visit_Module(self, node: ast.Module) -> Module:
|
||||||
module = Module(self.build, "-")
|
module = Module()
|
||||||
|
|
||||||
module.methods.update({k: v[0] for k, v in self.build.methods.items()})
|
|
||||||
module.operators.update({k: v[0] for k, v in self.build.operators.items()})
|
|
||||||
module.types.update(self.build.types)
|
|
||||||
|
|
||||||
_not_implemented(not node.type_ignores, 'Module.type_ignores')
|
_not_implemented(not node.type_ignores, 'Module.type_ignores')
|
||||||
|
|
||||||
@ -113,27 +71,25 @@ class OurVisitor[G]:
|
|||||||
if isinstance(res, ModuleConstantDef):
|
if isinstance(res, ModuleConstantDef):
|
||||||
if res.name in module.constant_defs:
|
if res.name in module.constant_defs:
|
||||||
raise StaticError(
|
raise StaticError(
|
||||||
f'{res.name} already defined on line {module.constant_defs[res.name].sourceref.lineno}'
|
f'{res.name} already defined on line {module.constant_defs[res.name].lineno}'
|
||||||
)
|
)
|
||||||
|
|
||||||
module.constant_defs[res.name] = res
|
module.constant_defs[res.name] = res
|
||||||
|
|
||||||
if isinstance(res, StructDefinition):
|
if isinstance(res, StructDefinition):
|
||||||
if res.struct_type5.name in module.types:
|
if res.struct_type3.name in module.struct_definitions:
|
||||||
raise StaticError(
|
raise StaticError(
|
||||||
f'{res.struct_type5.name} already defined as type'
|
f'{res.struct_type3.name} already defined on line {module.struct_definitions[res.struct_type3.name].lineno}'
|
||||||
)
|
)
|
||||||
|
|
||||||
module.types[res.struct_type5.name] = res.struct_type5
|
module.struct_definitions[res.struct_type3.name] = res
|
||||||
module.functions[res.struct_type5.name] = StructConstructor(res.struct_type5, res.sourceref)
|
constructor = StructConstructor(res.struct_type3)
|
||||||
module.functions[res.struct_type5.name].type5 = module.build.type5_make_function([x[1] for x in res.struct_type5.fields] + [res.struct_type5])
|
module.functions[constructor.name] = constructor
|
||||||
# Store that the definition was done in this module for the formatter
|
|
||||||
module.struct_definitions[res.struct_type5.name] = res
|
|
||||||
|
|
||||||
if isinstance(res, Function):
|
if isinstance(res, Function):
|
||||||
if res.name in module.functions:
|
if res.name in module.functions:
|
||||||
raise StaticError(
|
raise StaticError(
|
||||||
f'{res.name} already defined on line {module.functions[res.name].sourceref.lineno}'
|
f'{res.name} already defined on line {module.functions[res.name].lineno}'
|
||||||
)
|
)
|
||||||
|
|
||||||
module.functions[res.name] = res
|
module.functions[res.name] = res
|
||||||
@ -145,7 +101,7 @@ class OurVisitor[G]:
|
|||||||
|
|
||||||
return module
|
return module
|
||||||
|
|
||||||
def pre_visit_Module_stmt(self, module: Module[G], node: ast.stmt) -> Union[Function, StructDefinition, ModuleConstantDef]:
|
def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, StructDefinition, ModuleConstantDef]:
|
||||||
if isinstance(node, ast.FunctionDef):
|
if isinstance(node, ast.FunctionDef):
|
||||||
return self.pre_visit_Module_FunctionDef(module, node)
|
return self.pre_visit_Module_FunctionDef(module, node)
|
||||||
|
|
||||||
@ -157,25 +113,16 @@ class OurVisitor[G]:
|
|||||||
|
|
||||||
raise NotImplementedError(f'{node} on Module')
|
raise NotImplementedError(f'{node} on Module')
|
||||||
|
|
||||||
def pre_visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> Function:
|
def pre_visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> Function:
|
||||||
function = Function(node.name, srf(module, node))
|
function = Function(node.name, node.lineno)
|
||||||
|
|
||||||
_not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs')
|
_not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs')
|
||||||
|
|
||||||
arg_type5_list = []
|
|
||||||
|
|
||||||
for arg in node.args.args:
|
for arg in node.args.args:
|
||||||
if arg.annotation is None:
|
function.posonlyargs.append(FunctionParam(
|
||||||
_raise_static_error(node, 'Must give a argument type')
|
arg.arg,
|
||||||
|
self.visit_type(module, arg.annotation) if arg.annotation else None,
|
||||||
arg_type5 = self.visit_type5(module, arg.annotation)
|
))
|
||||||
|
|
||||||
arg_type5_list.append(arg_type5)
|
|
||||||
|
|
||||||
# FIXME: Allow TypeVariable in the function signature
|
|
||||||
# This would also require FunctionParam to accept a placeholder
|
|
||||||
|
|
||||||
function.arg_names.append(arg.arg)
|
|
||||||
|
|
||||||
_not_implemented(not node.args.vararg, 'FunctionDef.args.vararg')
|
_not_implemented(not node.args.vararg, 'FunctionDef.args.vararg')
|
||||||
_not_implemented(not node.args.kwonlyargs, 'FunctionDef.args.kwonlyargs')
|
_not_implemented(not node.args.kwonlyargs, 'FunctionDef.args.kwonlyargs')
|
||||||
@ -186,24 +133,6 @@ class OurVisitor[G]:
|
|||||||
# Do stmts at the end so we have the return value
|
# Do stmts at the end so we have the return value
|
||||||
|
|
||||||
for decorator in node.decorator_list:
|
for decorator in node.decorator_list:
|
||||||
if isinstance(decorator, ast.Call):
|
|
||||||
if not isinstance(decorator.func, ast.Name):
|
|
||||||
_raise_static_error(decorator, 'Function decorators must be string')
|
|
||||||
if not isinstance(decorator.func.ctx, ast.Load):
|
|
||||||
_raise_static_error(decorator, 'Must be load context')
|
|
||||||
_not_implemented(decorator.func.id == 'imported', 'Custom decorators')
|
|
||||||
|
|
||||||
if 1 != len(decorator.args):
|
|
||||||
_raise_static_error(decorator, 'One argument expected')
|
|
||||||
if not isinstance(decorator.args[0], ast.Constant):
|
|
||||||
_raise_static_error(decorator, 'Service name must be a constant')
|
|
||||||
if not isinstance(decorator.args[0].value, str):
|
|
||||||
_raise_static_error(decorator, 'Service name must be a stirng')
|
|
||||||
if 0 != len(decorator.keywords): # TODO: Allow for namespace keyword
|
|
||||||
_raise_static_error(decorator, 'No keyword arguments expected')
|
|
||||||
|
|
||||||
function.imported = decorator.args[0].value
|
|
||||||
else:
|
|
||||||
if not isinstance(decorator, ast.Name):
|
if not isinstance(decorator, ast.Name):
|
||||||
_raise_static_error(decorator, 'Function decorators must be string')
|
_raise_static_error(decorator, 'Function decorators must be string')
|
||||||
if not isinstance(decorator.ctx, ast.Load):
|
if not isinstance(decorator.ctx, ast.Load):
|
||||||
@ -213,25 +142,25 @@ class OurVisitor[G]:
|
|||||||
if decorator.id == 'exported':
|
if decorator.id == 'exported':
|
||||||
function.exported = True
|
function.exported = True
|
||||||
else:
|
else:
|
||||||
function.imported = 'imports'
|
function.imported = True
|
||||||
|
|
||||||
if node.returns is None: # Note: `-> None` would be a ast.Constant
|
if node.returns is not None: # Note: `-> None` would be a ast.Constant
|
||||||
_raise_static_error(node, 'Must give a return type')
|
function.returns_type3 = self.visit_type(module, node.returns)
|
||||||
arg_type5_list.append(self.visit_type5(module, node.returns))
|
else:
|
||||||
|
# Mostly works already, needs to fix Function.returns_type3 and have it updated
|
||||||
function.type5 = module.build.type5_make_function(arg_type5_list)
|
raise NotImplementedError('Function without an explicit return type')
|
||||||
|
|
||||||
_not_implemented(not node.type_comment, 'FunctionDef.type_comment')
|
_not_implemented(not node.type_comment, 'FunctionDef.type_comment')
|
||||||
|
|
||||||
return function
|
return function
|
||||||
|
|
||||||
def pre_visit_Module_ClassDef(self, module: Module[G], node: ast.ClassDef) -> StructDefinition:
|
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> StructDefinition:
|
||||||
|
|
||||||
_not_implemented(not node.bases, 'ClassDef.bases')
|
_not_implemented(not node.bases, 'ClassDef.bases')
|
||||||
_not_implemented(not node.keywords, 'ClassDef.keywords')
|
_not_implemented(not node.keywords, 'ClassDef.keywords')
|
||||||
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
|
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
|
||||||
|
|
||||||
members: Dict[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication] = {}
|
members: Dict[str, type3types.Type3] = {}
|
||||||
|
|
||||||
for stmt in node.body:
|
for stmt in node.body:
|
||||||
if not isinstance(stmt, ast.AnnAssign):
|
if not isinstance(stmt, ast.AnnAssign):
|
||||||
@ -240,7 +169,7 @@ class OurVisitor[G]:
|
|||||||
if not isinstance(stmt.target, ast.Name):
|
if not isinstance(stmt.target, ast.Name):
|
||||||
raise NotImplementedError('Class with default values')
|
raise NotImplementedError('Class with default values')
|
||||||
|
|
||||||
if stmt.value is not None:
|
if not stmt.value is None:
|
||||||
raise NotImplementedError('Class with default values')
|
raise NotImplementedError('Class with default values')
|
||||||
|
|
||||||
if stmt.simple != 1:
|
if stmt.simple != 1:
|
||||||
@ -249,60 +178,108 @@ class OurVisitor[G]:
|
|||||||
if stmt.target.id in members:
|
if stmt.target.id in members:
|
||||||
_raise_static_error(stmt, 'Struct members must have unique names')
|
_raise_static_error(stmt, 'Struct members must have unique names')
|
||||||
|
|
||||||
field_type5 = self.visit_type5(module, stmt.annotation)
|
members[stmt.target.id] = self.visit_type(module, stmt.annotation)
|
||||||
assert isinstance(field_type5, (type5typeexpr.AtomicType, type5typeexpr.TypeApplication, ))
|
|
||||||
members[stmt.target.id] = field_type5
|
|
||||||
|
|
||||||
return StructDefinition(
|
return StructDefinition(type3types.StructType3(node.name, members), node.lineno)
|
||||||
module.build.type5_make_struct(node.name, tuple(members.items())),
|
|
||||||
srf(module, node),
|
|
||||||
)
|
|
||||||
|
|
||||||
def pre_visit_Module_AnnAssign(self, module: Module[G], node: ast.AnnAssign) -> ModuleConstantDef:
|
def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef:
|
||||||
if not isinstance(node.target, ast.Name):
|
if not isinstance(node.target, ast.Name):
|
||||||
_raise_static_error(node.target, 'Must be name')
|
_raise_static_error(node, 'Must be name')
|
||||||
if not isinstance(node.target.ctx, ast.Store):
|
if not isinstance(node.target.ctx, ast.Store):
|
||||||
_raise_static_error(node.target, 'Must be store context')
|
_raise_static_error(node, 'Must be load context')
|
||||||
|
|
||||||
if isinstance(node.value, ast.Constant):
|
if isinstance(node.value, ast.Constant):
|
||||||
value_data = self.visit_Module_Constant(module, node.value)
|
type3 = self.visit_type(module, node.annotation)
|
||||||
|
|
||||||
return ModuleConstantDef(
|
return ModuleConstantDef(
|
||||||
node.target.id,
|
node.target.id,
|
||||||
srf(module, node),
|
node.lineno,
|
||||||
self.visit_type5(module, node.annotation),
|
type3,
|
||||||
value_data,
|
self.visit_Module_Constant(module, node.value),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(node.value, ast.Tuple):
|
if isinstance(node.value, ast.Tuple):
|
||||||
value_data = self.visit_Module_Constant(module, node.value)
|
tuple_data = [
|
||||||
|
self.visit_Module_Constant(module, arg_node)
|
||||||
|
for arg_node in node.value.elts
|
||||||
|
if isinstance(arg_node, ast.Constant)
|
||||||
|
]
|
||||||
|
if len(node.value.elts) != len(tuple_data):
|
||||||
|
_raise_static_error(node, 'Tuple arguments must be constants')
|
||||||
|
|
||||||
assert isinstance(value_data, ConstantTuple) # type hint
|
# Allocate the data
|
||||||
|
data_block = ModuleDataBlock(tuple_data)
|
||||||
|
module.data.blocks.append(data_block)
|
||||||
|
|
||||||
# Then return the constant as a pointer
|
# Then return the constant as a pointer
|
||||||
return ModuleConstantDef(
|
return ModuleConstantDef(
|
||||||
node.target.id,
|
node.target.id,
|
||||||
srf(module, node),
|
node.lineno,
|
||||||
self.visit_type5(module, node.annotation),
|
self.visit_type(module, node.annotation),
|
||||||
value_data,
|
ConstantTuple(tuple_data),
|
||||||
|
data_block,
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(node.value, ast.Call):
|
raise NotImplementedError('TODO: Broken after new typing system')
|
||||||
value_data = self.visit_Module_Constant(module, node.value)
|
|
||||||
|
|
||||||
assert isinstance(value_data, ConstantStruct) # type hint
|
# if isinstance(exp_type, TypeTuple):
|
||||||
|
# if not isinstance(node.value, ast.Tuple):
|
||||||
|
# _raise_static_error(node, 'Must be tuple')
|
||||||
|
#
|
||||||
|
# if len(exp_type.members) != len(node.value.elts):
|
||||||
|
# _raise_static_error(node, 'Invalid number of tuple values')
|
||||||
|
#
|
||||||
|
# tuple_data = [
|
||||||
|
# self.visit_Module_Constant(module, arg_node)
|
||||||
|
# for arg_node, mem in zip(node.value.elts, exp_type.members)
|
||||||
|
# if isinstance(arg_node, ast.Constant)
|
||||||
|
# ]
|
||||||
|
# if len(exp_type.members) != len(tuple_data):
|
||||||
|
# _raise_static_error(node, 'Tuple arguments must be constants')
|
||||||
|
#
|
||||||
|
# # Allocate the data
|
||||||
|
# data_block = ModuleDataBlock(tuple_data)
|
||||||
|
# module.data.blocks.append(data_block)
|
||||||
|
#
|
||||||
|
# # Then return the constant as a pointer
|
||||||
|
# return ModuleConstantDef(
|
||||||
|
# node.target.id,
|
||||||
|
# node.lineno,
|
||||||
|
# exp_type,
|
||||||
|
# ConstantTuple(tuple_data),
|
||||||
|
# data_block,
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# if isinstance(exp_type, TypeStaticArray):
|
||||||
|
# if not isinstance(node.value, ast.Tuple):
|
||||||
|
# _raise_static_error(node, 'Must be static array')
|
||||||
|
#
|
||||||
|
# if len(exp_type.members) != len(node.value.elts):
|
||||||
|
# _raise_static_error(node, 'Invalid number of static array values')
|
||||||
|
#
|
||||||
|
# static_array_data = [
|
||||||
|
# self.visit_Module_Constant(module, arg_node)
|
||||||
|
# for arg_node in node.value.elts
|
||||||
|
# if isinstance(arg_node, ast.Constant)
|
||||||
|
# ]
|
||||||
|
# if len(exp_type.members) != len(static_array_data):
|
||||||
|
# _raise_static_error(node, 'Static array arguments must be constants')
|
||||||
|
#
|
||||||
|
# # Allocate the data
|
||||||
|
# data_block = ModuleDataBlock(static_array_data)
|
||||||
|
# module.data.blocks.append(data_block)
|
||||||
|
#
|
||||||
|
# # Then return the constant as a pointer
|
||||||
|
# return ModuleConstantDef(
|
||||||
|
# node.target.id,
|
||||||
|
# node.lineno,
|
||||||
|
# ConstantStaticArray(static_array_data),
|
||||||
|
# data_block,
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# raise NotImplementedError(f'{node} on Module AnnAssign')
|
||||||
|
|
||||||
# Then return the constant as a pointer
|
def visit_Module_stmt(self, module: Module, node: ast.stmt) -> None:
|
||||||
return ModuleConstantDef(
|
|
||||||
node.target.id,
|
|
||||||
srf(module, node),
|
|
||||||
self.visit_type5(module, node.annotation),
|
|
||||||
value_data,
|
|
||||||
)
|
|
||||||
|
|
||||||
raise NotImplementedError(f'{node} on Module AnnAssign')
|
|
||||||
|
|
||||||
def visit_Module_stmt(self, module: Module[G], node: ast.stmt) -> None:
|
|
||||||
if isinstance(node, ast.FunctionDef):
|
if isinstance(node, ast.FunctionDef):
|
||||||
self.visit_Module_FunctionDef(module, node)
|
self.visit_Module_FunctionDef(module, node)
|
||||||
return
|
return
|
||||||
@ -315,17 +292,12 @@ class OurVisitor[G]:
|
|||||||
|
|
||||||
raise NotImplementedError(f'{node} on Module')
|
raise NotImplementedError(f'{node} on Module')
|
||||||
|
|
||||||
def visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> None:
|
def visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> None:
|
||||||
function = module.functions[node.name]
|
function = module.functions[node.name]
|
||||||
assert function.type5 is not None
|
|
||||||
|
|
||||||
func_arg_types = module.build.type5_is_function(function.type5)
|
|
||||||
assert func_arg_types is not None
|
|
||||||
func_arg_types.pop()
|
|
||||||
|
|
||||||
our_locals: OurLocals = {
|
our_locals: OurLocals = {
|
||||||
a_nam: FunctionParam(a_nam, a_typ)
|
x.name: x
|
||||||
for a_nam, a_typ in zip(function.arg_names, func_arg_types, strict=True)
|
for x in function.posonlyargs
|
||||||
}
|
}
|
||||||
|
|
||||||
for stmt in node.body:
|
for stmt in node.body:
|
||||||
@ -333,15 +305,14 @@ class OurVisitor[G]:
|
|||||||
self.visit_Module_FunctionDef_stmt(module, function, our_locals, stmt)
|
self.visit_Module_FunctionDef_stmt(module, function, our_locals, stmt)
|
||||||
)
|
)
|
||||||
|
|
||||||
def visit_Module_FunctionDef_stmt(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.stmt) -> Statement:
|
def visit_Module_FunctionDef_stmt(self, module: Module, function: Function, our_locals: OurLocals, node: ast.stmt) -> Statement:
|
||||||
if isinstance(node, ast.Return):
|
if isinstance(node, ast.Return):
|
||||||
if node.value is None:
|
if node.value is None:
|
||||||
# TODO: Implement methods without return values
|
# TODO: Implement methods without return values
|
||||||
_raise_static_error(node, 'Return must have an argument')
|
_raise_static_error(node, 'Return must have an argument')
|
||||||
|
|
||||||
return StatementReturn(
|
return StatementReturn(
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.value),
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.value)
|
||||||
srf(module, node),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(node, ast.If):
|
if isinstance(node, ast.If):
|
||||||
@ -362,14 +333,12 @@ class OurVisitor[G]:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
if isinstance(node, ast.Pass):
|
if isinstance(node, ast.Pass):
|
||||||
return StatementPass(srf(module, node))
|
return StatementPass()
|
||||||
|
|
||||||
raise NotImplementedError(f'{node} as stmt in FunctionDef')
|
raise NotImplementedError(f'{node} as stmt in FunctionDef')
|
||||||
|
|
||||||
def visit_Module_FunctionDef_expr(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
|
def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
|
||||||
if isinstance(node, ast.BinOp):
|
if isinstance(node, ast.BinOp):
|
||||||
operator: str
|
|
||||||
|
|
||||||
if isinstance(node.op, ast.Add):
|
if isinstance(node.op, ast.Add):
|
||||||
operator = '+'
|
operator = '+'
|
||||||
elif isinstance(node.op, ast.Sub):
|
elif isinstance(node.op, ast.Sub):
|
||||||
@ -378,10 +347,6 @@ class OurVisitor[G]:
|
|||||||
operator = '*'
|
operator = '*'
|
||||||
elif isinstance(node.op, ast.Div):
|
elif isinstance(node.op, ast.Div):
|
||||||
operator = '/'
|
operator = '/'
|
||||||
elif isinstance(node.op, ast.FloorDiv):
|
|
||||||
operator = '//'
|
|
||||||
elif isinstance(node.op, ast.Mod):
|
|
||||||
operator = '%'
|
|
||||||
elif isinstance(node.op, ast.LShift):
|
elif isinstance(node.op, ast.LShift):
|
||||||
operator = '<<'
|
operator = '<<'
|
||||||
elif isinstance(node.op, ast.RShift):
|
elif isinstance(node.op, ast.RShift):
|
||||||
@ -395,14 +360,26 @@ class OurVisitor[G]:
|
|||||||
else:
|
else:
|
||||||
raise NotImplementedError(f'Operator {node.op}')
|
raise NotImplementedError(f'Operator {node.op}')
|
||||||
|
|
||||||
if operator not in module.operators:
|
# Assume the type doesn't change when descending into a binary operator
|
||||||
raise NotImplementedError(f'Operator {operator}')
|
# e.g. you can do `"hello" * 3` with the code below (yet)
|
||||||
|
|
||||||
return BinaryOp(
|
return BinaryOp(
|
||||||
BuiltinFunction(operator, module.operators[operator]),
|
operator,
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.right),
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.right),
|
||||||
srf(module, node),
|
)
|
||||||
|
|
||||||
|
if isinstance(node, ast.UnaryOp):
|
||||||
|
if isinstance(node.op, ast.UAdd):
|
||||||
|
operator = '+'
|
||||||
|
elif isinstance(node.op, ast.USub):
|
||||||
|
operator = '-'
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(f'Operator {node.op}')
|
||||||
|
|
||||||
|
return UnaryOp(
|
||||||
|
operator,
|
||||||
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.operand),
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(node, ast.Compare):
|
if isinstance(node, ast.Compare):
|
||||||
@ -411,27 +388,20 @@ class OurVisitor[G]:
|
|||||||
|
|
||||||
if isinstance(node.ops[0], ast.Gt):
|
if isinstance(node.ops[0], ast.Gt):
|
||||||
operator = '>'
|
operator = '>'
|
||||||
elif isinstance(node.ops[0], ast.GtE):
|
|
||||||
operator = '>='
|
|
||||||
elif isinstance(node.ops[0], ast.Eq):
|
elif isinstance(node.ops[0], ast.Eq):
|
||||||
operator = '=='
|
operator = '=='
|
||||||
elif isinstance(node.ops[0], ast.NotEq):
|
|
||||||
operator = '!='
|
|
||||||
elif isinstance(node.ops[0], ast.Lt):
|
elif isinstance(node.ops[0], ast.Lt):
|
||||||
operator = '<'
|
operator = '<'
|
||||||
elif isinstance(node.ops[0], ast.LtE):
|
|
||||||
operator = '<='
|
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(f'Operator {node.ops}')
|
raise NotImplementedError(f'Operator {node.ops}')
|
||||||
|
|
||||||
if operator not in module.operators:
|
# Assume the type doesn't change when descending into a binary operator
|
||||||
raise NotImplementedError(f'Operator {operator}')
|
# e.g. you can do `"hello" * 3` with the code below (yet)
|
||||||
|
|
||||||
return BinaryOp(
|
return BinaryOp(
|
||||||
BuiltinFunction(operator, module.operators[operator]),
|
operator,
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.comparators[0]),
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.comparators[0]),
|
||||||
srf(module, node),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(node, ast.Call):
|
if isinstance(node, ast.Call):
|
||||||
@ -458,33 +428,40 @@ class OurVisitor[G]:
|
|||||||
|
|
||||||
if node.id in our_locals:
|
if node.id in our_locals:
|
||||||
param = our_locals[node.id]
|
param = our_locals[node.id]
|
||||||
return VariableReference(param, srf(module, node))
|
return VariableReference(param)
|
||||||
|
|
||||||
if node.id in module.constant_defs:
|
if node.id in module.constant_defs:
|
||||||
cdef = module.constant_defs[node.id]
|
cdef = module.constant_defs[node.id]
|
||||||
return VariableReference(cdef, srf(module, node))
|
return VariableReference(cdef)
|
||||||
|
|
||||||
if node.id in module.functions:
|
|
||||||
fun = module.functions[node.id]
|
|
||||||
return FunctionReference(fun, srf(module, node))
|
|
||||||
|
|
||||||
_raise_static_error(node, f'Undefined variable {node.id}')
|
_raise_static_error(node, f'Undefined variable {node.id}')
|
||||||
|
|
||||||
if isinstance(node, ast.Tuple):
|
if isinstance(node, ast.Tuple):
|
||||||
arguments = [
|
raise NotImplementedError('TODO: Broken after new type system')
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_node)
|
|
||||||
for arg_node in node.elts
|
|
||||||
if isinstance(arg_node, (ast.Constant, ast.Tuple, ast.Call, ))
|
|
||||||
]
|
|
||||||
|
|
||||||
if len(arguments) != len(node.elts):
|
# if not isinstance(node.ctx, ast.Load):
|
||||||
raise NotImplementedError('Non-constant tuple members')
|
# _raise_static_error(node, 'Must be load context')
|
||||||
|
#
|
||||||
return TupleInstantiation(arguments, srf(module, node))
|
# if isinstance(exp_type, TypeTuple):
|
||||||
|
# if len(exp_type.members) != len(node.elts):
|
||||||
|
# _raise_static_error(node, f'Expression is expecting a tuple of size {len(exp_type.members)}, but {len(node.elts)} are given')
|
||||||
|
#
|
||||||
|
# tuple_constructor = TupleConstructor(exp_type)
|
||||||
|
#
|
||||||
|
# func = module.functions[tuple_constructor.name]
|
||||||
|
#
|
||||||
|
# result = FunctionCall(func)
|
||||||
|
# result.arguments = [
|
||||||
|
# self.visit_Module_FunctionDef_expr(module, function, our_locals, mem.type, arg_node)
|
||||||
|
# for arg_node, mem in zip(node.elts, exp_type.members)
|
||||||
|
# ]
|
||||||
|
# return result
|
||||||
|
#
|
||||||
|
# _raise_static_error(node, f'Expression is expecting a {codestyle.type_(exp_type)}, not a tuple')
|
||||||
|
|
||||||
raise NotImplementedError(f'{node} as expr in FunctionDef')
|
raise NotImplementedError(f'{node} as expr in FunctionDef')
|
||||||
|
|
||||||
def visit_Module_FunctionDef_Call(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.Call) -> Union[FunctionCall]:
|
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Call) -> Union[Fold, FunctionCall, UnaryOp]:
|
||||||
if node.keywords:
|
if node.keywords:
|
||||||
_raise_static_error(node, 'Keyword calling not supported') # Yet?
|
_raise_static_error(node, 'Keyword calling not supported') # Yet?
|
||||||
|
|
||||||
@ -493,47 +470,118 @@ class OurVisitor[G]:
|
|||||||
if not isinstance(node.func.ctx, ast.Load):
|
if not isinstance(node.func.ctx, ast.Load):
|
||||||
_raise_static_error(node, 'Must be load context')
|
_raise_static_error(node, 'Must be load context')
|
||||||
|
|
||||||
func: Union[Function, FunctionParam]
|
if node.func.id in module.struct_definitions:
|
||||||
|
struct_definition = module.struct_definitions[node.func.id]
|
||||||
|
struct_constructor = StructConstructor(struct_definition.struct_type3)
|
||||||
|
|
||||||
if node.func.id in module.methods:
|
# FIXME: Defer struct de-allocation
|
||||||
func = BuiltinFunction(node.func.id, module.methods[node.func.id])
|
|
||||||
elif node.func.id in our_locals:
|
func = module.functions[struct_constructor.name]
|
||||||
func = our_locals[node.func.id]
|
elif node.func.id in WEBASSEMBLY_BUILTIN_FLOAT_OPS:
|
||||||
|
if 1 != len(node.args):
|
||||||
|
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
|
||||||
|
|
||||||
|
return UnaryOp(
|
||||||
|
'sqrt',
|
||||||
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]),
|
||||||
|
)
|
||||||
|
elif node.func.id == 'u32':
|
||||||
|
if 1 != len(node.args):
|
||||||
|
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
|
||||||
|
|
||||||
|
# FIXME: This is a stub, proper casting is todo
|
||||||
|
|
||||||
|
return UnaryOp(
|
||||||
|
'cast',
|
||||||
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]),
|
||||||
|
)
|
||||||
|
elif node.func.id == 'len':
|
||||||
|
if 1 != len(node.args):
|
||||||
|
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
|
||||||
|
|
||||||
|
return UnaryOp(
|
||||||
|
'len',
|
||||||
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]),
|
||||||
|
)
|
||||||
|
elif node.func.id == 'foldl':
|
||||||
|
# TODO: This should a much more generic function!
|
||||||
|
# For development purposes, we're assuming you're doing a foldl(Callable[[u8, u8], u8], u8, bytes)
|
||||||
|
# In the future, we should probably infer the type of the second argument,
|
||||||
|
# and use it as expected types for the other u8s and the Iterable[u8] (i.e. bytes)
|
||||||
|
|
||||||
|
if 3 != len(node.args):
|
||||||
|
_raise_static_error(node, f'Function {node.func.id} requires 3 arguments but {len(node.args)} are given')
|
||||||
|
|
||||||
|
# TODO: This is not generic
|
||||||
|
subnode = node.args[0]
|
||||||
|
if not isinstance(subnode, ast.Name):
|
||||||
|
raise NotImplementedError(f'Calling methods that are not a name {subnode}')
|
||||||
|
if not isinstance(subnode.ctx, ast.Load):
|
||||||
|
_raise_static_error(subnode, 'Must be load context')
|
||||||
|
if subnode.id not in module.functions:
|
||||||
|
_raise_static_error(subnode, 'Reference to undefined function')
|
||||||
|
func = module.functions[subnode.id]
|
||||||
|
if 2 != len(func.posonlyargs):
|
||||||
|
_raise_static_error(node, f'Function {node.func.id} requires a function with 2 arguments but a function with {len(func.posonlyargs)} args is given')
|
||||||
|
|
||||||
|
raise NotImplementedError('TODO: Broken after new type system')
|
||||||
|
|
||||||
|
return Fold(
|
||||||
|
Fold.Direction.LEFT,
|
||||||
|
func,
|
||||||
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[1]),
|
||||||
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[2]),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if node.func.id not in module.functions:
|
if node.func.id not in module.functions:
|
||||||
_raise_static_error(node, 'Call to undefined function')
|
_raise_static_error(node, 'Call to undefined function')
|
||||||
|
|
||||||
func = module.functions[node.func.id]
|
func = module.functions[node.func.id]
|
||||||
|
|
||||||
result = FunctionCall(func, sourceref=srf(module, node))
|
if len(func.posonlyargs) != len(node.args):
|
||||||
|
_raise_static_error(node, f'Function {node.func.id} requires {len(func.posonlyargs)} arguments but {len(node.args)} are given')
|
||||||
|
|
||||||
|
result = FunctionCall(func)
|
||||||
result.arguments.extend(
|
result.arguments.extend(
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_expr)
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_expr)
|
||||||
for arg_expr in node.args
|
for arg_expr, param in zip(node.args, func.posonlyargs)
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def visit_Module_FunctionDef_Attribute(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.Attribute) -> Expression:
|
def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Attribute) -> Expression:
|
||||||
|
del module
|
||||||
|
del function
|
||||||
|
|
||||||
if not isinstance(node.value, ast.Name):
|
if not isinstance(node.value, ast.Name):
|
||||||
_raise_static_error(node, 'Must reference a name')
|
_raise_static_error(node, 'Must reference a name')
|
||||||
|
|
||||||
if not isinstance(node.ctx, ast.Load):
|
if not isinstance(node.ctx, ast.Load):
|
||||||
_raise_static_error(node, 'Must be load context')
|
_raise_static_error(node, 'Must be load context')
|
||||||
|
|
||||||
varref = self.visit_Module_FunctionDef_expr(module, function, our_locals, node.value)
|
if not node.value.id in our_locals:
|
||||||
if not isinstance(varref, VariableReference):
|
_raise_static_error(node, f'Undefined variable {node.value.id}')
|
||||||
_raise_static_error(node.value, 'Must refer to variable')
|
|
||||||
|
param = our_locals[node.value.id]
|
||||||
|
|
||||||
|
node_typ = param.type3
|
||||||
|
if not isinstance(node_typ, type3types.StructType3):
|
||||||
|
_raise_static_error(node, f'Cannot take attribute of non-struct {node.value.id}')
|
||||||
|
|
||||||
|
member = node_typ.members.get(node.attr)
|
||||||
|
if member is None:
|
||||||
|
_raise_static_error(node, f'{node_typ.name} has no attribute {node.attr}')
|
||||||
|
|
||||||
return AccessStructMember(
|
return AccessStructMember(
|
||||||
varref,
|
VariableReference(param),
|
||||||
|
node_typ,
|
||||||
node.attr,
|
node.attr,
|
||||||
srf(module, node),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def visit_Module_FunctionDef_Subscript(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.Subscript) -> Expression:
|
def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Subscript) -> Expression:
|
||||||
if not isinstance(node.value, ast.Name):
|
if not isinstance(node.value, ast.Name):
|
||||||
_raise_static_error(node, 'Must reference a name')
|
_raise_static_error(node, 'Must reference a name')
|
||||||
|
|
||||||
if isinstance(node.slice, ast.Slice):
|
if not isinstance(node.slice, ast.Index):
|
||||||
_raise_static_error(node, 'Must subscript using an index')
|
_raise_static_error(node, 'Must subscript using an index')
|
||||||
|
|
||||||
if not isinstance(node.ctx, ast.Load):
|
if not isinstance(node.ctx, ast.Load):
|
||||||
@ -542,150 +590,141 @@ class OurVisitor[G]:
|
|||||||
varref: VariableReference
|
varref: VariableReference
|
||||||
if node.value.id in our_locals:
|
if node.value.id in our_locals:
|
||||||
param = our_locals[node.value.id]
|
param = our_locals[node.value.id]
|
||||||
varref = VariableReference(param, srf(module, node))
|
varref = VariableReference(param)
|
||||||
elif node.value.id in module.constant_defs:
|
elif node.value.id in module.constant_defs:
|
||||||
constant_def = module.constant_defs[node.value.id]
|
constant_def = module.constant_defs[node.value.id]
|
||||||
varref = VariableReference(constant_def, srf(module, node))
|
varref = VariableReference(constant_def)
|
||||||
else:
|
else:
|
||||||
_raise_static_error(node, f'Undefined variable {node.value.id}')
|
_raise_static_error(node, f'Undefined variable {node.value.id}')
|
||||||
|
|
||||||
slice_expr = self.visit_Module_FunctionDef_expr(
|
slice_expr = self.visit_Module_FunctionDef_expr(
|
||||||
module, function, our_locals, node.slice,
|
module, function, our_locals, node.slice.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Subscript(varref, slice_expr, srf(module, node))
|
return Subscript(varref, slice_expr)
|
||||||
|
|
||||||
def visit_Module_Constant(self, module: Module[G], node: Union[ast.Constant, ast.Tuple, ast.Call]) -> Union[ConstantPrimitive, ConstantBytes, ConstantTuple, ConstantStruct]:
|
# if isinstance(node_typ, TypeBytes):
|
||||||
if isinstance(node, ast.Tuple):
|
# if isinstance(varref, ModuleConstantReference):
|
||||||
tuple_data = [
|
# raise NotImplementedError(f'{node} from module constant')
|
||||||
self.visit_Module_Constant(module, arg_node)
|
#
|
||||||
for arg_node in node.elts
|
# return AccessBytesIndex(
|
||||||
if isinstance(arg_node, (ast.Constant, ast.Tuple, ast.Call, ))
|
# varref,
|
||||||
]
|
# slice_expr,
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# if isinstance(node_typ, TypeTuple):
|
||||||
|
# if not isinstance(slice_expr, ConstantPrimitive):
|
||||||
|
# _raise_static_error(node, 'Must subscript using a constant index')
|
||||||
|
#
|
||||||
|
# idx = slice_expr.value
|
||||||
|
#
|
||||||
|
# if not isinstance(idx, int):
|
||||||
|
# _raise_static_error(node, 'Must subscript using a constant integer index')
|
||||||
|
#
|
||||||
|
# if not (0 <= idx < len(node_typ.members)):
|
||||||
|
# _raise_static_error(node, f'Index {idx} out of bounds for tuple {node.value.id}')
|
||||||
|
#
|
||||||
|
# tuple_member = node_typ.members[idx]
|
||||||
|
#
|
||||||
|
# if isinstance(varref, ModuleConstantReference):
|
||||||
|
# raise NotImplementedError(f'{node} from module constant')
|
||||||
|
#
|
||||||
|
# return AccessTupleMember(
|
||||||
|
# varref,
|
||||||
|
# tuple_member,
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# if isinstance(node_typ, TypeStaticArray):
|
||||||
|
# if not isinstance(slice_expr, ConstantPrimitive):
|
||||||
|
# return AccessStaticArrayMember(
|
||||||
|
# varref,
|
||||||
|
# node_typ,
|
||||||
|
# slice_expr,
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# idx = slice_expr.value
|
||||||
|
#
|
||||||
|
# if not isinstance(idx, int):
|
||||||
|
# _raise_static_error(node, 'Must subscript using an integer index')
|
||||||
|
#
|
||||||
|
# if not (0 <= idx < len(node_typ.members)):
|
||||||
|
# _raise_static_error(node, f'Index {idx} out of bounds for static array {node.value.id}')
|
||||||
|
#
|
||||||
|
# static_array_member = node_typ.members[idx]
|
||||||
|
#
|
||||||
|
# return AccessStaticArrayMember(
|
||||||
|
# varref,
|
||||||
|
# node_typ,
|
||||||
|
# static_array_member,
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# _raise_static_error(node, f'Cannot take index of {node_typ} {node.value.id}')
|
||||||
|
|
||||||
if len(node.elts) != len(tuple_data):
|
def visit_Module_Constant(self, module: Module, node: ast.Constant) -> ConstantPrimitive:
|
||||||
_raise_static_error(node, 'Tuple arguments must be constants')
|
del module
|
||||||
|
|
||||||
# Allocate the data
|
|
||||||
data_block = ModuleDataBlock(tuple_data)
|
|
||||||
module.data.blocks.append(data_block)
|
|
||||||
|
|
||||||
return ConstantTuple(tuple_data, data_block, srf(module, node))
|
|
||||||
|
|
||||||
if isinstance(node, ast.Call):
|
|
||||||
# Struct constant
|
|
||||||
# Stored in memory like a tuple, so much of the code is the same
|
|
||||||
|
|
||||||
if not isinstance(node.func, ast.Name):
|
|
||||||
_raise_static_error(node.func, 'Must be name')
|
|
||||||
if not isinstance(node.func.ctx, ast.Load):
|
|
||||||
_raise_static_error(node.func, 'Must be load context')
|
|
||||||
|
|
||||||
struct_def = module.struct_definitions.get(node.func.id)
|
|
||||||
if struct_def is None:
|
|
||||||
_raise_static_error(node.func, 'Undefined struct')
|
|
||||||
|
|
||||||
if node.keywords:
|
|
||||||
_raise_static_error(node.func, 'Cannot use keywords')
|
|
||||||
|
|
||||||
struct_data = [
|
|
||||||
self.visit_Module_Constant(module, arg_node)
|
|
||||||
for arg_node in node.args
|
|
||||||
if isinstance(arg_node, (ast.Constant, ast.Tuple, ast.Call, ))
|
|
||||||
]
|
|
||||||
if len(node.args) != len(struct_data):
|
|
||||||
_raise_static_error(node, 'Struct arguments must be constants')
|
|
||||||
|
|
||||||
data_block = ModuleDataBlock(struct_data)
|
|
||||||
module.data.blocks.append(data_block)
|
|
||||||
return ConstantStruct(struct_def.struct_type5, struct_data, data_block, srf(module, node))
|
|
||||||
|
|
||||||
_not_implemented(node.kind is None, 'Constant.kind')
|
_not_implemented(node.kind is None, 'Constant.kind')
|
||||||
|
|
||||||
if isinstance(node.value, (int, float, )):
|
if isinstance(node.value, (int, float, )):
|
||||||
return ConstantPrimitive(node.value, srf(module, node))
|
return ConstantPrimitive(node.value)
|
||||||
|
|
||||||
if isinstance(node.value, bytes):
|
|
||||||
data_block = ModuleDataBlock([])
|
|
||||||
module.data.blocks.append(data_block)
|
|
||||||
|
|
||||||
result = ConstantBytes(node.value, data_block, srf(module, node))
|
|
||||||
data_block.data.append(result)
|
|
||||||
return result
|
|
||||||
|
|
||||||
raise NotImplementedError(f'{node.value} as constant')
|
raise NotImplementedError(f'{node.value} as constant')
|
||||||
|
|
||||||
def visit_type5(self, module: Module[G], node: ast.expr) -> type5typeexpr.TypeExpr:
|
def visit_type(self, module: Module, node: ast.expr) -> type3types.Type3:
|
||||||
if isinstance(node, ast.Constant):
|
if isinstance(node, ast.Constant):
|
||||||
if node.value is None:
|
if node.value is None:
|
||||||
return module.build.none_type5
|
return type3types.none
|
||||||
|
|
||||||
_raise_static_error(node, f'Unrecognized type {node.value!r}')
|
_raise_static_error(node, f'Unrecognized type {node.value}')
|
||||||
|
|
||||||
if isinstance(node, ast.Name):
|
if isinstance(node, ast.Name):
|
||||||
if not isinstance(node.ctx, ast.Load):
|
if not isinstance(node.ctx, ast.Load):
|
||||||
_raise_static_error(node, 'Must be load context')
|
_raise_static_error(node, 'Must be load context')
|
||||||
|
|
||||||
if node.id in module.types:
|
if node.id in type3types.LOOKUP_TABLE:
|
||||||
return module.types[node.id]
|
return type3types.LOOKUP_TABLE[node.id]
|
||||||
|
|
||||||
|
if node.id in module.struct_definitions:
|
||||||
|
return module.struct_definitions[node.id].struct_type3
|
||||||
|
|
||||||
_raise_static_error(node, f'Unrecognized type {node.id}')
|
_raise_static_error(node, f'Unrecognized type {node.id}')
|
||||||
|
|
||||||
if isinstance(node, ast.Subscript):
|
if isinstance(node, ast.Subscript):
|
||||||
if isinstance(node.value, ast.Name) and node.value.id == 'Callable':
|
if not isinstance(node.value, ast.Name):
|
||||||
func_arg_types: list[ast.expr]
|
_raise_static_error(node, 'Must be name')
|
||||||
|
if not isinstance(node.slice, ast.Index):
|
||||||
if isinstance(node.slice, ast.Name):
|
|
||||||
func_arg_types = [node.slice]
|
|
||||||
elif isinstance(node.slice, ast.Tuple):
|
|
||||||
func_arg_types = node.slice.elts
|
|
||||||
else:
|
|
||||||
_raise_static_error(node, 'Must subscript using a list of types')
|
|
||||||
|
|
||||||
return module.build.type5_make_function([
|
|
||||||
self.visit_type5(module, e)
|
|
||||||
for e in func_arg_types
|
|
||||||
])
|
|
||||||
|
|
||||||
if isinstance(node.slice, ast.Slice):
|
|
||||||
_raise_static_error(node, 'Must subscript using an index')
|
_raise_static_error(node, 'Must subscript using an index')
|
||||||
|
if not isinstance(node.slice.value, ast.Constant):
|
||||||
if not isinstance(node.slice, ast.Constant):
|
|
||||||
_raise_static_error(node, 'Must subscript using a constant index')
|
_raise_static_error(node, 'Must subscript using a constant index')
|
||||||
|
if not isinstance(node.slice.value.value, int):
|
||||||
if node.slice.value is Ellipsis:
|
|
||||||
return module.build.type5_make_dynamic_array(
|
|
||||||
self.visit_type5(module, node.value),
|
|
||||||
)
|
|
||||||
|
|
||||||
if not isinstance(node.slice.value, int):
|
|
||||||
_raise_static_error(node, 'Must subscript using a constant integer index')
|
_raise_static_error(node, 'Must subscript using a constant integer index')
|
||||||
if not isinstance(node.ctx, ast.Load):
|
if not isinstance(node.ctx, ast.Load):
|
||||||
_raise_static_error(node, 'Must be load context')
|
_raise_static_error(node, 'Must be load context')
|
||||||
|
|
||||||
return module.build.type5_make_static_array(
|
if node.value.id not in type3types.LOOKUP_TABLE: # FIXME: Tuple of tuples?
|
||||||
node.slice.value,
|
_raise_static_error(node, f'Unrecognized type {node.value.id}')
|
||||||
self.visit_type5(module, node.value),
|
|
||||||
|
return type3types.AppliedType3(
|
||||||
|
type3types.static_array,
|
||||||
|
[self.visit_type(module, node.value)],
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(node, ast.Tuple):
|
if isinstance(node, ast.Tuple):
|
||||||
if not isinstance(node.ctx, ast.Load):
|
if not isinstance(node.ctx, ast.Load):
|
||||||
_raise_static_error(node, 'Must be load context')
|
_raise_static_error(node, 'Must be load context')
|
||||||
|
|
||||||
return module.build.type5_make_tuple(
|
return type3types.AppliedType3(
|
||||||
[self.visit_type5(module, elt) for elt in node.elts],
|
type3types.tuple,
|
||||||
|
(self.visit_type(module, elt) for elt in node.elts)
|
||||||
)
|
)
|
||||||
|
|
||||||
raise NotImplementedError(node)
|
raise NotImplementedError(f'{node} as type')
|
||||||
|
|
||||||
def _not_implemented(check: Any, msg: str) -> None:
|
def _not_implemented(check: Any, msg: str) -> None:
|
||||||
if not check:
|
if not check:
|
||||||
raise NotImplementedError(msg)
|
raise NotImplementedError(msg)
|
||||||
|
|
||||||
def _raise_static_error(node: Union[ast.stmt, ast.expr], msg: str) -> NoReturn:
|
def _raise_static_error(node: Union[ast.mod, ast.stmt, ast.expr], msg: str) -> NoReturn:
|
||||||
raise StaticError(
|
raise StaticError(
|
||||||
f'Static error on line {node.lineno}: {msg}'
|
f'Static error on line {node.lineno}: {msg}'
|
||||||
)
|
)
|
||||||
|
|
||||||
def srf(mod: Module[Any], node: ast.stmt | ast.expr) -> SourceRef:
|
|
||||||
return SourceRef(mod.filename, node.lineno, node.col_offset)
|
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
stdlib: Memory allocation
|
stdlib: Memory allocation
|
||||||
"""
|
"""
|
||||||
from phasm.wasmgenerator import Generator, func_wrapper
|
from phasm.wasmgenerator import Generator, VarType_i32 as i32, func_wrapper
|
||||||
from phasm.wasmgenerator import VarType_i32 as i32
|
|
||||||
|
|
||||||
IDENTIFIER = 0xA1C0
|
IDENTIFIER = 0xA1C0
|
||||||
|
|
||||||
@ -15,7 +14,7 @@ UNALLOC_PTR = ADR_UNALLOC_PTR + 4
|
|||||||
|
|
||||||
# For memory initialization see phasm.compiler.module_data
|
# For memory initialization see phasm.compiler.module_data
|
||||||
|
|
||||||
@func_wrapper()
|
@func_wrapper(exported=False)
|
||||||
def __find_free_block__(g: Generator, alloc_size: i32) -> i32:
|
def __find_free_block__(g: Generator, alloc_size: i32) -> i32:
|
||||||
# Find out if we've freed any blocks at all so far
|
# Find out if we've freed any blocks at all so far
|
||||||
g.i32.const(ADR_FREE_BLOCK_PTR)
|
g.i32.const(ADR_FREE_BLOCK_PTR)
|
||||||
@ -32,7 +31,7 @@ def __find_free_block__(g: Generator, alloc_size: i32) -> i32:
|
|||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
return i32('return') # To satisfy mypy
|
||||||
|
|
||||||
@func_wrapper(exported=True)
|
@func_wrapper()
|
||||||
def __alloc__(g: Generator, alloc_size: i32) -> i32:
|
def __alloc__(g: Generator, alloc_size: i32) -> i32:
|
||||||
result = i32('result')
|
result = i32('result')
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
stdlib: Standard types that are not wasm primitives
|
stdlib: Standard types that are not wasm primitives
|
||||||
"""
|
"""
|
||||||
|
from phasm.wasmgenerator import Generator, VarType_i32 as i32, func_wrapper
|
||||||
|
|
||||||
from phasm.stdlib import alloc
|
from phasm.stdlib import alloc
|
||||||
from phasm.wasmgenerator import Generator, func_wrapper
|
|
||||||
from phasm.wasmgenerator import VarType_i32 as i32
|
|
||||||
from phasm.wasmgenerator import VarType_i64 as i64
|
|
||||||
|
|
||||||
|
@func_wrapper()
|
||||||
@func_wrapper(exported=True)
|
|
||||||
def __alloc_bytes__(g: Generator, length: i32) -> i32:
|
def __alloc_bytes__(g: Generator, length: i32) -> i32:
|
||||||
"""
|
"""
|
||||||
Allocates room for a bytes instance, but does not write
|
Allocates room for a bytes instance, but does not write
|
||||||
@ -34,400 +32,35 @@ def __alloc_bytes__(g: Generator, length: i32) -> i32:
|
|||||||
return i32('return') # To satisfy mypy
|
return i32('return') # To satisfy mypy
|
||||||
|
|
||||||
@func_wrapper()
|
@func_wrapper()
|
||||||
def __u32_min__(g: Generator, x: i32, y: i32) -> i32:
|
def __subscript_bytes__(g: Generator, adr: i32, ofs: i32) -> i32:
|
||||||
g.local.get(x)
|
"""
|
||||||
g.local.get(y)
|
Returns an index from a bytes value
|
||||||
|
|
||||||
|
If ofs is more than the length of the bytes, this
|
||||||
|
function returns 0, following the 'no undefined behaviour'
|
||||||
|
philosophy.
|
||||||
|
|
||||||
|
adr i32 The pointer for the allocated bytes
|
||||||
|
ofs i32 The offset within the allocated bytes
|
||||||
|
"""
|
||||||
|
g.local.get(ofs)
|
||||||
|
g.local.get(adr)
|
||||||
|
g.i32.load()
|
||||||
g.i32.lt_u()
|
g.i32.lt_u()
|
||||||
|
|
||||||
with g.if_():
|
with g.if_():
|
||||||
g.local.get(x)
|
# The offset is less than the length
|
||||||
|
|
||||||
|
g.local.get(adr)
|
||||||
|
g.i32.const(4) # Bytes header
|
||||||
|
g.i32.add()
|
||||||
|
g.local.get(ofs)
|
||||||
|
g.i32.add()
|
||||||
|
g.i32.load8_u()
|
||||||
g.return_()
|
g.return_()
|
||||||
|
|
||||||
g.local.get(y)
|
# The offset is outside the allocated bytes
|
||||||
g.return_()
|
g.i32.const(0)
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __u64_min__(g: Generator, x: i64, y: i64) -> i64:
|
|
||||||
g.local.get(x)
|
|
||||||
g.local.get(y)
|
|
||||||
g.i64.lt_u()
|
|
||||||
|
|
||||||
with g.if_():
|
|
||||||
g.local.get(x)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
g.local.get(y)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
return i64('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __i32_min__(g: Generator, x: i32, y: i32) -> i32:
|
|
||||||
g.local.get(x)
|
|
||||||
g.local.get(y)
|
|
||||||
g.i32.lt_s()
|
|
||||||
|
|
||||||
with g.if_():
|
|
||||||
g.local.get(x)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
g.local.get(y)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __i64_min__(g: Generator, x: i64, y: i64) -> i64:
|
|
||||||
g.local.get(x)
|
|
||||||
g.local.get(y)
|
|
||||||
g.i64.lt_s()
|
|
||||||
|
|
||||||
with g.if_():
|
|
||||||
g.local.get(x)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
g.local.get(y)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
return i64('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __u32_max__(g: Generator, x: i32, y: i32) -> i32:
|
|
||||||
g.local.get(x)
|
|
||||||
g.local.get(y)
|
|
||||||
g.i32.gt_u()
|
|
||||||
|
|
||||||
with g.if_():
|
|
||||||
g.local.get(x)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
g.local.get(y)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __u64_max__(g: Generator, x: i64, y: i64) -> i64:
|
|
||||||
g.local.get(x)
|
|
||||||
g.local.get(y)
|
|
||||||
g.i64.gt_u()
|
|
||||||
|
|
||||||
with g.if_():
|
|
||||||
g.local.get(x)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
g.local.get(y)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
return i64('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __i32_max__(g: Generator, x: i32, y: i32) -> i32:
|
|
||||||
g.local.get(x)
|
|
||||||
g.local.get(y)
|
|
||||||
g.i32.gt_s()
|
|
||||||
|
|
||||||
with g.if_():
|
|
||||||
g.local.get(x)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
g.local.get(y)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __i64_max__(g: Generator, x: i64, y: i64) -> i64:
|
|
||||||
g.local.get(x)
|
|
||||||
g.local.get(y)
|
|
||||||
g.i64.gt_s()
|
|
||||||
|
|
||||||
with g.if_():
|
|
||||||
g.local.get(x)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
g.local.get(y)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
return i64('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __i32_abs__(g: Generator, x: i32) -> i32:
|
|
||||||
# https://stackoverflow.com/a/14194764
|
|
||||||
y = i32('y')
|
|
||||||
# z = i32('z')
|
|
||||||
|
|
||||||
# y = x >> 31
|
|
||||||
g.local.get(x)
|
|
||||||
g.i32.const(31)
|
|
||||||
g.i32.shr_s() # Must be arithmetic shift
|
|
||||||
g.local.set(y)
|
|
||||||
|
|
||||||
# abs(x) = (x XOR y) - y
|
|
||||||
|
|
||||||
# (x XOR y)
|
|
||||||
g.local.get(x)
|
|
||||||
g.local.get(y)
|
|
||||||
g.i32.xor()
|
|
||||||
|
|
||||||
# - y
|
|
||||||
g.local.get(y)
|
|
||||||
g.i32.sub()
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __i64_abs__(g: Generator, x: i64) -> i64:
|
|
||||||
# https://stackoverflow.com/a/14194764
|
|
||||||
y = i64('y')
|
|
||||||
# z = i64('z')
|
|
||||||
|
|
||||||
# y = x >> 31
|
|
||||||
g.local.get(x)
|
|
||||||
g.i64.const(31)
|
|
||||||
g.i64.shr_s() # Must be arithmetic shift
|
|
||||||
g.local.set(y)
|
|
||||||
|
|
||||||
# abs(x) = (x XOR y) - y
|
|
||||||
|
|
||||||
# (x XOR y)
|
|
||||||
g.local.get(x)
|
|
||||||
g.local.get(y)
|
|
||||||
g.i64.xor()
|
|
||||||
|
|
||||||
# - y
|
|
||||||
g.local.get(y)
|
|
||||||
g.i64.sub()
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
return i64('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __u32_pow2__(g: Generator, x: i32) -> i32:
|
|
||||||
# 2^0 == 1
|
|
||||||
g.local.get(x)
|
|
||||||
g.i32.eqz()
|
|
||||||
with g.if_():
|
|
||||||
g.i32.const(1)
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
# 2 ^ x == 2 << (x - 1)
|
|
||||||
# (when x > 1)
|
|
||||||
g.i32.const(2)
|
|
||||||
g.local.get(x)
|
|
||||||
g.i32.const(1)
|
|
||||||
g.i32.sub()
|
|
||||||
g.i32.shl()
|
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __u8_rotl__(g: Generator, x: i32, r: i32) -> i32:
|
|
||||||
s = i32('s') # The shifted part we need to overlay
|
|
||||||
|
|
||||||
# Handle cases where we need to shift more than 8 bits
|
|
||||||
g.local.get(r)
|
|
||||||
g.i32.const(8)
|
|
||||||
g.i32.rem_u()
|
|
||||||
g.local.set(r)
|
|
||||||
|
|
||||||
# Now do the rotation
|
|
||||||
|
|
||||||
g.local.get(x)
|
|
||||||
|
|
||||||
# 0000 0000 1100 0011
|
|
||||||
|
|
||||||
g.local.get(r)
|
|
||||||
|
|
||||||
# 0000 0000 1100 0011, 3
|
|
||||||
|
|
||||||
g.i32.shl()
|
|
||||||
|
|
||||||
# 0000 0110 0001 1000
|
|
||||||
|
|
||||||
g.local.tee(s)
|
|
||||||
|
|
||||||
# 0000 0110 0001 1000
|
|
||||||
|
|
||||||
g.i32.const(255)
|
|
||||||
|
|
||||||
# 0000 0110 0001 1000, 0000 0000 1111 1111
|
|
||||||
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
# 0000 0000 0001 1000
|
|
||||||
|
|
||||||
g.local.get(s)
|
|
||||||
|
|
||||||
# 0000 0000 0001 1000, 0000 0110 0001 1000
|
|
||||||
|
|
||||||
g.i32.const(65280)
|
|
||||||
|
|
||||||
# 0000 0000 0001 1000, 0000 0110 0001 1000, 1111 1111 0000 0000
|
|
||||||
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
# 0000 0000 0001 1000, 0000 0110 0000 0000
|
|
||||||
|
|
||||||
g.i32.const(8)
|
|
||||||
|
|
||||||
# 0000 0000 0001 1000, 0000 0110 0000 0000, 8
|
|
||||||
|
|
||||||
g.i32.shr_u()
|
|
||||||
|
|
||||||
# 0000 0000 0001 1000, 0000 0000 0000 0110
|
|
||||||
|
|
||||||
g.i32.or_()
|
|
||||||
|
|
||||||
# 0000 0000 0001 110
|
|
||||||
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __u8_rotr__(g: Generator, x: i32, r: i32) -> i32:
|
|
||||||
s = i32('s') # The shifted part we need to overlay
|
|
||||||
|
|
||||||
# Handle cases where we need to shift more than 8 bits
|
|
||||||
g.local.get(r)
|
|
||||||
g.i32.const(8)
|
|
||||||
g.i32.rem_u()
|
|
||||||
g.local.set(r)
|
|
||||||
|
|
||||||
# Now do the rotation
|
|
||||||
|
|
||||||
g.local.get(x)
|
|
||||||
|
|
||||||
# 0000 0000 1100 0011
|
|
||||||
|
|
||||||
g.local.get(r)
|
|
||||||
|
|
||||||
# 0000 0000 1100 0011, 3
|
|
||||||
|
|
||||||
g.i32.rotr()
|
|
||||||
|
|
||||||
# 0110 0000 0000 0000 0000 0000 0001 1000
|
|
||||||
|
|
||||||
g.local.tee(s)
|
|
||||||
|
|
||||||
# 0110 0000 0000 0000 0000 0000 0001 1000
|
|
||||||
|
|
||||||
g.i32.const(255)
|
|
||||||
|
|
||||||
# 0110 0000 0000 0000 0000 0000 0001 1000, 0000 0000 1111 1111
|
|
||||||
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
# 0000 0000 0000 0000 0000 0000 0001 1000
|
|
||||||
|
|
||||||
g.local.get(s)
|
|
||||||
|
|
||||||
# 0000 0000 0000 0000 0000 0000 0001 1000, 0110 0000 0000 0000 0000 0000 0001 1000
|
|
||||||
|
|
||||||
g.i32.const(4278190080)
|
|
||||||
|
|
||||||
# 0000 0000 0000 0000 0000 0000 0001 1000, 0110 0000 0000 0000 0000 0000 0001 1000, 1111 1111 0000 0000 0000 0000 0000 0000
|
|
||||||
|
|
||||||
g.i32.and_()
|
|
||||||
|
|
||||||
# 0000 0000 0000 0000 0000 0000 0001 1000, 0110 0000 0000 0000 0000 0000 0000 0000
|
|
||||||
|
|
||||||
g.i32.const(24)
|
|
||||||
|
|
||||||
# 0000 0000 0000 0000 0000 0000 0001 1000, 0110 0000 0000 0000 0000 0000 0000 0000, 24
|
|
||||||
|
|
||||||
g.i32.shr_u()
|
|
||||||
|
|
||||||
# 0000 0000 0000 0000 0000 0000 0001 1000, 0000 0000 0000 0000 0000 0000 0110 0000
|
|
||||||
|
|
||||||
g.i32.or_()
|
|
||||||
|
|
||||||
# 0000 0000 0000 0000 0000 0000 0111 1000
|
|
||||||
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __u16_rotl__(g: Generator, x: i32, r: i32) -> i32:
|
|
||||||
s = i32('s') # The shifted part we need to overlay
|
|
||||||
|
|
||||||
# Handle cases where we need to shift more than 8 bits
|
|
||||||
g.local.get(r)
|
|
||||||
g.i32.const(8)
|
|
||||||
g.i32.rem_u()
|
|
||||||
g.local.set(r)
|
|
||||||
|
|
||||||
# Now do the rotation
|
|
||||||
|
|
||||||
g.local.get(x)
|
|
||||||
# 0x0000B2C3
|
|
||||||
g.local.get(r)
|
|
||||||
# 0x0000B2C3, 3
|
|
||||||
g.i32.shl()
|
|
||||||
# 0x00059618
|
|
||||||
g.local.tee(s)
|
|
||||||
# 0x00059618
|
|
||||||
g.i32.const(0xFFFF)
|
|
||||||
# 0x00059618, 0x0000FFFF
|
|
||||||
g.i32.and_()
|
|
||||||
# 0x00009618
|
|
||||||
g.local.get(s)
|
|
||||||
# 0x00009618, 0x00059618
|
|
||||||
g.i32.const(0xFFFF0000)
|
|
||||||
# 0x00009618, 0x00059618, 0xFFFF0000
|
|
||||||
g.i32.and_()
|
|
||||||
# 0x00009618, 0x00050000
|
|
||||||
g.i32.const(16)
|
|
||||||
# 0x00009618, 0x00050000, 16
|
|
||||||
g.i32.shr_u()
|
|
||||||
# 0x00009618, 0x00000005, 16
|
|
||||||
g.i32.or_()
|
|
||||||
# 0x0000961D
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
|
||||||
|
|
||||||
@func_wrapper()
|
|
||||||
def __u16_rotr__(g: Generator, x: i32, r: i32) -> i32:
|
|
||||||
s = i32('s') # The shifted part we need to overlay
|
|
||||||
|
|
||||||
# Handle cases where we need to shift more than 16 bits
|
|
||||||
g.local.get(r)
|
|
||||||
g.i32.const(16)
|
|
||||||
g.i32.rem_u()
|
|
||||||
g.local.set(r)
|
|
||||||
|
|
||||||
# Now do the rotation
|
|
||||||
|
|
||||||
g.local.get(x)
|
|
||||||
# 0x0000B2C3
|
|
||||||
g.local.get(r)
|
|
||||||
# 0x0000B2C3, 3
|
|
||||||
g.i32.rotr()
|
|
||||||
# 0x60001658
|
|
||||||
g.local.tee(s)
|
|
||||||
# 0x60001658
|
|
||||||
g.i32.const(0xFFFF)
|
|
||||||
# 0x60001658, 0x0000FFFF
|
|
||||||
g.i32.and_()
|
|
||||||
# 0x00001658
|
|
||||||
g.local.get(s)
|
|
||||||
# 0x00001658, 0x60001658
|
|
||||||
g.i32.const(0xFFFF0000)
|
|
||||||
# 0x00001658, 0x60001658, 0xFFFF0000
|
|
||||||
g.i32.and_()
|
|
||||||
# 0x00001658, 0x60000000
|
|
||||||
g.i32.const(16)
|
|
||||||
# 0x00001658, 0x60000000, 16
|
|
||||||
g.i32.shr_u()
|
|
||||||
# 0x00001658, 0x00006000
|
|
||||||
g.i32.or_()
|
|
||||||
# 0x00007658
|
|
||||||
g.return_()
|
g.return_()
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
return i32('return') # To satisfy mypy
|
||||||
|
|||||||
326
phasm/type3/constraints.py
Normal file
326
phasm/type3/constraints.py
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
"""
|
||||||
|
This module contains possible constraints generated based on the AST
|
||||||
|
|
||||||
|
These need to be resolved before the program can be compiled.
|
||||||
|
"""
|
||||||
|
from typing import Dict, Tuple, Union
|
||||||
|
|
||||||
|
from .. import ourlang
|
||||||
|
|
||||||
|
from . import types
|
||||||
|
|
||||||
|
class Error:
|
||||||
|
def __init__(self, msg: str) -> None:
|
||||||
|
self.msg = msg
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'Error({repr(self.msg)})'
|
||||||
|
|
||||||
|
class RequireTypeSubstitutes:
|
||||||
|
pass
|
||||||
|
|
||||||
|
CheckResult = Union[None, Error, RequireTypeSubstitutes]
|
||||||
|
|
||||||
|
SubstitutionMap = Dict[types.PlaceholderForType, types.Type3]
|
||||||
|
|
||||||
|
HumanReadableRet = Tuple[str, Dict[str, Union[str, ourlang.Expression, types.Type3, types.PlaceholderForType]]]
|
||||||
|
|
||||||
|
class Context:
|
||||||
|
"""
|
||||||
|
Context for constraints
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
class ConstraintBase:
|
||||||
|
"""
|
||||||
|
Base class for constraints
|
||||||
|
"""
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def check(self) -> CheckResult:
|
||||||
|
"""
|
||||||
|
Checks if the constraint hold, returning an error if it doesn't
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_new_placeholder_substitutes(self) -> SubstitutionMap:
|
||||||
|
"""
|
||||||
|
Returns any new placeholders that can be substituted for actual types
|
||||||
|
"""
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def substitute_placeholders(self, smap: SubstitutionMap) -> None:
|
||||||
|
"""
|
||||||
|
Called with type substitutes, so you can update any placeholders
|
||||||
|
you may have with the known types. Note that this does not guarantee
|
||||||
|
that all types are known, you may still have some placeholders left.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError(self, self.substitute_placeholders)
|
||||||
|
|
||||||
|
def human_readable(self) -> HumanReadableRet:
|
||||||
|
"""
|
||||||
|
Returns a more human readable form of this constraint
|
||||||
|
"""
|
||||||
|
return repr(self), {}
|
||||||
|
|
||||||
|
class SameTypeConstraint(ConstraintBase):
|
||||||
|
"""
|
||||||
|
Verifies that an expression has an expected type
|
||||||
|
"""
|
||||||
|
__slots__ = ('expected', 'actual', 'message', )
|
||||||
|
|
||||||
|
expected: types.Type3OrPlaceholder
|
||||||
|
actual: types.Type3OrPlaceholder
|
||||||
|
message: str
|
||||||
|
|
||||||
|
def __init__(self, expected: types.Type3OrPlaceholder, actual: types.Type3OrPlaceholder, message: str) -> None:
|
||||||
|
self.expected = expected
|
||||||
|
self.actual = actual
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
def check(self) -> CheckResult:
|
||||||
|
if isinstance(self.expected, types.PlaceholderForType) or isinstance(self.actual, types.PlaceholderForType):
|
||||||
|
return RequireTypeSubstitutes()
|
||||||
|
|
||||||
|
if self.expected is self.actual:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return Error(f'{self.expected:s} must be {self.actual:s} instead')
|
||||||
|
|
||||||
|
def get_new_placeholder_substitutes(self) -> SubstitutionMap:
|
||||||
|
result: SubstitutionMap = {}
|
||||||
|
|
||||||
|
if isinstance(self.expected, types.Type3) and isinstance(self.actual, types.PlaceholderForType):
|
||||||
|
result = {
|
||||||
|
self.actual: self.expected
|
||||||
|
}
|
||||||
|
|
||||||
|
self.actual.get_substituted(self.expected)
|
||||||
|
self.actual = self.expected
|
||||||
|
|
||||||
|
|
||||||
|
if isinstance(self.actual, types.Type3) and isinstance(self.expected, types.PlaceholderForType):
|
||||||
|
result = {
|
||||||
|
self.expected: self.actual
|
||||||
|
}
|
||||||
|
|
||||||
|
self.expected.get_substituted(self.actual)
|
||||||
|
self.expected = self.actual
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def substitute_placeholders(self, smap: SubstitutionMap) -> None:
|
||||||
|
if isinstance(self.expected, types.PlaceholderForType) and self.expected in smap: # FIXME: Check recursive?
|
||||||
|
self.expected.get_substituted(smap[self.expected])
|
||||||
|
self.expected = smap[self.expected]
|
||||||
|
|
||||||
|
if isinstance(self.actual, types.PlaceholderForType) and self.actual in smap: # FIXME: Check recursive?
|
||||||
|
self.actual.get_substituted(smap[self.actual])
|
||||||
|
self.actual = smap[self.actual]
|
||||||
|
|
||||||
|
def human_readable(self) -> HumanReadableRet:
|
||||||
|
return (
|
||||||
|
'{expected} == {actual}',
|
||||||
|
{
|
||||||
|
'expected': self.expected,
|
||||||
|
'actual': self.actual,
|
||||||
|
'comment': self.message,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'SameTypeConstraint({repr(self.expected)}, {repr(self.actual)}, {repr(self.message)})'
|
||||||
|
|
||||||
|
class MustImplementTypeClassConstraint(ConstraintBase):
|
||||||
|
"""
|
||||||
|
A type must implement a given type class
|
||||||
|
"""
|
||||||
|
__slots__ = ('type_class3', 'type3', )
|
||||||
|
|
||||||
|
type_class3: str
|
||||||
|
type3: types.Type3OrPlaceholder
|
||||||
|
|
||||||
|
def __init__(self, type_class3: str, type3: types.Type3OrPlaceholder) -> None:
|
||||||
|
self.type_class3 = type_class3
|
||||||
|
self.type3 = type3
|
||||||
|
|
||||||
|
def substitute_placeholders(self, smap: SubstitutionMap) -> None:
|
||||||
|
if isinstance(self.type3, types.PlaceholderForType) and self.type3 in smap: # FIXME: Check recursive?
|
||||||
|
self.type3.get_substituted(smap[self.type3])
|
||||||
|
self.type3 = smap[self.type3]
|
||||||
|
|
||||||
|
def check(self) -> CheckResult:
|
||||||
|
if isinstance(self.type3, types.PlaceholderForType):
|
||||||
|
return RequireTypeSubstitutes()
|
||||||
|
|
||||||
|
if 'BitWiseOr' == self.type_class3 and (self.type3 is types.u8 or self.type3 is types.u32 or self.type3 is types.u64):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return Error(f'{self.type3.name} does not implement the {self.type_class3} type class')
|
||||||
|
|
||||||
|
def human_readable(self) -> HumanReadableRet:
|
||||||
|
return (
|
||||||
|
'{type3} derives {type_class3}',
|
||||||
|
{
|
||||||
|
'type_class3': self.type_class3,
|
||||||
|
'type3': self.type3,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'MustImplementTypeClassConstraint({repr(self.type_class3)}, {repr(self.type3)})'
|
||||||
|
|
||||||
|
class LiteralFitsConstraint(ConstraintBase):
|
||||||
|
"""
|
||||||
|
A literal value fits a given type
|
||||||
|
"""
|
||||||
|
__slots__ = ('type3', 'literal', )
|
||||||
|
|
||||||
|
type3: types.Type3OrPlaceholder
|
||||||
|
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]
|
||||||
|
|
||||||
|
def __init__(self, type3: types.Type3OrPlaceholder, literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]) -> None:
|
||||||
|
self.type3 = type3
|
||||||
|
self.literal = literal
|
||||||
|
|
||||||
|
def check(self) -> CheckResult:
|
||||||
|
int_table: Dict[str, Tuple[int, bool]] = {
|
||||||
|
'u8': (1, False),
|
||||||
|
'u32': (4, False),
|
||||||
|
'u64': (8, False),
|
||||||
|
'i8': (1, True),
|
||||||
|
'i32': (4, True),
|
||||||
|
'i64': (8, True),
|
||||||
|
}
|
||||||
|
|
||||||
|
float_table: Dict[str, None] = {
|
||||||
|
'f32': None,
|
||||||
|
'f64': None,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _check(type3: types.Type3OrPlaceholder, literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]) -> CheckResult:
|
||||||
|
if isinstance(type3, types.PlaceholderForType):
|
||||||
|
return RequireTypeSubstitutes()
|
||||||
|
|
||||||
|
val = literal.value
|
||||||
|
|
||||||
|
if type3.name in int_table:
|
||||||
|
bts, sgn = int_table[type3.name]
|
||||||
|
|
||||||
|
if isinstance(val, int):
|
||||||
|
try:
|
||||||
|
val.to_bytes(bts, 'big', signed=sgn)
|
||||||
|
except OverflowError:
|
||||||
|
return Error(f'Must fit in {bts} byte(s)') # FIXME: Add line information
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
return Error('Must be integer') # FIXME: Add line information
|
||||||
|
|
||||||
|
|
||||||
|
if type3.name in float_table:
|
||||||
|
_ = float_table[type3.name]
|
||||||
|
|
||||||
|
if isinstance(val, float):
|
||||||
|
# FIXME: Bit check
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
return Error('Must be real') # FIXME: Add line information
|
||||||
|
|
||||||
|
if isinstance(type3, types.AppliedType3) and type3.base is types.tuple:
|
||||||
|
if not isinstance(literal, ourlang.ConstantTuple):
|
||||||
|
return Error('Must be tuple')
|
||||||
|
|
||||||
|
assert isinstance(val, list) # type hint
|
||||||
|
|
||||||
|
if len(type3.args) != len(val):
|
||||||
|
return Error('Tuple element count mismatch')
|
||||||
|
|
||||||
|
for elt_typ, elt_lit in zip(type3.args, val):
|
||||||
|
res = _check(elt_typ, elt_lit)
|
||||||
|
if res is not None:
|
||||||
|
return res
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
return _check(self.type3, self.literal)
|
||||||
|
|
||||||
|
def substitute_placeholders(self, smap: SubstitutionMap) -> None: # FIXME: Duplicate code
|
||||||
|
if isinstance(self.type3, types.PlaceholderForType) and self.type3 in smap: # FIXME: Check recursive?
|
||||||
|
self.type3.get_substituted(smap[self.type3])
|
||||||
|
self.type3 = smap[self.type3]
|
||||||
|
|
||||||
|
def human_readable(self) -> HumanReadableRet:
|
||||||
|
return (
|
||||||
|
'{literal} : {type3}',
|
||||||
|
{
|
||||||
|
'literal': self.literal,
|
||||||
|
'type3': self.type3,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'LiteralFitsConstraint({repr(self.type3)}, {repr(self.literal)})'
|
||||||
|
|
||||||
|
class CanBeSubscriptedConstraint(ConstraintBase):
|
||||||
|
"""
|
||||||
|
A value that is subscipted, i.e. a[0] (tuple) or a[b] (static array)
|
||||||
|
"""
|
||||||
|
__slots__ = ('type3', 'index', 'index_type3', )
|
||||||
|
|
||||||
|
type3: types.Type3OrPlaceholder
|
||||||
|
index: ourlang.Expression
|
||||||
|
index_type3: types.Type3OrPlaceholder
|
||||||
|
|
||||||
|
def __init__(self, type3: types.Type3OrPlaceholder, index: ourlang.Expression) -> None:
|
||||||
|
self.type3 = type3
|
||||||
|
self.index = index
|
||||||
|
self.index_type3 = index.type3
|
||||||
|
|
||||||
|
def check(self) -> CheckResult:
|
||||||
|
if isinstance(self.type3, types.PlaceholderForType):
|
||||||
|
return RequireTypeSubstitutes()
|
||||||
|
|
||||||
|
if isinstance(self.index_type3, types.PlaceholderForType):
|
||||||
|
return RequireTypeSubstitutes()
|
||||||
|
|
||||||
|
if not isinstance(self.type3, types.AppliedType3):
|
||||||
|
return Error(f'Cannot subscript {self.type3:s}')
|
||||||
|
|
||||||
|
if self.type3.base is types.tuple:
|
||||||
|
return None
|
||||||
|
|
||||||
|
raise NotImplementedError(self.type3)
|
||||||
|
|
||||||
|
def get_new_placeholder_substitutes(self) -> SubstitutionMap:
|
||||||
|
if isinstance(self.type3, types.AppliedType3) and self.type3.base is types.tuple and isinstance(self.index_type3, types.PlaceholderForType):
|
||||||
|
return {
|
||||||
|
self.index_type3: types.u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def substitute_placeholders(self, smap: SubstitutionMap) -> None: # FIXME: Duplicate code
|
||||||
|
if isinstance(self.type3, types.PlaceholderForType) and self.type3 in smap: # FIXME: Check recursive?
|
||||||
|
self.type3.get_substituted(smap[self.type3])
|
||||||
|
self.type3 = smap[self.type3]
|
||||||
|
|
||||||
|
if isinstance(self.index_type3, types.PlaceholderForType) and self.index_type3 in smap: # FIXME: Check recursive?
|
||||||
|
self.index_type3.get_substituted(smap[self.index_type3])
|
||||||
|
self.index_type3 = smap[self.index_type3]
|
||||||
|
|
||||||
|
def human_readable(self) -> HumanReadableRet:
|
||||||
|
return (
|
||||||
|
'{type3}[{index}]',
|
||||||
|
{
|
||||||
|
'type3': self.type3,
|
||||||
|
'index': self.index,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'CanBeSubscriptedConstraint({repr(self.type3)}, {repr(self.index)})'
|
||||||
97
phasm/type3/constraintsgenerator.py
Normal file
97
phasm/type3/constraintsgenerator.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
"""
|
||||||
|
This module generates the typing constraints for Phasm.
|
||||||
|
|
||||||
|
The constraints solver can then try to resolve all constraints.
|
||||||
|
"""
|
||||||
|
from typing import Generator, List
|
||||||
|
|
||||||
|
from .. import ourlang
|
||||||
|
|
||||||
|
from .constraints import (
|
||||||
|
Context,
|
||||||
|
|
||||||
|
ConstraintBase,
|
||||||
|
CanBeSubscriptedConstraint,
|
||||||
|
LiteralFitsConstraint, MustImplementTypeClassConstraint, SameTypeConstraint,
|
||||||
|
)
|
||||||
|
|
||||||
|
def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]:
|
||||||
|
ctx = Context()
|
||||||
|
|
||||||
|
return [*module(ctx, inp)]
|
||||||
|
|
||||||
|
def constant(ctx: Context, inp: ourlang.Constant) -> Generator[ConstraintBase, None, None]:
|
||||||
|
if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantTuple, )):
|
||||||
|
yield LiteralFitsConstraint(inp.type3, inp)
|
||||||
|
return
|
||||||
|
|
||||||
|
raise NotImplementedError(constant, inp)
|
||||||
|
|
||||||
|
def expression(ctx: Context, inp: ourlang.Expression) -> Generator[ConstraintBase, None, None]:
|
||||||
|
if isinstance(inp, ourlang.Constant):
|
||||||
|
yield from constant(ctx, inp)
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(inp, ourlang.VariableReference):
|
||||||
|
yield SameTypeConstraint(inp.variable.type3, inp.type3, f'The type of a variable reference is the same as the type of variable {inp.variable.name}')
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(inp, ourlang.BinaryOp):
|
||||||
|
if '|' == inp.operator:
|
||||||
|
yield from expression(ctx, inp.left)
|
||||||
|
yield from expression(ctx, inp.right)
|
||||||
|
|
||||||
|
yield MustImplementTypeClassConstraint('BitWiseOr', inp.left.type3)
|
||||||
|
yield SameTypeConstraint(inp.right.type3, inp.left.type3, '(|) :: a -> a -> a')
|
||||||
|
yield SameTypeConstraint(inp.type3, inp.right.type3, '(|) :: a -> a -> a')
|
||||||
|
return
|
||||||
|
|
||||||
|
raise NotImplementedError(expression, inp)
|
||||||
|
|
||||||
|
if isinstance(inp, ourlang.FunctionCall):
|
||||||
|
yield SameTypeConstraint(inp.function.returns_type3, inp.type3, f'The type of a function call to {inp.function.name} is the same as the type that the function returns')
|
||||||
|
|
||||||
|
assert len(inp.arguments) == len(inp.function.posonlyargs) # FIXME: Make this a Constraint
|
||||||
|
|
||||||
|
for fun_arg, call_arg in zip(inp.function.posonlyargs, inp.arguments):
|
||||||
|
yield from expression(ctx, call_arg)
|
||||||
|
yield SameTypeConstraint(fun_arg.type3, call_arg.type3,
|
||||||
|
f'The type of the value passed to argument {fun_arg.name} of function {inp.function.name} should match the type of that argument')
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(inp, ourlang.Subscript):
|
||||||
|
yield from expression(ctx, inp.varref)
|
||||||
|
yield from expression(ctx, inp.index)
|
||||||
|
|
||||||
|
yield CanBeSubscriptedConstraint(inp.varref.type3, inp.index)
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(inp, ourlang.AccessStructMember):
|
||||||
|
yield SameTypeConstraint(inp.struct_type3.members[inp.member], inp.type3,
|
||||||
|
f'The type of a struct member reference is the same as the type of struct member {inp.struct_type3.name}.{inp.member}')
|
||||||
|
return
|
||||||
|
|
||||||
|
raise NotImplementedError(expression, inp)
|
||||||
|
|
||||||
|
def function(ctx: Context, inp: ourlang.Function) -> Generator[ConstraintBase, None, None]:
|
||||||
|
if isinstance(inp, ourlang.StructConstructor):
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(inp.statements) != 1 or not isinstance(inp.statements[0], ourlang.StatementReturn):
|
||||||
|
raise NotImplementedError('Functions with not just a return statement')
|
||||||
|
|
||||||
|
yield from expression(ctx, inp.statements[0].value)
|
||||||
|
|
||||||
|
yield SameTypeConstraint(inp.returns_type3, inp.statements[0].value.type3, f'The type of the value returned from function {inp.name} should match its return type')
|
||||||
|
|
||||||
|
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> Generator[ConstraintBase, None, None]:
|
||||||
|
yield from constant(ctx, inp.constant)
|
||||||
|
yield SameTypeConstraint(inp.type3, inp.constant.type3, f'The type of the value for module constant definition {inp.name} should match the type of that constant')
|
||||||
|
|
||||||
|
def module(ctx: Context, inp: ourlang.Module) -> Generator[ConstraintBase, None, None]:
|
||||||
|
for cdef in inp.constant_defs.values():
|
||||||
|
yield from module_constant_def(ctx, cdef)
|
||||||
|
|
||||||
|
for func in inp.functions.values():
|
||||||
|
yield from function(ctx, func)
|
||||||
103
phasm/type3/entry.py
Normal file
103
phasm/type3/entry.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
"""
|
||||||
|
Entry point to the type3 system
|
||||||
|
"""
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
from .. import codestyle
|
||||||
|
from .. import ourlang
|
||||||
|
|
||||||
|
from .constraints import ConstraintBase, Error, RequireTypeSubstitutes, SameTypeConstraint, SubstitutionMap
|
||||||
|
from .constraintsgenerator import phasm_type3_generate_constraints
|
||||||
|
from .types import PlaceholderForType, Type3
|
||||||
|
|
||||||
|
MAX_RESTACK_COUNT = 100
|
||||||
|
|
||||||
|
class Type3Exception(BaseException):
|
||||||
|
"""
|
||||||
|
Thrown when the Type3 system detects constraints that do not hold
|
||||||
|
"""
|
||||||
|
|
||||||
|
def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
|
||||||
|
constraint_list = phasm_type3_generate_constraints(inp)
|
||||||
|
assert constraint_list
|
||||||
|
|
||||||
|
placeholder_substitutes: Dict[PlaceholderForType, Type3] = {}
|
||||||
|
placeholder_id_map: Dict[int, str] = {}
|
||||||
|
|
||||||
|
restack_counter = 0
|
||||||
|
|
||||||
|
error_list: List[Error] = []
|
||||||
|
while constraint_list:
|
||||||
|
if verbose:
|
||||||
|
print()
|
||||||
|
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
|
||||||
|
|
||||||
|
constraint = constraint_list.pop(0)
|
||||||
|
|
||||||
|
constraint.substitute_placeholders(placeholder_substitutes)
|
||||||
|
placeholder_substitutes.update(constraint.get_new_placeholder_substitutes())
|
||||||
|
|
||||||
|
check_result = constraint.check()
|
||||||
|
if check_result is None:
|
||||||
|
if verbose:
|
||||||
|
print('Constraint checks out')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(check_result, Error):
|
||||||
|
error_list.append(check_result)
|
||||||
|
if verbose:
|
||||||
|
print('Got an error')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(check_result, RequireTypeSubstitutes):
|
||||||
|
# FIXME: How to detect infinite loop? Is that necessary?
|
||||||
|
restack_counter += 1
|
||||||
|
if restack_counter > MAX_RESTACK_COUNT:
|
||||||
|
raise Exception('This looks like an infinite loop', constraint_list)
|
||||||
|
|
||||||
|
constraint_list.append(constraint)
|
||||||
|
if verbose:
|
||||||
|
print('Back on the todo list')
|
||||||
|
continue
|
||||||
|
|
||||||
|
raise NotImplementedError(constraint, check_result)
|
||||||
|
|
||||||
|
if error_list:
|
||||||
|
raise Type3Exception(error_list)
|
||||||
|
|
||||||
|
# TODO: Implement type substitution on the AST
|
||||||
|
|
||||||
|
def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase) -> None:
|
||||||
|
txt, fmt = constraint.human_readable()
|
||||||
|
act_fmt: Dict[str, str] = {}
|
||||||
|
for fmt_key, fmt_val in fmt.items():
|
||||||
|
if isinstance(fmt_val, ourlang.Expression):
|
||||||
|
fmt_val = codestyle.expression(fmt_val)
|
||||||
|
|
||||||
|
if isinstance(fmt_val, Type3):
|
||||||
|
fmt_val = fmt_val.name
|
||||||
|
|
||||||
|
if isinstance(fmt_val, PlaceholderForType):
|
||||||
|
placeholder_id = id(fmt_val)
|
||||||
|
if placeholder_id not in placeholder_id_map:
|
||||||
|
placeholder_id_map[placeholder_id] = 'T' + str(len(placeholder_id_map) + 1)
|
||||||
|
fmt_val = placeholder_id_map[placeholder_id]
|
||||||
|
|
||||||
|
if not isinstance(fmt_val, str):
|
||||||
|
fmt_val = repr(fmt_val)
|
||||||
|
|
||||||
|
act_fmt[fmt_key] = fmt_val
|
||||||
|
|
||||||
|
if 'comment' in act_fmt:
|
||||||
|
print('- ' + txt.format(**act_fmt).ljust(40) + '; ' + act_fmt['comment'])
|
||||||
|
else:
|
||||||
|
print('- ' + txt.format(**act_fmt))
|
||||||
|
|
||||||
|
def print_constraint_list(placeholder_id_map: Dict[int, str], constraint_list: List[ConstraintBase], placeholder_substitutes: SubstitutionMap) -> None:
|
||||||
|
print('=== v type3 constraint_list v === ')
|
||||||
|
for psk, psv in placeholder_substitutes.items():
|
||||||
|
print_constraint(placeholder_id_map, SameTypeConstraint(psk, psv, 'Deduced type'))
|
||||||
|
|
||||||
|
for constraint in constraint_list:
|
||||||
|
print_constraint(placeholder_id_map, constraint)
|
||||||
|
print('=== ^ type3 constraint_list ^ === ')
|
||||||
259
phasm/type3/types.py
Normal file
259
phasm/type3/types.py
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
"""
|
||||||
|
Contains the final types for use in Phasm
|
||||||
|
|
||||||
|
These are actual, instantiated types; not the abstract types that the
|
||||||
|
constraint generator works with.
|
||||||
|
"""
|
||||||
|
from typing import Any, Dict, Iterable, List, Protocol, Union
|
||||||
|
|
||||||
|
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method'
|
||||||
|
|
||||||
|
class ExpressionProtocol(Protocol):
|
||||||
|
"""
|
||||||
|
A protocol for classes that should be updated on substitution
|
||||||
|
"""
|
||||||
|
|
||||||
|
type3: 'Type3OrPlaceholder'
|
||||||
|
"""
|
||||||
|
The type to update
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Type3:
|
||||||
|
"""
|
||||||
|
Base class for the type3 types
|
||||||
|
"""
|
||||||
|
__slots__ = ('name', )
|
||||||
|
|
||||||
|
name: str
|
||||||
|
"""
|
||||||
|
The name of the string, as parsed and outputted by codestyle.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name: str) -> None:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'Type3("{self.name}")'
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def __format__(self, format_spec: str) -> str:
|
||||||
|
if format_spec != 's':
|
||||||
|
raise TypeError(f'unsupported format string passed to Type3.__format__: {format_spec}')
|
||||||
|
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
def __eq__(self, other: Any) -> bool:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __ne__(self, other: Any) -> bool:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class PlaceholderForType:
|
||||||
|
"""
|
||||||
|
A placeholder type, for when we don't know the final type yet
|
||||||
|
"""
|
||||||
|
__slots__ = ('update_on_substitution', )
|
||||||
|
|
||||||
|
update_on_substitution: List[ExpressionProtocol]
|
||||||
|
|
||||||
|
def __init__(self, update_on_substitution: Iterable[ExpressionProtocol]) -> None:
|
||||||
|
self.update_on_substitution = [*update_on_substitution]
|
||||||
|
|
||||||
|
def get_substituted(self, result_type: Type3) -> None:
|
||||||
|
"""
|
||||||
|
Informs this Placeholder that it's getting substituted
|
||||||
|
|
||||||
|
This will also clear the update_on_substitution list
|
||||||
|
"""
|
||||||
|
for uos in self.update_on_substitution:
|
||||||
|
uos.type3 = result_type
|
||||||
|
|
||||||
|
self.update_on_substitution = []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return f'T{id(self)}'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
uos = ', '.join(repr(x) for x in self.update_on_substitution)
|
||||||
|
|
||||||
|
return f'PlaceholderForType({id(self)}, [{uos}])'
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f'PhFT_{id(self)}'
|
||||||
|
|
||||||
|
def __format__(self, format_spec: str) -> str:
|
||||||
|
if format_spec != 's':
|
||||||
|
raise TypeError('unsupported format string passed to Type3.__format__')
|
||||||
|
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
def __eq__(self, other: Any) -> bool:
|
||||||
|
if not isinstance(other, PlaceholderForType):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
return self is other
|
||||||
|
|
||||||
|
def __ne__(self, other: Any) -> bool:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return 0 # Valid but performs badly
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
Type3OrPlaceholder = Union[Type3, PlaceholderForType]
|
||||||
|
|
||||||
|
class AppliedType3(Type3):
|
||||||
|
"""
|
||||||
|
A Type3 that has been applied to another type
|
||||||
|
"""
|
||||||
|
__slots__ = ('base', 'args', )
|
||||||
|
|
||||||
|
base: Type3
|
||||||
|
"""
|
||||||
|
The base type
|
||||||
|
"""
|
||||||
|
|
||||||
|
args: List[Type3OrPlaceholder]
|
||||||
|
"""
|
||||||
|
The applied types (or placeholders there for)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, base: Type3, args: Iterable[Type3OrPlaceholder]) -> None:
|
||||||
|
args = [*args]
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
base.name
|
||||||
|
+ ' ('
|
||||||
|
+ ') ('.join(str(x) for x in args) # FIXME: Do we need to redo the name on substitution?
|
||||||
|
+ ')'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.base = base
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'AppliedType3({repr(self.base)}, {repr(self.args)})'
|
||||||
|
|
||||||
|
class StructType3(Type3):
|
||||||
|
"""
|
||||||
|
A Type3 struct with named members
|
||||||
|
"""
|
||||||
|
__slots__ = ('name', 'members', )
|
||||||
|
|
||||||
|
name: str
|
||||||
|
"""
|
||||||
|
The structs fully qualified name
|
||||||
|
"""
|
||||||
|
|
||||||
|
members: Dict[str, Type3]
|
||||||
|
"""
|
||||||
|
The struct's field definitions
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name: str, members: Dict[str, Type3]) -> None:
|
||||||
|
super().__init__(name)
|
||||||
|
|
||||||
|
self.name = name
|
||||||
|
self.members = dict(members)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'StructType3(repr({self.name}), repr({self.members}))'
|
||||||
|
|
||||||
|
none = Type3('none')
|
||||||
|
"""
|
||||||
|
The none type, for when functions simply don't return anything. e.g., IO().
|
||||||
|
"""
|
||||||
|
|
||||||
|
u8 = Type3('u8')
|
||||||
|
"""
|
||||||
|
The unsigned 8-bit integer type.
|
||||||
|
|
||||||
|
Operations on variables employ modular arithmetic, with modulus 2^8.
|
||||||
|
"""
|
||||||
|
|
||||||
|
u32 = Type3('u32')
|
||||||
|
"""
|
||||||
|
The unsigned 32-bit integer type.
|
||||||
|
|
||||||
|
Operations on variables employ modular arithmetic, with modulus 2^32.
|
||||||
|
"""
|
||||||
|
|
||||||
|
u64 = Type3('u64')
|
||||||
|
"""
|
||||||
|
The unsigned 64-bit integer type.
|
||||||
|
|
||||||
|
Operations on variables employ modular arithmetic, with modulus 2^64.
|
||||||
|
"""
|
||||||
|
|
||||||
|
i8 = Type3('i8')
|
||||||
|
"""
|
||||||
|
The signed 8-bit integer type.
|
||||||
|
|
||||||
|
Operations on variables employ modular arithmetic, with modulus 2^8, but
|
||||||
|
with the middel point being 0.
|
||||||
|
"""
|
||||||
|
|
||||||
|
i32 = Type3('i32')
|
||||||
|
"""
|
||||||
|
The unsigned 32-bit integer type.
|
||||||
|
|
||||||
|
Operations on variables employ modular arithmetic, with modulus 2^32, but
|
||||||
|
with the middel point being 0.
|
||||||
|
"""
|
||||||
|
|
||||||
|
i64 = Type3('i64')
|
||||||
|
"""
|
||||||
|
The unsigned 64-bit integer type.
|
||||||
|
|
||||||
|
Operations on variables employ modular arithmetic, with modulus 2^64, but
|
||||||
|
with the middel point being 0.
|
||||||
|
"""
|
||||||
|
|
||||||
|
f32 = Type3('f32')
|
||||||
|
"""
|
||||||
|
A 32-bits IEEE 754 float, of 32 bits width.
|
||||||
|
"""
|
||||||
|
|
||||||
|
f64 = Type3('f64')
|
||||||
|
"""
|
||||||
|
A 32-bits IEEE 754 float, of 64 bits width.
|
||||||
|
"""
|
||||||
|
|
||||||
|
static_array = Type3('static_array')
|
||||||
|
"""
|
||||||
|
This is a fixed length piece of memory that can be indexed at runtime.
|
||||||
|
|
||||||
|
It should be applied with one argument. It has a runtime-dynamic length
|
||||||
|
of the same type repeated.
|
||||||
|
"""
|
||||||
|
|
||||||
|
tuple = Type3('tuple') # pylint: disable=W0622
|
||||||
|
"""
|
||||||
|
This is a fixed length piece of memory.
|
||||||
|
|
||||||
|
It should be applied with zero or more arguments. It has a compile time
|
||||||
|
determined length, and each argument can be different.
|
||||||
|
"""
|
||||||
|
|
||||||
|
LOOKUP_TABLE: Dict[str, Type3] = {
|
||||||
|
'none': none,
|
||||||
|
'u8': u8,
|
||||||
|
'u32': u32,
|
||||||
|
'u64': u64,
|
||||||
|
'i8': i8,
|
||||||
|
'i32': i32,
|
||||||
|
'i64': i64,
|
||||||
|
'f32': f32,
|
||||||
|
'f64': f64,
|
||||||
|
}
|
||||||
@ -1,63 +0,0 @@
|
|||||||
"""
|
|
||||||
Some expression have constraints - those are usually the outmost expressions.
|
|
||||||
"""
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import Callable, Self
|
|
||||||
|
|
||||||
from .kindexpr import KindExpr
|
|
||||||
from .typeexpr import TypeExpr, TypeVariable, instantiate
|
|
||||||
|
|
||||||
|
|
||||||
class TypeConstraint:
|
|
||||||
"""
|
|
||||||
Base class for type constraints
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def instantiate(self, known_map: dict[TypeVariable, TypeVariable]) -> Self:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ConstrainedExpr:
|
|
||||||
"""
|
|
||||||
Also known as a polytype.
|
|
||||||
|
|
||||||
We only have rank 1 types, so this is always on the outside.
|
|
||||||
"""
|
|
||||||
variables: set[TypeVariable]
|
|
||||||
expr: TypeExpr
|
|
||||||
constraints: tuple[TypeConstraint, ...]
|
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
|
||||||
return hash((
|
|
||||||
tuple(sorted(self.variables)),
|
|
||||||
self.expr,
|
|
||||||
self.constraints,
|
|
||||||
))
|
|
||||||
|
|
||||||
# TODO: Move instantiate here? it only makes sense for polytypes
|
|
||||||
|
|
||||||
def instantiate_constrained(
|
|
||||||
constrainedexpr: ConstrainedExpr,
|
|
||||||
make_variable: Callable[[KindExpr, str], TypeVariable],
|
|
||||||
) -> tuple[ConstrainedExpr, dict[TypeVariable, TypeVariable]]:
|
|
||||||
"""
|
|
||||||
Instantiates a type expression and its constraints
|
|
||||||
"""
|
|
||||||
# Would be handier if we had the list of variables on ConstrainedExpr
|
|
||||||
# So we don't have to pass make_variable
|
|
||||||
# This also helps with FunctionCall.substitutions
|
|
||||||
known_map = {
|
|
||||||
v: make_variable(v.kind, v.name)
|
|
||||||
for v in constrainedexpr.variables
|
|
||||||
}
|
|
||||||
|
|
||||||
expr = instantiate(constrainedexpr.expr, known_map)
|
|
||||||
constraints = tuple(
|
|
||||||
x.instantiate(known_map)
|
|
||||||
for x in constrainedexpr.constraints
|
|
||||||
)
|
|
||||||
return ConstrainedExpr(constrainedexpr.variables, expr, constraints), known_map
|
|
||||||
@ -1,638 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import dataclasses
|
|
||||||
from typing import Any, Callable, Iterable, Protocol, Sequence, TypeAlias
|
|
||||||
|
|
||||||
from ..build.base import BuildBase
|
|
||||||
from ..ourlang import SourceRef
|
|
||||||
from ..wasm import WasmTypeFloat32, WasmTypeFloat64, WasmTypeInt32, WasmTypeInt64
|
|
||||||
from .kindexpr import KindExpr, Star
|
|
||||||
from .record import Record
|
|
||||||
from .typeexpr import (
|
|
||||||
AtomicType,
|
|
||||||
TypeApplication,
|
|
||||||
TypeConstructor,
|
|
||||||
TypeExpr,
|
|
||||||
TypeLevelNat,
|
|
||||||
TypeVariable,
|
|
||||||
is_concrete,
|
|
||||||
occurs,
|
|
||||||
replace_variable,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ExpressionProtocol(Protocol):
|
|
||||||
"""
|
|
||||||
A protocol for classes that should be updated on substitution
|
|
||||||
"""
|
|
||||||
|
|
||||||
type5: TypeExpr | None
|
|
||||||
"""
|
|
||||||
The type to update
|
|
||||||
"""
|
|
||||||
|
|
||||||
PolytypeSubsituteMap: TypeAlias = dict[TypeVariable, TypeExpr]
|
|
||||||
|
|
||||||
class Context:
|
|
||||||
__slots__ = ("build", "placeholder_update", "ptst_update", )
|
|
||||||
|
|
||||||
build: BuildBase[Any]
|
|
||||||
placeholder_update: dict[TypeVariable, ExpressionProtocol | None]
|
|
||||||
ptst_update: dict[TypeVariable, tuple[PolytypeSubsituteMap, TypeVariable]]
|
|
||||||
|
|
||||||
def __init__(self, build: BuildBase[Any]) -> None:
|
|
||||||
self.build = build
|
|
||||||
self.placeholder_update = {}
|
|
||||||
self.ptst_update = {}
|
|
||||||
|
|
||||||
def make_placeholder(self, arg: ExpressionProtocol | None = None, kind: KindExpr = Star(), prefix: str = 'p') -> TypeVariable:
|
|
||||||
res = TypeVariable(kind, f"{prefix}_{len(self.placeholder_update)}")
|
|
||||||
self.placeholder_update[res] = arg
|
|
||||||
return res
|
|
||||||
|
|
||||||
def register_polytype_subsitutes(self, tvar: TypeVariable, arg: PolytypeSubsituteMap, orig_var: TypeVariable) -> None:
|
|
||||||
"""
|
|
||||||
When `tvar` gets subsituted, also set the result in arg with orig_var as key
|
|
||||||
|
|
||||||
e.g.
|
|
||||||
|
|
||||||
(-) :: Callable[a, a, a]
|
|
||||||
|
|
||||||
def foo() -> u32:
|
|
||||||
return 2 - 1
|
|
||||||
|
|
||||||
During typing, we instantiate a into a_3, and get the following constraints:
|
|
||||||
- u8 ~ p_1
|
|
||||||
- u8 ~ p_2
|
|
||||||
- Exists NatNum a_3
|
|
||||||
- Callable[a_3, a_3, a_3] ~ Callable[p_1, p_2, p_0]
|
|
||||||
- u8 ~ p_0
|
|
||||||
|
|
||||||
When we resolve a_3, then on the call to `-`, we should note that a_3 got resolved
|
|
||||||
to u32. But we need to use `a` as key, since that's what's used on the definition
|
|
||||||
"""
|
|
||||||
assert tvar in self.placeholder_update
|
|
||||||
assert tvar not in self.ptst_update
|
|
||||||
self.ptst_update[tvar] = (arg, orig_var)
|
|
||||||
|
|
||||||
class Success:
|
|
||||||
"""
|
|
||||||
The contsraint checks out.
|
|
||||||
|
|
||||||
Nothing new was learned and nothing new needs to be checked.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
|
|
||||||
return '(ok)'
|
|
||||||
|
|
||||||
class SkipForNow:
|
|
||||||
"""
|
|
||||||
Not enough information to resolve this constraint
|
|
||||||
"""
|
|
||||||
|
|
||||||
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
|
|
||||||
return '(skip for now)'
|
|
||||||
|
|
||||||
class ConstraintList(list['ConstraintBase']):
|
|
||||||
"""
|
|
||||||
A new list of constraints.
|
|
||||||
|
|
||||||
Sometimes, checking one constraint means you get a list of new contraints.
|
|
||||||
"""
|
|
||||||
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
|
|
||||||
return f'(got {len(self)} new constraints)'
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
|
||||||
class Failure:
|
|
||||||
"""
|
|
||||||
Both types are already different - cannot be unified.
|
|
||||||
"""
|
|
||||||
msg: str
|
|
||||||
|
|
||||||
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
|
|
||||||
return f'ERR: {self.msg}'
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
|
||||||
class ReplaceVariable:
|
|
||||||
"""
|
|
||||||
A variable should be replaced.
|
|
||||||
|
|
||||||
Either by another variable or by a (concrete) type.
|
|
||||||
"""
|
|
||||||
var: TypeVariable
|
|
||||||
typ: TypeExpr
|
|
||||||
|
|
||||||
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
|
|
||||||
return f'{{{self.var.name} := {type_namer(self.typ)}}}'
|
|
||||||
|
|
||||||
CheckResult: TypeAlias = Success | SkipForNow | ConstraintList | Failure | ReplaceVariable
|
|
||||||
|
|
||||||
def ok() -> CheckResult:
|
|
||||||
return Success()
|
|
||||||
|
|
||||||
def skip_for_now() -> CheckResult:
|
|
||||||
return SkipForNow()
|
|
||||||
|
|
||||||
def new_constraints(lst: Iterable[ConstraintBase]) -> CheckResult:
|
|
||||||
return ConstraintList(lst)
|
|
||||||
|
|
||||||
def fail(msg: str) -> CheckResult:
|
|
||||||
return Failure(msg)
|
|
||||||
|
|
||||||
def replace(var: TypeVariable, typ: TypeExpr) -> CheckResult:
|
|
||||||
return ReplaceVariable(var, typ)
|
|
||||||
|
|
||||||
class ConstraintBase:
|
|
||||||
__slots__ = ("ctx", "sourceref", )
|
|
||||||
|
|
||||||
ctx: Context
|
|
||||||
sourceref: SourceRef
|
|
||||||
|
|
||||||
def __init__(self, ctx: Context, sourceref: SourceRef) -> None:
|
|
||||||
self.ctx = ctx
|
|
||||||
self.sourceref = sourceref
|
|
||||||
|
|
||||||
def check(self) -> CheckResult:
|
|
||||||
raise NotImplementedError(self)
|
|
||||||
|
|
||||||
def complexity(self) -> int:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
class FromLiteralInteger(ConstraintBase):
|
|
||||||
__slots__ = ('type5', 'literal', )
|
|
||||||
|
|
||||||
type5: TypeExpr
|
|
||||||
literal: int
|
|
||||||
|
|
||||||
def __init__(self, ctx: Context, sourceref: SourceRef, type5: TypeExpr, literal: int) -> None:
|
|
||||||
super().__init__(ctx, sourceref)
|
|
||||||
|
|
||||||
self.type5 = type5
|
|
||||||
self.literal = literal
|
|
||||||
|
|
||||||
def check(self) -> CheckResult:
|
|
||||||
if not is_concrete(self.type5):
|
|
||||||
return skip_for_now()
|
|
||||||
|
|
||||||
type_info = self.ctx.build.type_info_map.get(self.type5.name)
|
|
||||||
if type_info is None:
|
|
||||||
return fail('Cannot convert from literal integer')
|
|
||||||
|
|
||||||
if type_info.wasm_type is not WasmTypeInt32 and type_info.wasm_type is not WasmTypeInt64:
|
|
||||||
return fail('Cannot convert from literal integer')
|
|
||||||
|
|
||||||
assert type_info.signed is not None # type hint
|
|
||||||
|
|
||||||
if not type_info.signed and self.literal < 0:
|
|
||||||
return fail('May not be negative')
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.literal.to_bytes(type_info.alloc_size, 'big', signed=type_info.signed)
|
|
||||||
except OverflowError:
|
|
||||||
return fail(f'Must fit in {type_info.alloc_size} byte(s)')
|
|
||||||
|
|
||||||
return ok()
|
|
||||||
|
|
||||||
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
|
|
||||||
self.type5 = replace_variable(self.type5, var, typ)
|
|
||||||
|
|
||||||
def complexity(self) -> int:
|
|
||||||
return 100 + complexity(self.type5)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f'FromLiteralInteger {self.ctx.build.type5_name(self.type5)} ~ {self.literal!r}'
|
|
||||||
|
|
||||||
class FromLiteralFloat(ConstraintBase):
|
|
||||||
__slots__ = ('type5', 'literal', )
|
|
||||||
|
|
||||||
type5: TypeExpr
|
|
||||||
literal: float
|
|
||||||
|
|
||||||
def __init__(self, ctx: Context, sourceref: SourceRef, type5: TypeExpr, literal: float) -> None:
|
|
||||||
super().__init__(ctx, sourceref)
|
|
||||||
|
|
||||||
self.type5 = type5
|
|
||||||
self.literal = literal
|
|
||||||
|
|
||||||
def check(self) -> CheckResult:
|
|
||||||
if not is_concrete(self.type5):
|
|
||||||
return skip_for_now()
|
|
||||||
|
|
||||||
type_info = self.ctx.build.type_info_map.get(self.type5.name)
|
|
||||||
if type_info is None:
|
|
||||||
return fail('Cannot convert from literal float')
|
|
||||||
|
|
||||||
if type_info.wasm_type is not WasmTypeFloat32 and type_info.wasm_type is not WasmTypeFloat64:
|
|
||||||
return fail('Cannot convert from literal float')
|
|
||||||
|
|
||||||
# TODO: Precision check
|
|
||||||
|
|
||||||
return ok()
|
|
||||||
|
|
||||||
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
|
|
||||||
self.type5 = replace_variable(self.type5, var, typ)
|
|
||||||
|
|
||||||
def complexity(self) -> int:
|
|
||||||
return 100 + complexity(self.type5)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f'FromLiteralFloat {self.ctx.build.type5_name(self.type5)} ~ {self.literal!r}'
|
|
||||||
|
|
||||||
class FromLiteralBytes(ConstraintBase):
|
|
||||||
__slots__ = ('type5', 'literal', )
|
|
||||||
|
|
||||||
type5: TypeExpr
|
|
||||||
literal: bytes
|
|
||||||
|
|
||||||
def __init__(self, ctx: Context, sourceref: SourceRef, type5: TypeExpr, literal: bytes) -> None:
|
|
||||||
super().__init__(ctx, sourceref)
|
|
||||||
|
|
||||||
self.type5 = type5
|
|
||||||
self.literal = literal
|
|
||||||
|
|
||||||
def check(self) -> CheckResult:
|
|
||||||
if not is_concrete(self.type5):
|
|
||||||
return skip_for_now()
|
|
||||||
|
|
||||||
da_arg = self.ctx.build.type5_is_dynamic_array(self.type5)
|
|
||||||
if da_arg is None or da_arg != self.ctx.build.u8_type5:
|
|
||||||
return fail('Cannot convert from literal bytes')
|
|
||||||
|
|
||||||
return ok()
|
|
||||||
|
|
||||||
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
|
|
||||||
self.type5 = replace_variable(self.type5, var, typ)
|
|
||||||
|
|
||||||
def complexity(self) -> int:
|
|
||||||
return 100 + complexity(self.type5)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f'FromLiteralBytes {self.ctx.build.type5_name(self.type5)} ~ {self.literal!r}'
|
|
||||||
|
|
||||||
class UnifyTypesConstraint(ConstraintBase):
|
|
||||||
__slots__ = ("lft", "rgt", "prefix", )
|
|
||||||
|
|
||||||
def __init__(self, ctx: Context, sourceref: SourceRef, lft: TypeExpr, rgt: TypeExpr, prefix: str | None = None) -> None:
|
|
||||||
super().__init__(ctx, sourceref)
|
|
||||||
|
|
||||||
self.lft = lft
|
|
||||||
self.rgt = rgt
|
|
||||||
self.prefix = prefix
|
|
||||||
|
|
||||||
def check(self) -> CheckResult:
|
|
||||||
lft = self.lft
|
|
||||||
rgt = self.rgt
|
|
||||||
|
|
||||||
if lft == self.rgt:
|
|
||||||
return ok()
|
|
||||||
|
|
||||||
if lft.kind != rgt.kind:
|
|
||||||
return fail("Kind mismatch")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if isinstance(lft, AtomicType) and isinstance(rgt, AtomicType):
|
|
||||||
return fail("Not the same type")
|
|
||||||
|
|
||||||
if isinstance(lft, AtomicType) and isinstance(rgt, TypeVariable):
|
|
||||||
return replace(rgt, lft)
|
|
||||||
|
|
||||||
if isinstance(lft, AtomicType) and isinstance(rgt, TypeConstructor):
|
|
||||||
raise NotImplementedError # Should have been caught by kind check above
|
|
||||||
|
|
||||||
if isinstance(lft, AtomicType) and isinstance(rgt, TypeApplication):
|
|
||||||
return fail("Not the same type" if is_concrete(rgt) else "Type shape mismatch")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if isinstance(lft, TypeVariable) and isinstance(rgt, AtomicType):
|
|
||||||
return replace(lft, rgt)
|
|
||||||
|
|
||||||
if isinstance(lft, TypeVariable) and isinstance(rgt, TypeVariable):
|
|
||||||
return replace(lft, rgt)
|
|
||||||
|
|
||||||
if isinstance(lft, TypeVariable) and isinstance(rgt, TypeConstructor):
|
|
||||||
return replace(lft, rgt)
|
|
||||||
|
|
||||||
if isinstance(lft, TypeVariable) and isinstance(rgt, TypeApplication):
|
|
||||||
if occurs(lft, rgt):
|
|
||||||
return fail("One type occurs in the other")
|
|
||||||
|
|
||||||
return replace(lft, rgt)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if isinstance(lft, TypeConstructor) and isinstance(rgt, AtomicType):
|
|
||||||
raise NotImplementedError # Should have been caught by kind check above
|
|
||||||
|
|
||||||
if isinstance(lft, TypeConstructor) and isinstance(rgt, TypeVariable):
|
|
||||||
return replace(rgt, lft)
|
|
||||||
|
|
||||||
if isinstance(lft, TypeConstructor) and isinstance(rgt, TypeConstructor):
|
|
||||||
return fail("Not the same type constructor")
|
|
||||||
|
|
||||||
if isinstance(lft, TypeConstructor) and isinstance(rgt, TypeApplication):
|
|
||||||
return fail("Not the same type constructor")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if isinstance(lft, TypeApplication) and isinstance(rgt, AtomicType):
|
|
||||||
return fail("Not the same type" if is_concrete(lft) else "Type shape mismatch")
|
|
||||||
|
|
||||||
if isinstance(lft, TypeApplication) and isinstance(rgt, TypeVariable):
|
|
||||||
if occurs(rgt, lft):
|
|
||||||
return fail("One type occurs in the other")
|
|
||||||
|
|
||||||
return replace(rgt, lft)
|
|
||||||
|
|
||||||
if isinstance(lft, TypeApplication) and isinstance(rgt, TypeConstructor):
|
|
||||||
return fail("Not the same type constructor")
|
|
||||||
|
|
||||||
if isinstance(lft, TypeApplication) and isinstance(rgt, TypeApplication):
|
|
||||||
|
|
||||||
## USABILITY HACK
|
|
||||||
## Often, we have two type applications in the same go
|
|
||||||
## If so, resolve it in a single step
|
|
||||||
## (Helps with debugging function unification)
|
|
||||||
## This *should* not affect the actual type unification
|
|
||||||
## It's just one less call to UnifyTypesConstraint.check
|
|
||||||
if isinstance(lft.constructor, TypeApplication) and isinstance(rgt.constructor, TypeApplication):
|
|
||||||
return new_constraints([
|
|
||||||
UnifyTypesConstraint(self.ctx, self.sourceref, lft.constructor.constructor, rgt.constructor.constructor),
|
|
||||||
UnifyTypesConstraint(self.ctx, self.sourceref, lft.constructor.argument, rgt.constructor.argument),
|
|
||||||
UnifyTypesConstraint(self.ctx, self.sourceref, lft.argument, rgt.argument),
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
return new_constraints([
|
|
||||||
UnifyTypesConstraint(self.ctx, self.sourceref, lft.constructor, rgt.constructor),
|
|
||||||
UnifyTypesConstraint(self.ctx, self.sourceref, lft.argument, rgt.argument),
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
raise NotImplementedError(lft, rgt)
|
|
||||||
|
|
||||||
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
|
|
||||||
self.lft = replace_variable(self.lft, var, typ)
|
|
||||||
self.rgt = replace_variable(self.rgt, var, typ)
|
|
||||||
|
|
||||||
def complexity(self) -> int:
|
|
||||||
return complexity(self.lft) + complexity(self.rgt)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
prefix = f'{self.prefix} :: ' if self.prefix else ''
|
|
||||||
return f"{prefix}{self.ctx.build.type5_name(self.lft)} ~ {self.ctx.build.type5_name(self.rgt)}"
|
|
||||||
|
|
||||||
class CanBeSubscriptedConstraint(ConstraintBase):
|
|
||||||
__slots__ = ('ret_type5', 'container_type5', 'index_type5', 'index_const', )
|
|
||||||
|
|
||||||
ret_type5: TypeExpr
|
|
||||||
container_type5: TypeExpr
|
|
||||||
index_type5: TypeExpr
|
|
||||||
index_const: int | None
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
ctx: Context,
|
|
||||||
sourceref: SourceRef,
|
|
||||||
ret_type5: TypeExpr,
|
|
||||||
container_type5: TypeExpr,
|
|
||||||
index_type5: TypeExpr,
|
|
||||||
index_const: int | None,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(ctx, sourceref)
|
|
||||||
|
|
||||||
self.ret_type5 = ret_type5
|
|
||||||
self.container_type5 = container_type5
|
|
||||||
self.index_type5 = index_type5
|
|
||||||
self.index_const = index_const
|
|
||||||
|
|
||||||
def check(self) -> CheckResult:
|
|
||||||
if not is_concrete(self.container_type5):
|
|
||||||
return skip_for_now()
|
|
||||||
|
|
||||||
tp_args = self.ctx.build.type5_is_tuple(self.container_type5)
|
|
||||||
if tp_args is not None:
|
|
||||||
if self.index_const is None:
|
|
||||||
return fail('Must index with integer literal')
|
|
||||||
|
|
||||||
if len(tp_args) <= self.index_const:
|
|
||||||
return fail('Tuple index out of range')
|
|
||||||
|
|
||||||
return new_constraints([
|
|
||||||
UnifyTypesConstraint(self.ctx, self.sourceref, tp_args[self.index_const], self.ret_type5),
|
|
||||||
UnifyTypesConstraint(self.ctx, self.sourceref, self.ctx.build.u32_type5, self.index_type5),
|
|
||||||
])
|
|
||||||
|
|
||||||
if not isinstance(self.container_type5, TypeApplication):
|
|
||||||
return fail('Missing type class instance')
|
|
||||||
|
|
||||||
return new_constraints([
|
|
||||||
TypeClassInstanceExistsConstraint(
|
|
||||||
self.ctx,
|
|
||||||
self.sourceref,
|
|
||||||
'Subscriptable',
|
|
||||||
(self.container_type5.constructor, ),
|
|
||||||
),
|
|
||||||
UnifyTypesConstraint(self.ctx, self.sourceref, self.container_type5.argument, self.ret_type5),
|
|
||||||
UnifyTypesConstraint(self.ctx, self.sourceref, self.ctx.build.u32_type5, self.index_type5),
|
|
||||||
])
|
|
||||||
|
|
||||||
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
|
|
||||||
self.ret_type5 = replace_variable(self.ret_type5, var, typ)
|
|
||||||
self.container_type5 = replace_variable(self.container_type5, var, typ)
|
|
||||||
self.index_type5 = replace_variable(self.index_type5, var, typ)
|
|
||||||
|
|
||||||
def complexity(self) -> int:
|
|
||||||
return 100 + complexity(self.ret_type5) + complexity(self.container_type5) + complexity(self.index_type5)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f"[] :: t a -> b -> a ~ {self.ctx.build.type5_name(self.container_type5)} -> {self.ctx.build.type5_name(self.index_type5)} -> {self.ctx.build.type5_name(self.ret_type5)}"
|
|
||||||
|
|
||||||
class CanAccessStructMemberConstraint(ConstraintBase):
|
|
||||||
__slots__ = ('ret_type5', 'struct_type5', 'member_name', )
|
|
||||||
|
|
||||||
ret_type5: TypeExpr
|
|
||||||
struct_type5: TypeExpr
|
|
||||||
member_name: str
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
ctx: Context,
|
|
||||||
sourceref: SourceRef,
|
|
||||||
ret_type5: TypeExpr,
|
|
||||||
struct_type5: TypeExpr,
|
|
||||||
member_name: str,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(ctx, sourceref)
|
|
||||||
|
|
||||||
self.ret_type5 = ret_type5
|
|
||||||
self.struct_type5 = struct_type5
|
|
||||||
self.member_name = member_name
|
|
||||||
|
|
||||||
def check(self) -> CheckResult:
|
|
||||||
if not is_concrete(self.struct_type5):
|
|
||||||
return skip_for_now()
|
|
||||||
|
|
||||||
st_args = self.ctx.build.type5_is_struct(self.struct_type5)
|
|
||||||
if st_args is None:
|
|
||||||
return fail('Must be a struct')
|
|
||||||
|
|
||||||
member_dict = dict(st_args)
|
|
||||||
|
|
||||||
if self.member_name not in member_dict:
|
|
||||||
return fail('Must have a field with this name')
|
|
||||||
|
|
||||||
return UnifyTypesConstraint(self.ctx, self.sourceref, self.ret_type5, member_dict[self.member_name]).check()
|
|
||||||
|
|
||||||
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
|
|
||||||
self.ret_type5 = replace_variable(self.ret_type5, var, typ)
|
|
||||||
self.struct_type5 = replace_variable(self.struct_type5, var, typ)
|
|
||||||
|
|
||||||
def complexity(self) -> int:
|
|
||||||
return 100 + complexity(self.ret_type5) + complexity(self.struct_type5)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
st_args = self.ctx.build.type5_is_struct(self.struct_type5)
|
|
||||||
member_dict = dict(st_args or [])
|
|
||||||
member_typ = member_dict.get(self.member_name)
|
|
||||||
|
|
||||||
if member_typ is None:
|
|
||||||
expect = 'a -> b'
|
|
||||||
else:
|
|
||||||
expect = f'{self.ctx.build.type5_name(self.struct_type5)} -> {self.ctx.build.type5_name(member_typ)}'
|
|
||||||
|
|
||||||
return f".{self.member_name} :: {expect} ~ {self.ctx.build.type5_name(self.struct_type5)} -> {self.ctx.build.type5_name(self.ret_type5)}"
|
|
||||||
|
|
||||||
class FromTupleConstraint(ConstraintBase):
|
|
||||||
__slots__ = ('ret_type5', 'member_type5_list', )
|
|
||||||
|
|
||||||
ret_type5: TypeExpr
|
|
||||||
member_type5_list: list[TypeExpr]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
ctx: Context,
|
|
||||||
sourceref: SourceRef,
|
|
||||||
ret_type5: TypeExpr,
|
|
||||||
member_type5_list: Sequence[TypeExpr],
|
|
||||||
) -> None:
|
|
||||||
super().__init__(ctx, sourceref)
|
|
||||||
|
|
||||||
self.ret_type5 = ret_type5
|
|
||||||
self.member_type5_list = list(member_type5_list)
|
|
||||||
|
|
||||||
def check(self) -> CheckResult:
|
|
||||||
if not is_concrete(self.ret_type5):
|
|
||||||
return skip_for_now()
|
|
||||||
|
|
||||||
da_arg = self.ctx.build.type5_is_dynamic_array(self.ret_type5)
|
|
||||||
if da_arg is not None:
|
|
||||||
return new_constraints([
|
|
||||||
UnifyTypesConstraint(self.ctx, self.sourceref, da_arg, x)
|
|
||||||
for x in self.member_type5_list
|
|
||||||
])
|
|
||||||
|
|
||||||
sa_args = self.ctx.build.type5_is_static_array(self.ret_type5)
|
|
||||||
if sa_args is not None:
|
|
||||||
sa_len, sa_typ = sa_args
|
|
||||||
if sa_len != len(self.member_type5_list):
|
|
||||||
return fail('Tuple element count mismatch')
|
|
||||||
|
|
||||||
return new_constraints([
|
|
||||||
UnifyTypesConstraint(self.ctx, self.sourceref, sa_typ, x)
|
|
||||||
for x in self.member_type5_list
|
|
||||||
])
|
|
||||||
|
|
||||||
tp_args = self.ctx.build.type5_is_tuple(self.ret_type5)
|
|
||||||
if tp_args is not None:
|
|
||||||
if len(tp_args) != len(self.member_type5_list):
|
|
||||||
return fail('Tuple element count mismatch')
|
|
||||||
|
|
||||||
return new_constraints([
|
|
||||||
UnifyTypesConstraint(self.ctx, self.sourceref, act_typ, exp_typ)
|
|
||||||
for act_typ, exp_typ in zip(tp_args, self.member_type5_list, strict=True)
|
|
||||||
])
|
|
||||||
|
|
||||||
raise NotImplementedError(self.ret_type5)
|
|
||||||
|
|
||||||
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
|
|
||||||
self.ret_type5 = replace_variable(self.ret_type5, var, typ)
|
|
||||||
self.member_type5_list = [
|
|
||||||
replace_variable(x, var, typ)
|
|
||||||
for x in self.member_type5_list
|
|
||||||
]
|
|
||||||
|
|
||||||
def complexity(self) -> int:
|
|
||||||
return 100 + complexity(self.ret_type5) + sum(complexity(x) for x in self.member_type5_list)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
args = ', '.join(self.ctx.build.type5_name(x) for x in self.member_type5_list)
|
|
||||||
return f'FromTuple {self.ctx.build.type5_name(self.ret_type5)} ~ ({args}, )'
|
|
||||||
|
|
||||||
class TypeClassInstanceExistsConstraint(ConstraintBase):
|
|
||||||
__slots__ = ('typeclass', 'arg_list', )
|
|
||||||
|
|
||||||
typeclass: str
|
|
||||||
arg_list: list[TypeExpr]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
ctx: Context,
|
|
||||||
sourceref: SourceRef,
|
|
||||||
typeclass: str,
|
|
||||||
arg_list: Sequence[TypeExpr]
|
|
||||||
) -> None:
|
|
||||||
super().__init__(ctx, sourceref)
|
|
||||||
|
|
||||||
self.typeclass = typeclass
|
|
||||||
self.arg_list = list(arg_list)
|
|
||||||
|
|
||||||
def check(self) -> CheckResult:
|
|
||||||
c_arg_list = [
|
|
||||||
x for x in self.arg_list if is_concrete(x)
|
|
||||||
]
|
|
||||||
if len(c_arg_list) != len(self.arg_list):
|
|
||||||
return skip_for_now()
|
|
||||||
|
|
||||||
if any(isinstance(x, Record) for x in c_arg_list):
|
|
||||||
# TODO: Allow users to implement type classes on their structs
|
|
||||||
return fail('Missing type class instance')
|
|
||||||
|
|
||||||
key = tuple(c_arg_list)
|
|
||||||
existing_instances = self.ctx.build.type_class_instances[self.typeclass]
|
|
||||||
if key in existing_instances:
|
|
||||||
return ok()
|
|
||||||
|
|
||||||
return fail('Missing type class instance')
|
|
||||||
|
|
||||||
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
|
|
||||||
self.arg_list = [
|
|
||||||
replace_variable(x, var, typ)
|
|
||||||
for x in self.arg_list
|
|
||||||
]
|
|
||||||
|
|
||||||
def complexity(self) -> int:
|
|
||||||
return 100 + sum(complexity(x) for x in self.arg_list)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
args = ' '.join(self.ctx.build.type5_name(x) for x in self.arg_list)
|
|
||||||
return f'Exists {self.typeclass} {args}'
|
|
||||||
|
|
||||||
def complexity(expr: TypeExpr) -> int:
|
|
||||||
if isinstance(expr, AtomicType | TypeLevelNat):
|
|
||||||
return 1
|
|
||||||
|
|
||||||
if isinstance(expr, TypeConstructor):
|
|
||||||
return 2
|
|
||||||
|
|
||||||
if isinstance(expr, TypeVariable):
|
|
||||||
return 5
|
|
||||||
|
|
||||||
if isinstance(expr, TypeApplication):
|
|
||||||
return complexity(expr.constructor) + complexity(expr.argument)
|
|
||||||
|
|
||||||
raise NotImplementedError(expr)
|
|
||||||
@ -1,290 +0,0 @@
|
|||||||
from typing import Any, Generator
|
|
||||||
|
|
||||||
from .. import ourlang
|
|
||||||
from ..typeclass import TypeClassConstraint
|
|
||||||
from .constrainedexpr import ConstrainedExpr, instantiate_constrained
|
|
||||||
from .constraints import (
|
|
||||||
CanAccessStructMemberConstraint,
|
|
||||||
CanBeSubscriptedConstraint,
|
|
||||||
ConstraintBase,
|
|
||||||
Context,
|
|
||||||
FromLiteralBytes,
|
|
||||||
FromLiteralFloat,
|
|
||||||
FromLiteralInteger,
|
|
||||||
FromTupleConstraint,
|
|
||||||
TypeClassInstanceExistsConstraint,
|
|
||||||
UnifyTypesConstraint,
|
|
||||||
)
|
|
||||||
from .kindexpr import KindExpr
|
|
||||||
from .typeexpr import TypeApplication, TypeExpr, TypeVariable, is_concrete
|
|
||||||
|
|
||||||
ConstraintGenerator = Generator[ConstraintBase, None, None]
|
|
||||||
|
|
||||||
|
|
||||||
def phasm_type5_generate_constraints(ctx: Context, inp: ourlang.Module[Any]) -> list[ConstraintBase]:
|
|
||||||
return [*module(ctx, inp)]
|
|
||||||
|
|
||||||
def expression_constant_primitive(ctx: Context, inp: ourlang.ConstantPrimitive, phft: TypeVariable) -> ConstraintGenerator:
|
|
||||||
if isinstance(inp.value, int):
|
|
||||||
yield FromLiteralInteger(ctx, inp.sourceref, phft, inp.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp.value, float):
|
|
||||||
yield FromLiteralFloat(ctx, inp.sourceref, phft, inp.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
raise NotImplementedError(inp.value)
|
|
||||||
|
|
||||||
def expression_constant_bytes(ctx: Context, inp: ourlang.ConstantBytes, phft: TypeVariable) -> ConstraintGenerator:
|
|
||||||
yield FromLiteralBytes(ctx, inp.sourceref, phft, inp.value)
|
|
||||||
|
|
||||||
def expression_constant_tuple(ctx: Context, inp: ourlang.ConstantTuple, phft: TypeVariable) -> ConstraintGenerator:
|
|
||||||
member_type5_list = [
|
|
||||||
ctx.make_placeholder(arg)
|
|
||||||
for arg in inp.value
|
|
||||||
]
|
|
||||||
|
|
||||||
for arg, arg_phft in zip(inp.value, member_type5_list):
|
|
||||||
yield from expression(ctx, arg, arg_phft)
|
|
||||||
|
|
||||||
yield FromTupleConstraint(ctx, inp.sourceref, phft, member_type5_list)
|
|
||||||
|
|
||||||
def expression_constant_struct(ctx: Context, inp: ourlang.ConstantStruct, phft: TypeVariable) -> ConstraintGenerator:
|
|
||||||
member_type5_list = [
|
|
||||||
ctx.make_placeholder(arg)
|
|
||||||
for arg in inp.value
|
|
||||||
]
|
|
||||||
|
|
||||||
for arg, arg_phft in zip(inp.value, member_type5_list):
|
|
||||||
yield from expression(ctx, arg, arg_phft)
|
|
||||||
|
|
||||||
lft = ctx.build.type5_make_function([x[1] for x in inp.struct_type5.fields] + [inp.struct_type5])
|
|
||||||
rgt = ctx.build.type5_make_function(member_type5_list + [phft])
|
|
||||||
|
|
||||||
yield UnifyTypesConstraint(ctx, inp.sourceref, lft, rgt)
|
|
||||||
|
|
||||||
def expression_constant_memory_stored(ctx: Context, inp: ourlang.ConstantMemoryStored, phft: TypeVariable) -> ConstraintGenerator:
|
|
||||||
if isinstance(inp, ourlang.ConstantBytes):
|
|
||||||
yield from expression_constant_bytes(ctx, inp, phft)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.ConstantTuple):
|
|
||||||
yield from expression_constant_tuple(ctx, inp, phft)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.ConstantStruct):
|
|
||||||
yield from expression_constant_struct(ctx, inp, phft)
|
|
||||||
return
|
|
||||||
|
|
||||||
raise NotImplementedError(inp)
|
|
||||||
|
|
||||||
def expression_constant(ctx: Context, inp: ourlang.Constant, phft: TypeVariable) -> ConstraintGenerator:
|
|
||||||
if isinstance(inp, ourlang.ConstantPrimitive):
|
|
||||||
yield from expression_constant_primitive(ctx, inp, phft)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.ConstantMemoryStored):
|
|
||||||
yield from expression_constant_memory_stored(ctx, inp, phft)
|
|
||||||
return
|
|
||||||
|
|
||||||
raise NotImplementedError(inp)
|
|
||||||
|
|
||||||
def expression_variable_reference(ctx: Context, inp: ourlang.VariableReference, phft: TypeVariable) -> ConstraintGenerator:
|
|
||||||
yield UnifyTypesConstraint(ctx, inp.sourceref, inp.variable.type5, phft, prefix=inp.variable.name)
|
|
||||||
|
|
||||||
def expression_binary_operator(ctx: Context, inp: ourlang.BinaryOp, phft: TypeVariable) -> ConstraintGenerator:
|
|
||||||
yield from _expression_binary_operator_or_function_call(
|
|
||||||
ctx,
|
|
||||||
inp.operator,
|
|
||||||
inp.polytype_substitutions,
|
|
||||||
[inp.left, inp.right],
|
|
||||||
inp.sourceref,
|
|
||||||
f'({inp.operator.name})',
|
|
||||||
phft,
|
|
||||||
)
|
|
||||||
|
|
||||||
def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: TypeVariable) -> ConstraintGenerator:
|
|
||||||
yield from _expression_binary_operator_or_function_call(
|
|
||||||
ctx,
|
|
||||||
inp.function,
|
|
||||||
inp.polytype_substitutions,
|
|
||||||
inp.arguments,
|
|
||||||
inp.sourceref,
|
|
||||||
inp.function.name,
|
|
||||||
phft,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _expression_binary_operator_or_function_call(
|
|
||||||
ctx: Context,
|
|
||||||
function: ourlang.Function | ourlang.FunctionParam,
|
|
||||||
polytype_substitutions: dict[TypeVariable, TypeExpr],
|
|
||||||
arguments: list[ourlang.Expression],
|
|
||||||
sourceref: ourlang.SourceRef,
|
|
||||||
function_name: str,
|
|
||||||
phft: TypeVariable,
|
|
||||||
) -> ConstraintGenerator:
|
|
||||||
arg_typ_list = []
|
|
||||||
for arg in arguments:
|
|
||||||
arg_tv = ctx.make_placeholder(arg)
|
|
||||||
yield from expression(ctx, arg, arg_tv)
|
|
||||||
arg_typ_list.append(arg_tv)
|
|
||||||
|
|
||||||
def make_placeholder(x: KindExpr, p: str) -> TypeVariable:
|
|
||||||
return ctx.make_placeholder(kind=x, prefix=p)
|
|
||||||
|
|
||||||
ftp5 = function.type5
|
|
||||||
assert ftp5 is not None
|
|
||||||
if isinstance(ftp5, ConstrainedExpr):
|
|
||||||
ftp5, phft_lookup = instantiate_constrained(ftp5, make_placeholder)
|
|
||||||
|
|
||||||
for orig_tvar, tvar in phft_lookup.items():
|
|
||||||
ctx.register_polytype_subsitutes(tvar, polytype_substitutions, orig_tvar)
|
|
||||||
|
|
||||||
for type_constraint in ftp5.constraints:
|
|
||||||
if isinstance(type_constraint, TypeClassConstraint):
|
|
||||||
yield TypeClassInstanceExistsConstraint(ctx, sourceref, type_constraint.cls.name, type_constraint.variables)
|
|
||||||
continue
|
|
||||||
|
|
||||||
raise NotImplementedError(type_constraint)
|
|
||||||
|
|
||||||
ftp5 = ftp5.expr
|
|
||||||
else:
|
|
||||||
assert is_concrete(ftp5)
|
|
||||||
|
|
||||||
expr_type = ctx.build.type5_make_function(arg_typ_list + [phft])
|
|
||||||
|
|
||||||
yield UnifyTypesConstraint(ctx, sourceref, ftp5, expr_type, prefix=function_name)
|
|
||||||
|
|
||||||
def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: TypeVariable) -> ConstraintGenerator:
|
|
||||||
assert inp.function.type5 is not None # Todo: Make not nullable
|
|
||||||
|
|
||||||
ftp5 = inp.function.type5
|
|
||||||
if isinstance(ftp5, ConstrainedExpr):
|
|
||||||
ftp5 = ftp5.expr
|
|
||||||
|
|
||||||
yield UnifyTypesConstraint(ctx, inp.sourceref, ftp5, phft, prefix=inp.function.name)
|
|
||||||
|
|
||||||
def expression_tuple_instantiation(ctx: Context, inp: ourlang.TupleInstantiation, phft: TypeVariable) -> ConstraintGenerator:
|
|
||||||
arg_typ_list = []
|
|
||||||
for arg in inp.elements:
|
|
||||||
arg_tv = ctx.make_placeholder(arg)
|
|
||||||
yield from expression(ctx, arg, arg_tv)
|
|
||||||
arg_typ_list.append(arg_tv)
|
|
||||||
|
|
||||||
yield FromTupleConstraint(ctx, inp.sourceref, phft, arg_typ_list)
|
|
||||||
|
|
||||||
def expression_subscript(ctx: Context, inp: ourlang.Subscript, phft: TypeVariable) -> ConstraintGenerator:
|
|
||||||
varref_phft = ctx.make_placeholder(inp.varref)
|
|
||||||
index_phft = ctx.make_placeholder(inp.index)
|
|
||||||
|
|
||||||
yield from expression(ctx, inp.varref, varref_phft)
|
|
||||||
yield from expression(ctx, inp.index, index_phft)
|
|
||||||
|
|
||||||
if isinstance(inp.index, ourlang.ConstantPrimitive) and isinstance(inp.index.value, int):
|
|
||||||
yield CanBeSubscriptedConstraint(ctx, inp.sourceref, phft, varref_phft, index_phft, inp.index.value)
|
|
||||||
else:
|
|
||||||
yield CanBeSubscriptedConstraint(ctx, inp.sourceref, phft, varref_phft, index_phft, None)
|
|
||||||
|
|
||||||
def expression_access_struct_member(ctx: Context, inp: ourlang.AccessStructMember, phft: TypeVariable) -> ConstraintGenerator:
|
|
||||||
varref_phft = ctx.make_placeholder(inp.varref)
|
|
||||||
|
|
||||||
yield from expression_variable_reference(ctx, inp.varref, varref_phft)
|
|
||||||
|
|
||||||
yield CanAccessStructMemberConstraint(ctx, inp.sourceref, phft, varref_phft, inp.member)
|
|
||||||
|
|
||||||
def expression(ctx: Context, inp: ourlang.Expression, phft: TypeVariable) -> ConstraintGenerator:
|
|
||||||
if isinstance(inp, ourlang.Constant):
|
|
||||||
yield from expression_constant(ctx, inp, phft)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.VariableReference):
|
|
||||||
yield from expression_variable_reference(ctx, inp, phft)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.BinaryOp):
|
|
||||||
yield from expression_binary_operator(ctx, inp, phft)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.FunctionCall):
|
|
||||||
yield from expression_function_call(ctx, inp, phft)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.FunctionReference):
|
|
||||||
yield from expression_function_reference(ctx, inp, phft)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.TupleInstantiation):
|
|
||||||
yield from expression_tuple_instantiation(ctx, inp, phft)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.Subscript):
|
|
||||||
yield from expression_subscript(ctx, inp, phft)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.AccessStructMember):
|
|
||||||
yield from expression_access_struct_member(ctx, inp, phft)
|
|
||||||
return
|
|
||||||
|
|
||||||
raise NotImplementedError(inp)
|
|
||||||
|
|
||||||
def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementReturn) -> ConstraintGenerator:
|
|
||||||
phft = ctx.make_placeholder(inp.value)
|
|
||||||
|
|
||||||
if fun.type5 is None:
|
|
||||||
raise NotImplementedError("Deducing function type - you'll have to annotate it.")
|
|
||||||
|
|
||||||
if isinstance(fun.type5, TypeApplication):
|
|
||||||
args = ctx.build.type5_is_function(fun.type5)
|
|
||||||
assert args is not None
|
|
||||||
type5 = args[-1]
|
|
||||||
else:
|
|
||||||
type5 = fun.type5.expr if isinstance(fun.type5, ConstrainedExpr) else fun.type5
|
|
||||||
|
|
||||||
yield from expression(ctx, inp.value, phft)
|
|
||||||
yield UnifyTypesConstraint(ctx, inp.sourceref, type5, phft, prefix=f'{fun.name}(...)')
|
|
||||||
|
|
||||||
def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator:
|
|
||||||
test_phft = ctx.make_placeholder(inp.test)
|
|
||||||
|
|
||||||
yield from expression(ctx, inp.test, test_phft)
|
|
||||||
|
|
||||||
yield UnifyTypesConstraint(ctx, inp.test.sourceref, test_phft, ctx.build.bool_type5)
|
|
||||||
|
|
||||||
for stmt in inp.statements:
|
|
||||||
yield from statement(ctx, fun, stmt)
|
|
||||||
|
|
||||||
for stmt in inp.else_statements:
|
|
||||||
yield from statement(ctx, fun, stmt)
|
|
||||||
|
|
||||||
def statement(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement) -> ConstraintGenerator:
|
|
||||||
if isinstance(inp, ourlang.StatementReturn):
|
|
||||||
yield from statement_return(ctx, fun, inp)
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(inp, ourlang.StatementIf):
|
|
||||||
yield from statement_if(ctx, fun, inp)
|
|
||||||
return
|
|
||||||
|
|
||||||
raise NotImplementedError(inp)
|
|
||||||
|
|
||||||
def function(ctx: Context, inp: ourlang.Function) -> ConstraintGenerator:
|
|
||||||
for stmt in inp.statements:
|
|
||||||
yield from statement(ctx, inp, stmt)
|
|
||||||
|
|
||||||
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> ConstraintGenerator:
|
|
||||||
phft = ctx.make_placeholder(inp.constant)
|
|
||||||
|
|
||||||
yield from expression_constant(ctx, inp.constant, phft)
|
|
||||||
yield UnifyTypesConstraint(ctx, inp.sourceref, inp.type5, phft)
|
|
||||||
|
|
||||||
def module(ctx: Context, inp: ourlang.Module[Any]) -> ConstraintGenerator:
|
|
||||||
for cdef in inp.constant_defs.values():
|
|
||||||
yield from module_constant_def(ctx, cdef)
|
|
||||||
|
|
||||||
for func in inp.functions.values():
|
|
||||||
if func.imported:
|
|
||||||
continue
|
|
||||||
|
|
||||||
yield from function(ctx, func)
|
|
||||||
|
|
||||||
# TODO: Generalize?
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import TypeAlias
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Star:
|
|
||||||
def __rshift__(self, other: KindExpr) -> Arrow:
|
|
||||||
return Arrow(self, other)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return "*"
|
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
|
||||||
return 0 # All Stars are the same
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Nat:
|
|
||||||
def __rshift__(self, other: KindExpr) -> Arrow:
|
|
||||||
return Arrow(self, other)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return "Nat"
|
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
|
||||||
return 0 # All Stars are the same
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Arrow:
|
|
||||||
"""
|
|
||||||
Represents an arrow kind `K1 -> K2`.
|
|
||||||
|
|
||||||
To create K1 -> K2 -> K3, pass an Arrow for result_kind.
|
|
||||||
For now, we do not support Arrows as arguments (i.e.,
|
|
||||||
no higher order kinds).
|
|
||||||
"""
|
|
||||||
arg_kind: Star | Nat
|
|
||||||
result_kind: KindExpr
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
if isinstance(self.arg_kind, Star):
|
|
||||||
arg_kind = "*"
|
|
||||||
else:
|
|
||||||
arg_kind = f"({str(self.arg_kind)})"
|
|
||||||
|
|
||||||
if isinstance(self.result_kind, Star):
|
|
||||||
result_kind = "*"
|
|
||||||
else:
|
|
||||||
result_kind = f"({str(self.result_kind)})"
|
|
||||||
|
|
||||||
return f"{arg_kind} -> {result_kind}"
|
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
|
||||||
return hash((self.arg_kind, self.result_kind, ))
|
|
||||||
|
|
||||||
KindExpr: TypeAlias = Star | Nat | Arrow
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
from .kindexpr import Star
|
|
||||||
from .typeexpr import AtomicType, TypeApplication, is_concrete
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Record(AtomicType):
|
|
||||||
"""
|
|
||||||
Records are a fundamental type. But we need to store some extra info.
|
|
||||||
"""
|
|
||||||
fields: tuple[tuple[str, AtomicType | TypeApplication], ...]
|
|
||||||
|
|
||||||
def __init__(self, name: str, fields: tuple[tuple[str, AtomicType | TypeApplication], ...]) -> None:
|
|
||||||
for field_name, field_type in fields:
|
|
||||||
if field_type.kind != Star():
|
|
||||||
raise TypeError(f"Record fields must not be constructors ({field_name} :: {field_type})")
|
|
||||||
|
|
||||||
if not is_concrete(field_type):
|
|
||||||
raise TypeError("Record field types must be concrete types ({field_name} :: {field_type})")
|
|
||||||
|
|
||||||
super().__init__(name)
|
|
||||||
self.fields = fields
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
args = ", ".join(
|
|
||||||
f"{field_name} :: {field_type}"
|
|
||||||
for field_name, field_type in self.fields
|
|
||||||
)
|
|
||||||
return f"{self.name} {{{args}}} :: {self.kind}"
|
|
||||||
|
|
||||||
# @dataclass
|
|
||||||
# class RecordConstructor(TypeConstructor):
|
|
||||||
# """
|
|
||||||
# TODO.
|
|
||||||
|
|
||||||
# i.e.:
|
|
||||||
# ```
|
|
||||||
# class Foo[T, R]:
|
|
||||||
# lft: T
|
|
||||||
# rgt: R
|
|
||||||
# """
|
|
||||||
# name: str
|
|
||||||
@ -1,122 +0,0 @@
|
|||||||
from typing import Any
|
|
||||||
|
|
||||||
from ..ourlang import Module
|
|
||||||
from .constraints import ConstraintBase, ConstraintList, Context, Failure, ReplaceVariable, SkipForNow, Success
|
|
||||||
from .fromast import phasm_type5_generate_constraints
|
|
||||||
from .typeexpr import TypeExpr, TypeVariable, is_concrete, replace_variable
|
|
||||||
|
|
||||||
MAX_RESTACK_COUNT = 100
|
|
||||||
|
|
||||||
class Type5SolverException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def phasm_type5(inp: Module[Any], verbose: bool = False) -> None:
|
|
||||||
ctx = Context(inp.build)
|
|
||||||
|
|
||||||
constraint_list = phasm_type5_generate_constraints(ctx, inp)
|
|
||||||
assert constraint_list
|
|
||||||
|
|
||||||
placeholder_types: dict[TypeVariable, TypeExpr] = {}
|
|
||||||
|
|
||||||
error_list: list[tuple[str, str, str]] = []
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print('Constraints')
|
|
||||||
|
|
||||||
for _ in range(MAX_RESTACK_COUNT):
|
|
||||||
if verbose:
|
|
||||||
for constraint in constraint_list:
|
|
||||||
print(f"{constraint.sourceref!s} {constraint!s}")
|
|
||||||
print("Validating")
|
|
||||||
|
|
||||||
new_constraint_list: list[ConstraintBase] = []
|
|
||||||
|
|
||||||
# Iterate using a while and pop since on ReplaceVariable
|
|
||||||
# we want to iterate over the list as well, and since on
|
|
||||||
# ConstraintList we want to treat those first.
|
|
||||||
remaining_constraint_list = sorted(constraint_list, key=lambda x: x.complexity())
|
|
||||||
while remaining_constraint_list:
|
|
||||||
constraint = remaining_constraint_list.pop(0)
|
|
||||||
result = constraint.check()
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print(f"{constraint.sourceref!s} {constraint!s}")
|
|
||||||
print(f"{constraint.sourceref!s} => {result.to_str(inp.build.type5_name)}")
|
|
||||||
|
|
||||||
match result:
|
|
||||||
case Success():
|
|
||||||
# This constraint was valid
|
|
||||||
continue
|
|
||||||
case SkipForNow():
|
|
||||||
# We have to check later
|
|
||||||
new_constraint_list.append(constraint)
|
|
||||||
continue
|
|
||||||
case ConstraintList(items):
|
|
||||||
# This constraint was valid, but we have new once
|
|
||||||
# Do this as the first next items, so when users are reading the
|
|
||||||
# solver output they don't need to context switch.
|
|
||||||
remaining_constraint_list = items + remaining_constraint_list
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
for new_const in items:
|
|
||||||
print(f"{constraint.sourceref!s} => + {new_const!s}")
|
|
||||||
|
|
||||||
continue
|
|
||||||
case Failure(msg):
|
|
||||||
error_list.append((str(constraint.sourceref), str(constraint), msg, ))
|
|
||||||
continue
|
|
||||||
case ReplaceVariable(action_var, action_typ):
|
|
||||||
assert action_var not in placeholder_types # When does this happen?
|
|
||||||
assert not isinstance(action_typ, TypeVariable) or action_typ not in placeholder_types # When does this happen?
|
|
||||||
assert action_var != action_typ # When does this happen?
|
|
||||||
|
|
||||||
# Ensure all existing found types are updated
|
|
||||||
# if they have this variable somewhere inside them.
|
|
||||||
placeholder_types = {
|
|
||||||
k: replace_variable(v, action_var, action_typ)
|
|
||||||
for k, v in placeholder_types.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add the new variable to the registry
|
|
||||||
placeholder_types[action_var] = action_typ
|
|
||||||
|
|
||||||
# Also update all constraints that may refer to this variable
|
|
||||||
# that they now have more detailed information.
|
|
||||||
for oth_const in new_constraint_list + remaining_constraint_list:
|
|
||||||
old_str = str(oth_const)
|
|
||||||
oth_const.replace_variable(action_var, action_typ)
|
|
||||||
new_str = str(oth_const)
|
|
||||||
|
|
||||||
if verbose and old_str != new_str:
|
|
||||||
print(f"{oth_const.sourceref!s} => - {old_str!s}")
|
|
||||||
print(f"{oth_const.sourceref!s} => + {new_str!s}")
|
|
||||||
|
|
||||||
continue
|
|
||||||
|
|
||||||
if error_list:
|
|
||||||
raise Type5SolverException(error_list)
|
|
||||||
|
|
||||||
if not new_constraint_list:
|
|
||||||
break
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print()
|
|
||||||
print('New round')
|
|
||||||
|
|
||||||
constraint_list = new_constraint_list
|
|
||||||
|
|
||||||
if new_constraint_list:
|
|
||||||
raise Type5SolverException('Was unable to complete typing this program')
|
|
||||||
|
|
||||||
for placeholder, expression in ctx.placeholder_update.items():
|
|
||||||
if expression is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
resolved_type5 = placeholder_types[placeholder]
|
|
||||||
assert is_concrete(resolved_type5) # When does this happen?
|
|
||||||
expression.type5 = resolved_type5
|
|
||||||
|
|
||||||
for placeholder, (ptst_map, orig_tvar) in ctx.ptst_update.items():
|
|
||||||
resolved_type5 = placeholder_types[placeholder]
|
|
||||||
assert is_concrete(resolved_type5) # When does this happen?
|
|
||||||
ptst_map[orig_tvar] = resolved_type5
|
|
||||||
@ -1,201 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
from .kindexpr import Arrow, KindExpr, Nat, Star
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TypeExpr:
|
|
||||||
kind: KindExpr
|
|
||||||
name: str
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f"{self.name} :: {self.kind}"
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class AtomicType(TypeExpr):
|
|
||||||
def __init__(self, name: str) -> None:
|
|
||||||
super().__init__(Star(), name)
|
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
|
||||||
return hash((self.kind, self.name))
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TypeLevelNat(TypeExpr):
|
|
||||||
value: int
|
|
||||||
|
|
||||||
def __init__(self, nat: int) -> None:
|
|
||||||
assert 0 <= nat
|
|
||||||
|
|
||||||
super().__init__(Nat(), str(nat))
|
|
||||||
|
|
||||||
self.value = nat
|
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
|
||||||
return hash((self.kind, self.name, self.value))
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TypeVariable(TypeExpr):
|
|
||||||
"""
|
|
||||||
A placeholder in a type expression
|
|
||||||
"""
|
|
||||||
def __hash__(self) -> int:
|
|
||||||
return hash((self.kind, self.name))
|
|
||||||
|
|
||||||
def __lt__(self, other: object) -> bool:
|
|
||||||
if not isinstance(other, TypeVariable):
|
|
||||||
raise TypeError
|
|
||||||
|
|
||||||
return self.name < other.name
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TypeConstructor(TypeExpr):
|
|
||||||
def __init__(self, kind: Arrow, name: str) -> None:
|
|
||||||
super().__init__(kind, name)
|
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
|
||||||
return hash((self.kind, self.name))
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TypeApplication(TypeExpr):
|
|
||||||
constructor: TypeConstructor | TypeApplication | TypeVariable
|
|
||||||
argument: TypeExpr
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
constructor: TypeConstructor | TypeApplication | TypeVariable,
|
|
||||||
argument: TypeExpr,
|
|
||||||
) -> None:
|
|
||||||
if isinstance(constructor.kind, Star):
|
|
||||||
raise TypeError("A constructor cannot be a concrete type")
|
|
||||||
|
|
||||||
if isinstance(constructor.kind, Nat):
|
|
||||||
raise TypeError("A constructor cannot be a number")
|
|
||||||
|
|
||||||
if constructor.kind.arg_kind != argument.kind:
|
|
||||||
raise TypeError("Argument does match construtor's expectations")
|
|
||||||
|
|
||||||
super().__init__(
|
|
||||||
constructor.kind.result_kind,
|
|
||||||
f"{constructor.name} ({argument.name})",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.constructor = constructor
|
|
||||||
self.argument = argument
|
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
|
||||||
return hash((self.kind, self.name, self.constructor, self.argument))
|
|
||||||
|
|
||||||
def occurs(lft: TypeVariable, rgt: TypeApplication) -> bool:
|
|
||||||
"""
|
|
||||||
Checks whether the given variable occurs in the given application.
|
|
||||||
"""
|
|
||||||
if lft == rgt.constructor:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if lft == rgt.argument:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if isinstance(rgt.argument, TypeApplication):
|
|
||||||
return occurs(lft, rgt.argument)
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_concrete(lft: TypeExpr) -> bool:
|
|
||||||
"""
|
|
||||||
A concrete type has no variables in it.
|
|
||||||
|
|
||||||
This is also known as a monomorphic type or a closed type.
|
|
||||||
TODO: I don't know the differen between them yet.
|
|
||||||
"""
|
|
||||||
if isinstance(lft, AtomicType):
|
|
||||||
return True
|
|
||||||
|
|
||||||
if isinstance(lft, TypeLevelNat):
|
|
||||||
return True
|
|
||||||
|
|
||||||
if isinstance(lft, TypeVariable):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if isinstance(lft, TypeConstructor):
|
|
||||||
return True
|
|
||||||
|
|
||||||
if isinstance(lft, TypeApplication):
|
|
||||||
return is_concrete(lft.constructor) and is_concrete(lft.argument)
|
|
||||||
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def is_polymorphic(lft: TypeExpr) -> bool:
|
|
||||||
"""
|
|
||||||
A polymorphic type has one or more variables in it.
|
|
||||||
"""
|
|
||||||
return not is_concrete(lft)
|
|
||||||
|
|
||||||
def replace_variable(expr: TypeExpr, var: TypeVariable, rep_expr: TypeExpr) -> TypeExpr:
|
|
||||||
assert var.kind == rep_expr.kind, (var, rep_expr, )
|
|
||||||
|
|
||||||
if isinstance(expr, AtomicType):
|
|
||||||
# Nothing to replace
|
|
||||||
return expr
|
|
||||||
|
|
||||||
if isinstance(expr, TypeLevelNat):
|
|
||||||
# Nothing to replace
|
|
||||||
return expr
|
|
||||||
|
|
||||||
if isinstance(expr, TypeVariable):
|
|
||||||
if expr == var:
|
|
||||||
return rep_expr
|
|
||||||
return expr
|
|
||||||
|
|
||||||
if isinstance(expr, TypeConstructor):
|
|
||||||
# Nothing to replace
|
|
||||||
return expr
|
|
||||||
|
|
||||||
if isinstance(expr, TypeApplication):
|
|
||||||
new_constructor = replace_variable(expr.constructor, var, rep_expr)
|
|
||||||
|
|
||||||
assert isinstance(new_constructor, TypeConstructor | TypeApplication | TypeVariable) # type hint
|
|
||||||
|
|
||||||
return TypeApplication(
|
|
||||||
constructor=new_constructor,
|
|
||||||
argument=replace_variable(expr.argument, var, rep_expr),
|
|
||||||
)
|
|
||||||
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def instantiate(
|
|
||||||
expr: TypeExpr,
|
|
||||||
known_map: dict[TypeVariable, TypeVariable],
|
|
||||||
) -> TypeExpr:
|
|
||||||
"""
|
|
||||||
Make a copy of all variables.
|
|
||||||
|
|
||||||
This is need when you use a polymorphic function in two places. In the
|
|
||||||
one place, `t a` may refer to `u32[...]` and in another to `f32[4]`.
|
|
||||||
These should not be unified since they refer to a different monomorphic
|
|
||||||
version of the function.
|
|
||||||
"""
|
|
||||||
if isinstance(expr, AtomicType):
|
|
||||||
return expr
|
|
||||||
|
|
||||||
if isinstance(expr, TypeLevelNat):
|
|
||||||
return expr
|
|
||||||
|
|
||||||
if isinstance(expr, TypeVariable):
|
|
||||||
return known_map[expr]
|
|
||||||
|
|
||||||
if isinstance(expr, TypeConstructor):
|
|
||||||
return expr
|
|
||||||
|
|
||||||
if isinstance(expr, TypeApplication):
|
|
||||||
new_constructor = instantiate(expr.constructor, known_map)
|
|
||||||
|
|
||||||
assert isinstance(new_constructor, TypeConstructor | TypeApplication | TypeVariable) # type hint
|
|
||||||
|
|
||||||
return TypeApplication(
|
|
||||||
constructor=new_constructor,
|
|
||||||
argument=instantiate(expr.argument, known_map),
|
|
||||||
)
|
|
||||||
|
|
||||||
raise NotImplementedError(expr)
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
from .record import Record
|
|
||||||
from .typeexpr import (
|
|
||||||
AtomicType,
|
|
||||||
TypeApplication,
|
|
||||||
TypeConstructor,
|
|
||||||
TypeExpr,
|
|
||||||
TypeLevelNat,
|
|
||||||
TypeVariable,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TypeRouter[T]:
|
|
||||||
def when_atomic(self, typ: AtomicType) -> T:
|
|
||||||
raise NotImplementedError(typ)
|
|
||||||
|
|
||||||
def when_application(self, typ: TypeApplication) -> T:
|
|
||||||
raise NotImplementedError(typ)
|
|
||||||
|
|
||||||
def when_constructor(self, typ: TypeConstructor) -> T:
|
|
||||||
raise NotImplementedError(typ)
|
|
||||||
|
|
||||||
def when_record(self, typ: Record) -> T:
|
|
||||||
raise NotImplementedError(typ)
|
|
||||||
|
|
||||||
def when_type_level_nat(self, typ: TypeLevelNat) -> T:
|
|
||||||
raise NotImplementedError(typ)
|
|
||||||
|
|
||||||
def when_variable(self, typ: TypeVariable) -> T:
|
|
||||||
raise NotImplementedError(typ)
|
|
||||||
|
|
||||||
def __call__(self, typ: TypeExpr) -> T:
|
|
||||||
if isinstance(typ, AtomicType):
|
|
||||||
if isinstance(typ, Record):
|
|
||||||
return self.when_record(typ)
|
|
||||||
|
|
||||||
return self.when_atomic(typ)
|
|
||||||
|
|
||||||
if isinstance(typ, TypeApplication):
|
|
||||||
return self.when_application(typ)
|
|
||||||
|
|
||||||
if isinstance(typ, TypeConstructor):
|
|
||||||
return self.when_constructor(typ)
|
|
||||||
|
|
||||||
if isinstance(typ, TypeLevelNat):
|
|
||||||
return self.when_type_level_nat(typ)
|
|
||||||
|
|
||||||
if isinstance(typ, TypeVariable):
|
|
||||||
return self.when_variable(typ)
|
|
||||||
|
|
||||||
raise NotImplementedError(typ)
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import dataclasses
|
|
||||||
from typing import Iterable
|
|
||||||
|
|
||||||
from ..type5.constrainedexpr import ConstrainedExpr, TypeConstraint
|
|
||||||
from ..type5.typeexpr import TypeExpr, TypeVariable, instantiate
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
|
||||||
class TypeClass:
|
|
||||||
name: str
|
|
||||||
variables: tuple[TypeVariable, ...]
|
|
||||||
methods: dict[str, TypeExpr | ConstrainedExpr] = dataclasses.field(default_factory=dict)
|
|
||||||
operators: dict[str, TypeExpr | ConstrainedExpr] = dataclasses.field(default_factory=dict)
|
|
||||||
|
|
||||||
class TypeClassConstraint(TypeConstraint):
|
|
||||||
__slots__ = ('cls', 'variables', )
|
|
||||||
|
|
||||||
def __init__(self, cls: TypeClass, variables: Iterable[TypeVariable]) -> None:
|
|
||||||
self.cls = cls
|
|
||||||
self.variables = tuple(variables)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
vrs = ' '.join(x.name for x in self.variables)
|
|
||||||
|
|
||||||
return f'{self.cls.name} {vrs}'
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
vrs = ', '.join(str(x) for x in self.variables)
|
|
||||||
|
|
||||||
return f'TypeClassConstraint({self.cls.name}, [{vrs}])'
|
|
||||||
|
|
||||||
def instantiate(self, known_map: dict[TypeVariable, TypeVariable]) -> TypeClassConstraint:
|
|
||||||
return TypeClassConstraint(
|
|
||||||
self.cls,
|
|
||||||
[
|
|
||||||
# instantiate returns a TypeVariable if you give it a TypeVariable,
|
|
||||||
# but I can't seem to convince mypy of that.
|
|
||||||
instantiate(var, known_map) # type: ignore
|
|
||||||
for var in self.variables
|
|
||||||
]
|
|
||||||
)
|
|
||||||
@ -5,7 +5,6 @@ and being able to conver it to Web Assembly Text Format
|
|||||||
|
|
||||||
from typing import Iterable, List, Optional, Tuple
|
from typing import Iterable, List, Optional, Tuple
|
||||||
|
|
||||||
|
|
||||||
class WatSerializable:
|
class WatSerializable:
|
||||||
"""
|
"""
|
||||||
Mixin for clases that can be serialized as WebAssembly Text
|
Mixin for clases that can be serialized as WebAssembly Text
|
||||||
@ -105,17 +104,11 @@ class Import(WatSerializable):
|
|||||||
else f' (result {self.result.to_wat()})'
|
else f' (result {self.result.to_wat()})'
|
||||||
)
|
)
|
||||||
|
|
||||||
class StatementBase(WatSerializable):
|
class Statement(WatSerializable):
|
||||||
pass
|
|
||||||
|
|
||||||
class Statement(StatementBase ):
|
|
||||||
"""
|
"""
|
||||||
Represents a Web Assembly statement
|
Represents a Web Assembly statement
|
||||||
"""
|
"""
|
||||||
def __init__(self, name: str, *args: str, comment: Optional[str] = None):
|
def __init__(self, name: str, *args: str, comment: Optional[str] = None):
|
||||||
assert ' ' not in name, 'Please pass argument separately'
|
|
||||||
assert name != 'call', 'Please use StatementCall'
|
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.args = args
|
self.args = args
|
||||||
self.comment = comment
|
self.comment = comment
|
||||||
@ -126,16 +119,6 @@ class Statement(StatementBase ):
|
|||||||
|
|
||||||
return f'{self.name} {args}{comment}'
|
return f'{self.name} {args}{comment}'
|
||||||
|
|
||||||
class StatementCall(StatementBase ):
|
|
||||||
def __init__(self, func_name: str, comment: str | None = None):
|
|
||||||
self.func_name = func_name
|
|
||||||
self.comment = comment
|
|
||||||
|
|
||||||
def to_wat(self) -> str:
|
|
||||||
comment = f' ;; {self.comment}' if self.comment else ''
|
|
||||||
|
|
||||||
return f'call ${self.func_name} {comment}'
|
|
||||||
|
|
||||||
class Function(WatSerializable):
|
class Function(WatSerializable):
|
||||||
"""
|
"""
|
||||||
Represents a Web Assembly function
|
Represents a Web Assembly function
|
||||||
@ -147,7 +130,7 @@ class Function(WatSerializable):
|
|||||||
params: Iterable[Param],
|
params: Iterable[Param],
|
||||||
locals_: Iterable[Param],
|
locals_: Iterable[Param],
|
||||||
result: WasmType,
|
result: WasmType,
|
||||||
statements: Iterable[StatementBase],
|
statements: Iterable[Statement],
|
||||||
) -> None:
|
) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.exported_name = exported_name
|
self.exported_name = exported_name
|
||||||
@ -202,7 +185,6 @@ class Module(WatSerializable):
|
|||||||
"""
|
"""
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.imports: List[Import] = []
|
self.imports: List[Import] = []
|
||||||
self.table: dict[int, str] = {}
|
|
||||||
self.functions: List[Function] = []
|
self.functions: List[Function] = []
|
||||||
self.memory = ModuleMemory()
|
self.memory = ModuleMemory()
|
||||||
|
|
||||||
@ -210,10 +192,8 @@ class Module(WatSerializable):
|
|||||||
"""
|
"""
|
||||||
Generates the text version
|
Generates the text version
|
||||||
"""
|
"""
|
||||||
return '(module\n {}\n {}\n {}\n {}\n {})\n'.format(
|
return '(module\n {}\n {}\n {})\n'.format(
|
||||||
'\n '.join(x.to_wat() for x in self.imports),
|
'\n '.join(x.to_wat() for x in self.imports),
|
||||||
f'(table {len(self.table)} funcref)',
|
|
||||||
'\n '.join(f'(elem (i32.const {k}) ${v})' for k, v in self.table.items()),
|
|
||||||
self.memory.to_wat(),
|
self.memory.to_wat(),
|
||||||
'\n '.join(x.to_wat() for x in self.functions),
|
'\n '.join(x.to_wat() for x in self.functions),
|
||||||
)
|
)
|
||||||
|
|||||||
69
phasm/wasmeasy.py
Normal file
69
phasm/wasmeasy.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
"""
|
||||||
|
Helper functions to quickly generate WASM code
|
||||||
|
"""
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from . import wasm
|
||||||
|
|
||||||
|
#pylint: disable=C0103,C0115,C0116,R0201,R0902
|
||||||
|
|
||||||
|
class Prefix_inn_fnn:
|
||||||
|
def __init__(self, prefix: str) -> None:
|
||||||
|
self.prefix = prefix
|
||||||
|
|
||||||
|
# 6.5.5. Memory Instructions
|
||||||
|
self.load = functools.partial(wasm.Statement, f'{self.prefix}.load')
|
||||||
|
self.store = functools.partial(wasm.Statement, f'{self.prefix}.store')
|
||||||
|
|
||||||
|
# 6.5.6. Numeric Instructions
|
||||||
|
self.clz = functools.partial(wasm.Statement, f'{self.prefix}.clz')
|
||||||
|
self.ctz = functools.partial(wasm.Statement, f'{self.prefix}.ctz')
|
||||||
|
self.popcnt = functools.partial(wasm.Statement, f'{self.prefix}.popcnt')
|
||||||
|
self.add = functools.partial(wasm.Statement, f'{self.prefix}.add')
|
||||||
|
self.sub = functools.partial(wasm.Statement, f'{self.prefix}.sub')
|
||||||
|
self.mul = functools.partial(wasm.Statement, f'{self.prefix}.mul')
|
||||||
|
self.div_s = functools.partial(wasm.Statement, f'{self.prefix}.div_s')
|
||||||
|
self.div_u = functools.partial(wasm.Statement, f'{self.prefix}.div_u')
|
||||||
|
self.rem_s = functools.partial(wasm.Statement, f'{self.prefix}.rem_s')
|
||||||
|
self.rem_u = functools.partial(wasm.Statement, f'{self.prefix}.rem_u')
|
||||||
|
self.and_ = functools.partial(wasm.Statement, f'{self.prefix}.and')
|
||||||
|
self.or_ = functools.partial(wasm.Statement, f'{self.prefix}.or')
|
||||||
|
self.xor = functools.partial(wasm.Statement, f'{self.prefix}.xor')
|
||||||
|
self.shl = functools.partial(wasm.Statement, f'{self.prefix}.shl')
|
||||||
|
self.shr_s = functools.partial(wasm.Statement, f'{self.prefix}.shr_s')
|
||||||
|
self.shr_u = functools.partial(wasm.Statement, f'{self.prefix}.shr_u')
|
||||||
|
self.rotl = functools.partial(wasm.Statement, f'{self.prefix}.rotl')
|
||||||
|
self.rotr = functools.partial(wasm.Statement, f'{self.prefix}.rotr')
|
||||||
|
|
||||||
|
self.eqz = functools.partial(wasm.Statement, f'{self.prefix}.eqz')
|
||||||
|
self.eq = functools.partial(wasm.Statement, f'{self.prefix}.eq')
|
||||||
|
self.ne = functools.partial(wasm.Statement, f'{self.prefix}.ne')
|
||||||
|
self.lt_s = functools.partial(wasm.Statement, f'{self.prefix}.lt_s')
|
||||||
|
self.lt_u = functools.partial(wasm.Statement, f'{self.prefix}.lt_u')
|
||||||
|
self.gt_s = functools.partial(wasm.Statement, f'{self.prefix}.gt_s')
|
||||||
|
self.gt_u = functools.partial(wasm.Statement, f'{self.prefix}.gt_u')
|
||||||
|
self.le_s = functools.partial(wasm.Statement, f'{self.prefix}.le_s')
|
||||||
|
self.le_u = functools.partial(wasm.Statement, f'{self.prefix}.le_u')
|
||||||
|
self.ge_s = functools.partial(wasm.Statement, f'{self.prefix}.ge_s')
|
||||||
|
self.ge_u = functools.partial(wasm.Statement, f'{self.prefix}.ge_u')
|
||||||
|
|
||||||
|
def const(self, value: int, comment: Optional[str] = None) -> wasm.Statement:
|
||||||
|
return wasm.Statement(f'{self.prefix}.const', f'0x{value:08x}', comment=comment)
|
||||||
|
|
||||||
|
i32 = Prefix_inn_fnn('i32')
|
||||||
|
i64 = Prefix_inn_fnn('i64')
|
||||||
|
|
||||||
|
class Block:
|
||||||
|
def __init__(self, start: str) -> None:
|
||||||
|
self.start = start
|
||||||
|
|
||||||
|
def __call__(self, *statements: wasm.Statement) -> List[wasm.Statement]:
|
||||||
|
return [
|
||||||
|
wasm.Statement('if'),
|
||||||
|
*statements,
|
||||||
|
wasm.Statement('end'),
|
||||||
|
]
|
||||||
|
|
||||||
|
if_ = Block('if')
|
||||||
@ -1,8 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
Helper functions to generate WASM code by writing Python functions
|
Helper functions to generate WASM code by writing Python functions
|
||||||
"""
|
"""
|
||||||
|
from typing import Any, Callable, Dict, List, Optional, Type
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Type
|
|
||||||
|
|
||||||
from . import wasm
|
from . import wasm
|
||||||
|
|
||||||
@ -21,15 +22,6 @@ class VarType_u8(VarType_Base):
|
|||||||
class VarType_i32(VarType_Base):
|
class VarType_i32(VarType_Base):
|
||||||
wasm_type = wasm.WasmTypeInt32
|
wasm_type = wasm.WasmTypeInt32
|
||||||
|
|
||||||
class VarType_i64(VarType_Base):
|
|
||||||
wasm_type = wasm.WasmTypeInt64
|
|
||||||
|
|
||||||
class VarType_f32(VarType_Base):
|
|
||||||
wasm_type = wasm.WasmTypeFloat32
|
|
||||||
|
|
||||||
class VarType_f64(VarType_Base):
|
|
||||||
wasm_type = wasm.WasmTypeFloat64
|
|
||||||
|
|
||||||
class Generator_i32i64:
|
class Generator_i32i64:
|
||||||
def __init__(self, prefix: str, generator: 'Generator') -> None:
|
def __init__(self, prefix: str, generator: 'Generator') -> None:
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
@ -40,101 +32,41 @@ class Generator_i32i64:
|
|||||||
self.add = functools.partial(self.generator.add_statement, f'{prefix}.add')
|
self.add = functools.partial(self.generator.add_statement, f'{prefix}.add')
|
||||||
self.sub = functools.partial(self.generator.add_statement, f'{prefix}.sub')
|
self.sub = functools.partial(self.generator.add_statement, f'{prefix}.sub')
|
||||||
self.mul = functools.partial(self.generator.add_statement, f'{prefix}.mul')
|
self.mul = functools.partial(self.generator.add_statement, f'{prefix}.mul')
|
||||||
self.div_s = functools.partial(self.generator.add_statement, f'{prefix}.div_s')
|
|
||||||
self.div_u = functools.partial(self.generator.add_statement, f'{prefix}.div_u')
|
|
||||||
self.rem_s = functools.partial(self.generator.add_statement, f'{prefix}.rem_s')
|
|
||||||
self.rem_u = functools.partial(self.generator.add_statement, f'{prefix}.rem_u')
|
|
||||||
self.and_ = functools.partial(self.generator.add_statement, f'{prefix}.and')
|
|
||||||
self.or_ = functools.partial(self.generator.add_statement, f'{prefix}.or')
|
|
||||||
self.xor = functools.partial(self.generator.add_statement, f'{prefix}.xor')
|
|
||||||
self.shl = functools.partial(self.generator.add_statement, f'{prefix}.shl')
|
|
||||||
self.shr_s = functools.partial(self.generator.add_statement, f'{prefix}.shr_s')
|
|
||||||
self.shr_u = functools.partial(self.generator.add_statement, f'{prefix}.shr_u')
|
|
||||||
self.rotl = functools.partial(self.generator.add_statement, f'{prefix}.rotl')
|
|
||||||
self.rotr = functools.partial(self.generator.add_statement, f'{prefix}.rotr')
|
|
||||||
|
|
||||||
# itestop
|
|
||||||
self.eqz = functools.partial(self.generator.add_statement, f'{prefix}.eqz')
|
|
||||||
|
|
||||||
# irelop
|
# irelop
|
||||||
self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq')
|
self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq')
|
||||||
self.ne = functools.partial(self.generator.add_statement, f'{prefix}.ne')
|
self.ne = functools.partial(self.generator.add_statement, f'{prefix}.ne')
|
||||||
self.lt_s = functools.partial(self.generator.add_statement, f'{prefix}.lt_s')
|
|
||||||
self.lt_u = functools.partial(self.generator.add_statement, f'{prefix}.lt_u')
|
self.lt_u = functools.partial(self.generator.add_statement, f'{prefix}.lt_u')
|
||||||
self.gt_s = functools.partial(self.generator.add_statement, f'{prefix}.gt_s')
|
|
||||||
self.gt_u = functools.partial(self.generator.add_statement, f'{prefix}.gt_u')
|
|
||||||
self.le_s = functools.partial(self.generator.add_statement, f'{prefix}.le_s')
|
|
||||||
self.le_u = functools.partial(self.generator.add_statement, f'{prefix}.le_u')
|
|
||||||
self.ge_s = functools.partial(self.generator.add_statement, f'{prefix}.ge_s')
|
|
||||||
self.ge_u = functools.partial(self.generator.add_statement, f'{prefix}.ge_u')
|
self.ge_u = functools.partial(self.generator.add_statement, f'{prefix}.ge_u')
|
||||||
|
|
||||||
self.trunc_f32_s = functools.partial(self.generator.add_statement, f'{prefix}.trunc_f32_s')
|
|
||||||
self.trunc_f32_u = functools.partial(self.generator.add_statement, f'{prefix}.trunc_f32_u')
|
|
||||||
self.trunc_f64_s = functools.partial(self.generator.add_statement, f'{prefix}.trunc_f64_s')
|
|
||||||
self.trunc_f64_u = functools.partial(self.generator.add_statement, f'{prefix}.trunc_f64_u')
|
|
||||||
|
|
||||||
# 2.4.4. Memory Instructions
|
# 2.4.4. Memory Instructions
|
||||||
self.load = functools.partial(self.generator.add_statement, f'{prefix}.load')
|
self.load = functools.partial(self.generator.add_statement, f'{prefix}.load')
|
||||||
self.load8_u = functools.partial(self.generator.add_statement, f'{prefix}.load8_u')
|
self.load8_u = functools.partial(self.generator.add_statement, f'{prefix}.load8_u')
|
||||||
self.store = functools.partial(self.generator.add_statement, f'{prefix}.store')
|
self.store = functools.partial(self.generator.add_statement, f'{prefix}.store')
|
||||||
|
|
||||||
def const(self, value: int, comment: Optional[str] = None) -> None:
|
def const(self, value: int, comment: Optional[str] = None) -> None:
|
||||||
self.generator.add_statement(f'{self.prefix}.const', f'{value}', comment=comment)
|
self.generator.add_statement(f'{self.prefix}.const', f'0x{value:08x}', comment=comment)
|
||||||
|
|
||||||
class Generator_i32(Generator_i32i64):
|
class Generator_i32(Generator_i32i64):
|
||||||
def __init__(self, generator: 'Generator') -> None:
|
def __init__(self, generator: 'Generator') -> None:
|
||||||
super().__init__('i32', generator)
|
super().__init__('i32', generator)
|
||||||
|
|
||||||
# 2.4.1. Numeric Instructions
|
|
||||||
self.reinterpret_f32 = functools.partial(self.generator.add_statement, 'i32.reinterpret_f32')
|
|
||||||
self.wrap_i64 = functools.partial(self.generator.add_statement, 'i32.wrap_i64')
|
|
||||||
|
|
||||||
class Generator_i64(Generator_i32i64):
|
class Generator_i64(Generator_i32i64):
|
||||||
def __init__(self, generator: 'Generator') -> None:
|
def __init__(self, generator: 'Generator') -> None:
|
||||||
super().__init__('i64', generator)
|
super().__init__('i64', generator)
|
||||||
|
|
||||||
# 2.4.1. Numeric Instructions
|
|
||||||
self.extend_i32_s = functools.partial(self.generator.add_statement, 'i64.extend_i32_s')
|
|
||||||
self.extend_i32_u = functools.partial(self.generator.add_statement, 'i64.extend_i32_u')
|
|
||||||
self.reinterpret_f64 = functools.partial(self.generator.add_statement, 'i64.reinterpret_f64')
|
|
||||||
|
|
||||||
class Generator_f32f64:
|
class Generator_f32f64:
|
||||||
def __init__(self, prefix: str, generator: 'Generator') -> None:
|
def __init__(self, prefix: str, generator: 'Generator') -> None:
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
self.generator = generator
|
self.generator = generator
|
||||||
|
|
||||||
# 2.4.1. Numeric Instructions
|
# 2.4.1. Numeric Instructions
|
||||||
# funop
|
|
||||||
self.abs = functools.partial(self.generator.add_statement, f'{prefix}.abs')
|
|
||||||
self.neg = functools.partial(self.generator.add_statement, f'{prefix}.neg')
|
|
||||||
self.sqrt = functools.partial(self.generator.add_statement, f'{prefix}.sqrt')
|
|
||||||
self.ceil = functools.partial(self.generator.add_statement, f'{prefix}.ceil')
|
|
||||||
self.floor = functools.partial(self.generator.add_statement, f'{prefix}.floor')
|
|
||||||
self.trunc = functools.partial(self.generator.add_statement, f'{prefix}.trunc')
|
|
||||||
self.nearest = functools.partial(self.generator.add_statement, f'{prefix}.nearest')
|
|
||||||
|
|
||||||
# fbinop
|
# fbinop
|
||||||
self.add = functools.partial(self.generator.add_statement, f'{prefix}.add')
|
self.add = functools.partial(self.generator.add_statement, f'{prefix}.add')
|
||||||
self.sub = functools.partial(self.generator.add_statement, f'{prefix}.sub')
|
|
||||||
self.mul = functools.partial(self.generator.add_statement, f'{prefix}.mul')
|
|
||||||
self.div = functools.partial(self.generator.add_statement, f'{prefix}.div')
|
|
||||||
self.min = functools.partial(self.generator.add_statement, f'{prefix}.min')
|
|
||||||
self.max = functools.partial(self.generator.add_statement, f'{prefix}.max')
|
|
||||||
self.copysign = functools.partial(self.generator.add_statement, f'{prefix}.copysign')
|
|
||||||
|
|
||||||
# frelop
|
# frelop
|
||||||
self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq')
|
self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq')
|
||||||
self.ne = functools.partial(self.generator.add_statement, f'{prefix}.ne')
|
self.ne = functools.partial(self.generator.add_statement, f'{prefix}.ne')
|
||||||
self.lt = functools.partial(self.generator.add_statement, f'{prefix}.lt')
|
|
||||||
self.gt = functools.partial(self.generator.add_statement, f'{prefix}.gt')
|
|
||||||
self.le = functools.partial(self.generator.add_statement, f'{prefix}.le')
|
|
||||||
self.ge = functools.partial(self.generator.add_statement, f'{prefix}.ge')
|
|
||||||
|
|
||||||
# Other instr - convert
|
|
||||||
self.convert_i32_s = functools.partial(self.generator.add_statement, f'{prefix}.convert_i32_s')
|
|
||||||
self.convert_i32_u = functools.partial(self.generator.add_statement, f'{prefix}.convert_i32_u')
|
|
||||||
self.convert_i64_s = functools.partial(self.generator.add_statement, f'{prefix}.convert_i64_s')
|
|
||||||
self.convert_i64_u = functools.partial(self.generator.add_statement, f'{prefix}.convert_i64_u')
|
|
||||||
|
|
||||||
# 2.4.4. Memory Instructions
|
# 2.4.4. Memory Instructions
|
||||||
self.load = functools.partial(self.generator.add_statement, f'{prefix}.load')
|
self.load = functools.partial(self.generator.add_statement, f'{prefix}.load')
|
||||||
@ -148,19 +80,10 @@ class Generator_f32(Generator_f32f64):
|
|||||||
def __init__(self, generator: 'Generator') -> None:
|
def __init__(self, generator: 'Generator') -> None:
|
||||||
super().__init__('f32', generator)
|
super().__init__('f32', generator)
|
||||||
|
|
||||||
# 2.4.1 Numeric Instructions
|
|
||||||
self.demote_f64 = functools.partial(self.generator.add_statement, 'f32.demote_f64')
|
|
||||||
self.reinterpret_i32 = functools.partial(self.generator.add_statement, 'f32.reinterpret_i32')
|
|
||||||
|
|
||||||
class Generator_f64(Generator_f32f64):
|
class Generator_f64(Generator_f32f64):
|
||||||
def __init__(self, generator: 'Generator') -> None:
|
def __init__(self, generator: 'Generator') -> None:
|
||||||
super().__init__('f64', generator)
|
super().__init__('f64', generator)
|
||||||
|
|
||||||
# 2.4.1 Numeric Instructions
|
|
||||||
self.promote_f32 = functools.partial(self.generator.add_statement, 'f64.promote_f32')
|
|
||||||
self.reinterpret_i64 = functools.partial(self.generator.add_statement, 'f64.reinterpret_i64')
|
|
||||||
self.reinterpret_i64 = functools.partial(self.generator.add_statement, 'f64.reinterpret_i64')
|
|
||||||
|
|
||||||
class Generator_Local:
|
class Generator_Local:
|
||||||
def __init__(self, generator: 'Generator') -> None:
|
def __init__(self, generator: 'Generator') -> None:
|
||||||
self.generator = generator
|
self.generator = generator
|
||||||
@ -180,33 +103,12 @@ class Generator_Local:
|
|||||||
self.generator.add_statement('local.tee', variable.name_ref, comment=comment)
|
self.generator.add_statement('local.tee', variable.name_ref, comment=comment)
|
||||||
|
|
||||||
class GeneratorBlock:
|
class GeneratorBlock:
|
||||||
def __init__(
|
def __init__(self, generator: 'Generator', name: str) -> None:
|
||||||
self,
|
|
||||||
generator: 'Generator',
|
|
||||||
name: str,
|
|
||||||
params: Iterable[str | Type[wasm.WasmType]] = (),
|
|
||||||
result: str | Type[wasm.WasmType] | None = None,
|
|
||||||
comment: str | None = None,
|
|
||||||
) -> None:
|
|
||||||
self.generator = generator
|
self.generator = generator
|
||||||
self.name = name
|
self.name = name
|
||||||
self.params = params
|
|
||||||
self.result = result
|
|
||||||
self.comment = comment
|
|
||||||
|
|
||||||
def __enter__(self) -> None:
|
def __enter__(self) -> None:
|
||||||
stmt = self.name
|
self.generator.add_statement(self.name)
|
||||||
args: list[str] = []
|
|
||||||
if self.params:
|
|
||||||
args.extend(
|
|
||||||
f'(param {typ})' if isinstance(typ, str) else f'(param {typ().to_wat()})'
|
|
||||||
for typ in self.params
|
|
||||||
)
|
|
||||||
if self.result:
|
|
||||||
result = self.result if isinstance(self.result, str) else self.result().to_wat()
|
|
||||||
args.append(f'(result {result})')
|
|
||||||
|
|
||||||
self.generator.add_statement(stmt, *args, comment=self.comment)
|
|
||||||
|
|
||||||
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
||||||
if not exc_type:
|
if not exc_type:
|
||||||
@ -214,7 +116,7 @@ class GeneratorBlock:
|
|||||||
|
|
||||||
class Generator:
|
class Generator:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.statements: List[wasm.StatementBase] = []
|
self.statements: List[wasm.Statement] = []
|
||||||
self.locals: Dict[str, VarType_Base] = {}
|
self.locals: Dict[str, VarType_Base] = {}
|
||||||
|
|
||||||
self.i32 = Generator_i32(self)
|
self.i32 = Generator_i32(self)
|
||||||
@ -222,16 +124,13 @@ class Generator:
|
|||||||
self.f32 = Generator_f32(self)
|
self.f32 = Generator_f32(self)
|
||||||
self.f64 = Generator_f64(self)
|
self.f64 = Generator_f64(self)
|
||||||
|
|
||||||
# Parametric Instructions
|
|
||||||
self.drop = functools.partial(self.add_statement, 'drop')
|
|
||||||
|
|
||||||
# 2.4.3 Variable Instructions
|
# 2.4.3 Variable Instructions
|
||||||
self.local = Generator_Local(self)
|
self.local = Generator_Local(self)
|
||||||
|
|
||||||
# 2.4.5 Control Instructions
|
# 2.4.5 Control Instructions
|
||||||
self.nop = functools.partial(self.add_statement, 'nop')
|
self.nop = functools.partial(self.add_statement, 'nop')
|
||||||
self.unreachable = functools.partial(self.add_statement, 'unreachable')
|
self.unreachable = functools.partial(self.add_statement, 'unreachable')
|
||||||
self.block = functools.partial(GeneratorBlock, self, 'block')
|
# block
|
||||||
self.loop = functools.partial(GeneratorBlock, self, 'loop')
|
self.loop = functools.partial(GeneratorBlock, self, 'loop')
|
||||||
self.if_ = functools.partial(GeneratorBlock, self, 'if')
|
self.if_ = functools.partial(GeneratorBlock, self, 'if')
|
||||||
# br
|
# br
|
||||||
@ -239,68 +138,32 @@ class Generator:
|
|||||||
# br_table
|
# br_table
|
||||||
self.return_ = functools.partial(self.add_statement, 'return')
|
self.return_ = functools.partial(self.add_statement, 'return')
|
||||||
# call - see below
|
# call - see below
|
||||||
# call_indirect - see below
|
# call_indirect
|
||||||
|
|
||||||
def br_if(self, idx: int) -> None:
|
def br_if(self, idx: int) -> None:
|
||||||
self.add_statement('br_if', f'{idx}')
|
self.add_statement('br_if', f'{idx}')
|
||||||
|
|
||||||
def call(self, function: wasm.Function | str) -> None:
|
def call(self, function: wasm.Function) -> None:
|
||||||
if isinstance(function, wasm.Function):
|
self.add_statement('call', f'${function.name}')
|
||||||
function = function.name
|
|
||||||
|
|
||||||
self.statements.append(wasm.StatementCall(function))
|
|
||||||
|
|
||||||
def call_indirect(self, params: Iterable[Type[wasm.WasmType] | wasm.WasmType], result: Type[wasm.WasmType] | wasm.WasmType) -> None:
|
|
||||||
param_str = ' '.join(
|
|
||||||
(x() if isinstance(x, type) else x).to_wat()
|
|
||||||
for x in params
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(result, type):
|
|
||||||
result = result()
|
|
||||||
result_str = result.to_wat()
|
|
||||||
|
|
||||||
self.add_statement('call_indirect', f'(param {param_str})', f'(result {result_str})')
|
|
||||||
|
|
||||||
def add_statement(self, name: str, *args: str, comment: Optional[str] = None) -> None:
|
def add_statement(self, name: str, *args: str, comment: Optional[str] = None) -> None:
|
||||||
self.statements.append(wasm.Statement(name, *args, comment=comment))
|
self.statements.append(wasm.Statement(name, *args, comment=comment))
|
||||||
|
|
||||||
def temp_var[T: VarType_Base](self, var: T) -> T:
|
def temp_var_i32(self, infix: str) -> VarType_i32:
|
||||||
idx = 0
|
idx = 0
|
||||||
while (varname := f'__{var.name}_tmp_var_{idx}__') in self.locals:
|
while (varname := f'__{infix}_tmp_var_{idx}__') in self.locals:
|
||||||
idx += 1
|
idx += 1
|
||||||
|
|
||||||
return var.__class__(varname)
|
|
||||||
|
|
||||||
def temp_var_t(self, typ: Type[wasm.WasmType], name: str) -> VarType_Base:
|
|
||||||
idx = 0
|
|
||||||
while (varname := f'__{name}_tmp_var_{idx}__') in self.locals:
|
|
||||||
idx += 1
|
|
||||||
|
|
||||||
if typ is wasm.WasmTypeInt32:
|
|
||||||
return VarType_u8(varname)
|
|
||||||
|
|
||||||
if typ is wasm.WasmTypeInt32:
|
|
||||||
return VarType_i32(varname)
|
return VarType_i32(varname)
|
||||||
|
|
||||||
if typ is wasm.WasmTypeInt64:
|
|
||||||
return VarType_i64(varname)
|
|
||||||
|
|
||||||
if typ is wasm.WasmTypeFloat32:
|
|
||||||
return VarType_f32(varname)
|
|
||||||
|
|
||||||
if typ is wasm.WasmTypeFloat64:
|
|
||||||
return VarType_f64(varname)
|
|
||||||
|
|
||||||
raise NotImplementedError(typ)
|
|
||||||
|
|
||||||
def temp_var_i32(self, infix: str) -> VarType_i32:
|
|
||||||
return self.temp_var(VarType_i32(infix))
|
|
||||||
|
|
||||||
def temp_var_u8(self, infix: str) -> VarType_u8:
|
def temp_var_u8(self, infix: str) -> VarType_u8:
|
||||||
return self.temp_var(VarType_u8(infix))
|
idx = 0
|
||||||
|
while (varname := f'__{infix}_tmp_var_{idx}__') in self.locals:
|
||||||
|
idx += 1
|
||||||
|
|
||||||
def func_wrapper(exported: bool = False) -> Callable[[Any], wasm.Function]:
|
return VarType_u8(varname)
|
||||||
|
|
||||||
|
def func_wrapper(exported: bool = True) -> Callable[[Any], wasm.Function]:
|
||||||
"""
|
"""
|
||||||
This wrapper will execute the function and return
|
This wrapper will execute the function and return
|
||||||
a wasm Function with the generated Statements
|
a wasm Function with the generated Statements
|
||||||
@ -342,10 +205,6 @@ def func_wrapper(exported: bool = False) -> Callable[[Any], wasm.Function]:
|
|||||||
# Check what locals were used, and define them
|
# Check what locals were used, and define them
|
||||||
locals_: List[wasm.Param] = []
|
locals_: List[wasm.Param] = []
|
||||||
for local_name, local_type in generator.locals.items():
|
for local_name, local_type in generator.locals.items():
|
||||||
if local_name in args:
|
|
||||||
# Already defined as a local by wasm itself
|
|
||||||
continue
|
|
||||||
|
|
||||||
locals_.append((local_name, local_type.wasm_type(), ))
|
locals_.append((local_name, local_type.wasm_type(), ))
|
||||||
|
|
||||||
# Complete function definition
|
# Complete function definition
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
[tool.ruff.lint]
|
|
||||||
select = ["F", "E", "W", "I"]
|
|
||||||
ignore = ["E501"]
|
|
||||||
@ -1,19 +1,10 @@
|
|||||||
marko==2.1.3
|
mypy==0.812
|
||||||
mypy==1.17.1
|
pygments==2.12.0
|
||||||
pygments==2.19.1
|
pylint==2.7.4
|
||||||
pytest==8.3.5
|
pytest==6.2.2
|
||||||
pytest-integration==0.2.2
|
pytest-integration==0.2.2
|
||||||
ruff==0.12.7
|
pywasm==1.0.7
|
||||||
|
pywasm3==0.5.0
|
||||||
|
wasmer==1.1.0
|
||||||
wasmtime==31.0.0
|
wasmer_compiler_cranelift==1.1.0
|
||||||
|
wasmtime==0.36.0
|
||||||
# TODO:
|
|
||||||
# extism?
|
|
||||||
# wasmedge
|
|
||||||
|
|
||||||
# Check 2025-04-05
|
|
||||||
# wasm3: minimal maintenance phase
|
|
||||||
# py-wasm: last updated 6 years ago
|
|
||||||
# wasmer-python: Not compatible with python3.12, last updated 2 years ago
|
|
||||||
# WAVM: Last updated 3 years ago
|
|
||||||
|
|||||||
14
stubs/pywasm/__init__.pyi
Normal file
14
stubs/pywasm/__init__.pyi
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
|
from . import binary
|
||||||
|
from . import option
|
||||||
|
from . import execution
|
||||||
|
|
||||||
|
class Runtime:
|
||||||
|
store: execution.Store
|
||||||
|
|
||||||
|
def __init__(self, module: binary.Module, imps: Optional[Dict[str, Any]] = None, opts: Optional[option.Option] = None):
|
||||||
|
...
|
||||||
|
|
||||||
|
def exec(self, name: str, args: List[Union[int, float]]) -> Any:
|
||||||
|
...
|
||||||
6
stubs/pywasm/binary.pyi
Normal file
6
stubs/pywasm/binary.pyi
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from typing import BinaryIO
|
||||||
|
|
||||||
|
class Module:
|
||||||
|
@classmethod
|
||||||
|
def from_reader(cls, reader: BinaryIO) -> 'Module':
|
||||||
|
...
|
||||||
10
stubs/pywasm/execution.pyi
Normal file
10
stubs/pywasm/execution.pyi
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
class Result:
|
||||||
|
...
|
||||||
|
|
||||||
|
class MemoryInstance:
|
||||||
|
data: bytearray
|
||||||
|
|
||||||
|
class Store:
|
||||||
|
memory_list: List[MemoryInstance]
|
||||||
2
stubs/pywasm/option.pyi
Normal file
2
stubs/pywasm/option.pyi
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
class Option:
|
||||||
|
...
|
||||||
23
stubs/wasm3.pyi
Normal file
23
stubs/wasm3.pyi
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
class Module:
|
||||||
|
...
|
||||||
|
|
||||||
|
class Runtime:
|
||||||
|
...
|
||||||
|
|
||||||
|
def load(self, wasm_bin: Module) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
def get_memory(self, memid: int) -> memoryview:
|
||||||
|
...
|
||||||
|
|
||||||
|
def find_function(self, name: str) -> Callable[[Any], Any]:
|
||||||
|
...
|
||||||
|
|
||||||
|
class Environment:
|
||||||
|
def new_runtime(self, mem_size: int) -> Runtime:
|
||||||
|
...
|
||||||
|
|
||||||
|
def parse_module(self, wasm_bin: bytes) -> Module:
|
||||||
|
...
|
||||||
39
stubs/wasmer.pyi
Normal file
39
stubs/wasmer.pyi
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
from typing import Any, Dict, Callable, Union
|
||||||
|
|
||||||
|
def wat2wasm(inp: str) -> bytes:
|
||||||
|
...
|
||||||
|
|
||||||
|
class Store:
|
||||||
|
...
|
||||||
|
|
||||||
|
class Function:
|
||||||
|
def __init__(self, store: Store, func: Callable[[Any], Any]) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
class Module:
|
||||||
|
def __init__(self, store: Store, wasm: bytes) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
class Uint8Array:
|
||||||
|
def __getitem__(self, index: Union[int, slice]) -> int:
|
||||||
|
...
|
||||||
|
|
||||||
|
def __setitem__(self, idx: int, value: int) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
class Memory:
|
||||||
|
def uint8_view(self, offset: int = 0) -> Uint8Array:
|
||||||
|
...
|
||||||
|
|
||||||
|
class Exports:
|
||||||
|
...
|
||||||
|
|
||||||
|
class ImportObject:
|
||||||
|
def register(self, region: str, values: Dict[str, Function]) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
class Instance:
|
||||||
|
exports: Exports
|
||||||
|
|
||||||
|
def __init__(self, module: Module, imports: ImportObject) -> None:
|
||||||
|
...
|
||||||
16
tests/integration/constants.py
Normal file
16
tests/integration/constants.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
Constants for use in the tests
|
||||||
|
"""
|
||||||
|
|
||||||
|
ALL_INT_TYPES = ['u8', 'u32', 'u64', 'i32', 'i64']
|
||||||
|
COMPLETE_INT_TYPES = ['u32', 'u64', 'i32', 'i64']
|
||||||
|
|
||||||
|
ALL_FLOAT_TYPES = ['f32', 'f64']
|
||||||
|
COMPLETE_FLOAT_TYPES = ALL_FLOAT_TYPES
|
||||||
|
|
||||||
|
TYPE_MAP = {
|
||||||
|
**{x: int for x in ALL_INT_TYPES},
|
||||||
|
**{x: float for x in ALL_FLOAT_TYPES},
|
||||||
|
}
|
||||||
|
|
||||||
|
COMPLETE_NUMERIC_TYPES = COMPLETE_INT_TYPES + COMPLETE_FLOAT_TYPES
|
||||||
@ -1,164 +1,85 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Callable, List, TextIO, Union
|
|
||||||
|
|
||||||
from phasm.codestyle import phasm_render
|
from phasm.codestyle import phasm_render
|
||||||
from phasm.wasm import (
|
|
||||||
WasmTypeFloat32,
|
|
||||||
WasmTypeFloat64,
|
|
||||||
WasmTypeInt32,
|
|
||||||
WasmTypeInt64,
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import memory, runners
|
from . import runners
|
||||||
|
|
||||||
DASHES = '-' * 16
|
DASHES = '-' * 16
|
||||||
|
|
||||||
class InvalidArgumentException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class SuiteResult:
|
class SuiteResult:
|
||||||
def __init__(self) -> None:
|
def __init__(self):
|
||||||
self.returned_value = None
|
self.returned_value = None
|
||||||
|
|
||||||
RUNNER_CLASS_MAP = {
|
RUNNER_CLASS_MAP = {
|
||||||
|
'pywasm': runners.RunnerPywasm,
|
||||||
|
'pywasm3': runners.RunnerPywasm3,
|
||||||
'wasmtime': runners.RunnerWasmtime,
|
'wasmtime': runners.RunnerWasmtime,
|
||||||
|
'wasmer': runners.RunnerWasmer,
|
||||||
}
|
}
|
||||||
|
|
||||||
TRACE_CODE_PREFIX = """
|
|
||||||
@imported
|
|
||||||
def trace_adr(adr: i32) -> i32:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@imported
|
|
||||||
def trace_i32(x: i32) -> i32:
|
|
||||||
pass
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def make_int_trace(prefix: str) -> Callable[[int], int]:
|
|
||||||
def trace_helper(adr: int) -> int:
|
|
||||||
sys.stderr.write(f'{prefix} {adr}\n')
|
|
||||||
sys.stderr.flush()
|
|
||||||
return adr
|
|
||||||
|
|
||||||
return trace_helper
|
|
||||||
|
|
||||||
class Suite:
|
class Suite:
|
||||||
"""
|
"""
|
||||||
WebAssembly test suite
|
WebAssembly test suite
|
||||||
"""
|
"""
|
||||||
def __init__(self, code_py: str) -> None:
|
def __init__(self, code_py):
|
||||||
self.code_py = code_py
|
self.code_py = code_py
|
||||||
|
|
||||||
def run_code(
|
def run_code(self, *args, runtime='pywasm3', imports=None):
|
||||||
self,
|
|
||||||
*args: Any,
|
|
||||||
runtime: str = 'wasmtime',
|
|
||||||
func_name: str = 'testEntry',
|
|
||||||
imports: runners.Imports = None,
|
|
||||||
do_format_check: bool = True,
|
|
||||||
verbose: bool | None = None,
|
|
||||||
with_traces: bool = False,
|
|
||||||
) -> Any:
|
|
||||||
"""
|
"""
|
||||||
Compiles the given python code into wasm and
|
Compiles the given python code into wasm and
|
||||||
then runs it
|
then runs it
|
||||||
|
|
||||||
Returned is an object with the results set
|
Returned is an object with the results set
|
||||||
"""
|
"""
|
||||||
if verbose is None:
|
|
||||||
verbose = bool(os.environ.get('VERBOSE'))
|
|
||||||
|
|
||||||
code_prefix = ''
|
|
||||||
|
|
||||||
if with_traces:
|
|
||||||
assert do_format_check is False
|
|
||||||
|
|
||||||
if imports is None:
|
|
||||||
imports = {}
|
|
||||||
|
|
||||||
imports.update({
|
|
||||||
'trace_adr': make_int_trace('ADR'),
|
|
||||||
'trace_i32': make_int_trace('i32'),
|
|
||||||
})
|
|
||||||
|
|
||||||
code_prefix = TRACE_CODE_PREFIX
|
|
||||||
|
|
||||||
class_ = RUNNER_CLASS_MAP[runtime]
|
class_ = RUNNER_CLASS_MAP[runtime]
|
||||||
|
|
||||||
runner = class_(code_prefix + self.code_py)
|
runner = class_(self.code_py)
|
||||||
|
|
||||||
if verbose:
|
runner.parse()
|
||||||
write_header(sys.stderr, 'Phasm')
|
|
||||||
runner.dump_phasm_code(sys.stderr)
|
|
||||||
|
|
||||||
runner.parse(verbose=verbose)
|
|
||||||
runner.compile_ast()
|
runner.compile_ast()
|
||||||
runner.optimise_wasm_ast()
|
|
||||||
runner.compile_wat()
|
runner.compile_wat()
|
||||||
|
runner.compile_wasm()
|
||||||
if verbose:
|
|
||||||
write_header(sys.stderr, 'Assembly')
|
|
||||||
runner.dump_wasm_wat(sys.stderr)
|
|
||||||
|
|
||||||
runner.interpreter_setup()
|
runner.interpreter_setup()
|
||||||
runner.interpreter_load(imports)
|
runner.interpreter_load(imports)
|
||||||
|
|
||||||
allocator_generator = memory.Allocator(runner.phasm_ast.build, runner)
|
write_header(sys.stderr, 'Phasm')
|
||||||
|
runner.dump_phasm_code(sys.stderr)
|
||||||
|
write_header(sys.stderr, 'Assembly')
|
||||||
|
runner.dump_wasm_wat(sys.stderr)
|
||||||
|
|
||||||
# Check if code formatting works
|
# Check if code formatting works
|
||||||
if do_format_check:
|
|
||||||
assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests
|
assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests
|
||||||
|
|
||||||
func = runner.phasm_ast.functions[func_name]
|
wasm_args = []
|
||||||
assert func.type5 is not None # Type hint
|
|
||||||
func_args = runner.phasm_ast.build.type5_is_function(func.type5)
|
|
||||||
assert func_args is not None
|
|
||||||
func_ret = func_args.pop()
|
|
||||||
if len(func_args) != len(args):
|
|
||||||
raise RuntimeError(f'Invalid number of args for {func_name}')
|
|
||||||
|
|
||||||
wasm_args: List[Union[float, int]] = []
|
|
||||||
if args:
|
if args:
|
||||||
if verbose:
|
|
||||||
write_header(sys.stderr, 'Memory (pre alloc)')
|
write_header(sys.stderr, 'Memory (pre alloc)')
|
||||||
runner.interpreter_dump_memory(sys.stderr)
|
runner.interpreter_dump_memory(sys.stderr)
|
||||||
|
|
||||||
for arg, arg_typ in zip(args, func_args, strict=True):
|
for arg in args:
|
||||||
arg_typ_info = runner.phasm_ast.build.type_info_map.get(arg_typ.name)
|
if isinstance(arg, (int, float, )):
|
||||||
|
|
||||||
if arg_typ_info and (arg_typ_info.wasm_type is WasmTypeInt32 or arg_typ_info.wasm_type is WasmTypeInt64):
|
|
||||||
assert isinstance(arg, int)
|
|
||||||
wasm_args.append(arg)
|
wasm_args.append(arg)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if arg_typ_info and (arg_typ_info.wasm_type is WasmTypeFloat32 or arg_typ_info.wasm_type is WasmTypeFloat64):
|
if isinstance(arg, bytes):
|
||||||
assert isinstance(arg, float)
|
adr = runner.call('stdlib.types.__alloc_bytes__', len(arg))
|
||||||
wasm_args.append(arg)
|
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(arg)}\n')
|
||||||
continue
|
|
||||||
|
|
||||||
allocator = allocator_generator(arg_typ)
|
runner.interpreter_write_memory(adr + 4, arg)
|
||||||
adr = allocator(arg)
|
|
||||||
wasm_args.append(adr)
|
wasm_args.append(adr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
raise NotImplementedError(arg)
|
||||||
|
|
||||||
if verbose:
|
|
||||||
write_header(sys.stderr, 'Memory (pre run)')
|
write_header(sys.stderr, 'Memory (pre run)')
|
||||||
runner.interpreter_dump_memory(sys.stderr)
|
runner.interpreter_dump_memory(sys.stderr)
|
||||||
|
|
||||||
result = SuiteResult()
|
result = SuiteResult()
|
||||||
result.returned_value = runner.call(func_name, *wasm_args)
|
result.returned_value = runner.call('testEntry', *wasm_args)
|
||||||
|
|
||||||
extractor = memory.Extractor(runner.phasm_ast.build, runner)(func_ret)
|
|
||||||
result.returned_value = extractor(result.returned_value)
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
write_header(sys.stderr, 'Memory (post run)')
|
write_header(sys.stderr, 'Memory (post run)')
|
||||||
runner.interpreter_dump_memory(sys.stderr)
|
runner.interpreter_dump_memory(sys.stderr)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def write_header(textio: TextIO, msg: str) -> None:
|
def write_header(textio, msg: str) -> None:
|
||||||
textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n')
|
textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n')
|
||||||
|
|||||||
@ -1,551 +0,0 @@
|
|||||||
import struct
|
|
||||||
from typing import Any, Protocol
|
|
||||||
|
|
||||||
from phasm.build.base import BuildBase
|
|
||||||
from phasm.build.typerouter import BuildTypeRouter
|
|
||||||
from phasm.type5.record import Record
|
|
||||||
from phasm.type5.typeexpr import AtomicType, TypeExpr
|
|
||||||
from phasm.wasm import (
|
|
||||||
WasmTypeFloat32,
|
|
||||||
WasmTypeFloat64,
|
|
||||||
WasmTypeInt32,
|
|
||||||
WasmTypeInt64,
|
|
||||||
WasmTypeNone,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryAccess(Protocol):
|
|
||||||
def call(self, function: str, *args: Any) -> Any:
|
|
||||||
"""
|
|
||||||
Use for calling allocator methods inside the WASM environment.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def interpreter_write_memory(self, offset: int, data: bytes) -> None:
|
|
||||||
"""
|
|
||||||
Writes bytes directly to WASM environment memory.
|
|
||||||
|
|
||||||
Addresses should be generated using allocators via call.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
|
|
||||||
"""
|
|
||||||
Reads bytes directly from WASM environment memory.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class MemorySlice:
|
|
||||||
__slots__ = ('memory', 'offset', )
|
|
||||||
|
|
||||||
def __init__(self, memory: bytes, offset: int) -> None:
|
|
||||||
self.memory = memory
|
|
||||||
self.offset = offset
|
|
||||||
|
|
||||||
def __call__(self, size: int) -> bytes:
|
|
||||||
return self.memory[self.offset:self.offset + size]
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f'MemorySlice({self.memory!r}, {self.offset!r})'
|
|
||||||
|
|
||||||
class AllocatorFunc(Protocol):
|
|
||||||
alloc_size: int
|
|
||||||
|
|
||||||
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
|
|
||||||
"""
|
|
||||||
Takes a Python value and allocaties it in the given memory
|
|
||||||
Based on the phasm type.
|
|
||||||
|
|
||||||
When the parent already has allocated memory, the store_at_adr is set.
|
|
||||||
In that case, write your value to the given address, and return it.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Allocator(BuildTypeRouter[AllocatorFunc]):
|
|
||||||
__slots__ = ('access', )
|
|
||||||
|
|
||||||
access: MemoryAccess
|
|
||||||
|
|
||||||
def __init__(self, build: BuildBase[Any], access: MemoryAccess) -> None:
|
|
||||||
super().__init__(build)
|
|
||||||
self.access = access
|
|
||||||
|
|
||||||
def when_atomic(self, typ: AtomicType) -> AllocatorFunc:
|
|
||||||
type_info = self.build.type_info_map[typ.name]
|
|
||||||
|
|
||||||
if type_info.wasm_type is WasmTypeNone:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
if type_info.wasm_type is WasmTypeInt32 or type_info.wasm_type is WasmTypeInt64:
|
|
||||||
if type_info.signed is None:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
return IntAllocator(self.access, type_info.signed, type_info.alloc_size)
|
|
||||||
|
|
||||||
if type_info.wasm_type is WasmTypeFloat32 or type_info.wasm_type is WasmTypeFloat64:
|
|
||||||
return FloatAllocator(self.access, type_info.alloc_size)
|
|
||||||
|
|
||||||
raise NotImplementedError(typ)
|
|
||||||
|
|
||||||
def when_dynamic_array(self, da_arg: TypeExpr) -> AllocatorFunc:
|
|
||||||
if da_arg.name == 'u8':
|
|
||||||
return BytesAllocator(self.access)
|
|
||||||
|
|
||||||
return DynamicArrayAllocator(self.access, self(da_arg))
|
|
||||||
|
|
||||||
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> AllocatorFunc:
|
|
||||||
return StaticArrayAllocator(self.access, sa_len, self(sa_typ))
|
|
||||||
|
|
||||||
def when_struct(self, typ: Record) -> AllocatorFunc:
|
|
||||||
return StructAllocator(self.access, [(x_nam, self(x_typ)) for x_nam, x_typ in typ.fields])
|
|
||||||
|
|
||||||
def when_tuple(self, tp_args: list[TypeExpr]) -> AllocatorFunc:
|
|
||||||
return TupleAllocator(self.access, list(map(self, tp_args)))
|
|
||||||
|
|
||||||
class ExtractorFunc(Protocol):
|
|
||||||
alloc_size: int
|
|
||||||
|
|
||||||
def __call__(self, wasm_value: Any) -> Any:
|
|
||||||
"""
|
|
||||||
Takes a WASM value and returns a Python value
|
|
||||||
Based on the phasm type
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Extractor(BuildTypeRouter[ExtractorFunc]):
|
|
||||||
__slots__ = ('access', )
|
|
||||||
|
|
||||||
access: MemoryAccess
|
|
||||||
|
|
||||||
def __init__(self, build: BuildBase[Any], access: MemoryAccess) -> None:
|
|
||||||
super().__init__(build)
|
|
||||||
self.access = access
|
|
||||||
|
|
||||||
def when_atomic(self, typ: AtomicType) -> ExtractorFunc:
|
|
||||||
type_info = self.build.type_info_map[typ.name]
|
|
||||||
|
|
||||||
if type_info.wasm_type is WasmTypeNone:
|
|
||||||
return NoneExtractor()
|
|
||||||
|
|
||||||
if type_info.wasm_type is WasmTypeInt32 or type_info.wasm_type is WasmTypeInt64:
|
|
||||||
if type_info.signed is None:
|
|
||||||
return BoolExtractor()
|
|
||||||
|
|
||||||
return IntExtractor(type_info.signed, type_info.alloc_size)
|
|
||||||
|
|
||||||
if type_info.wasm_type is WasmTypeFloat32 or type_info.wasm_type is WasmTypeFloat64:
|
|
||||||
return FloatExtractor(type_info.alloc_size)
|
|
||||||
|
|
||||||
raise NotImplementedError(typ)
|
|
||||||
|
|
||||||
def when_dynamic_array(self, da_arg: TypeExpr) -> ExtractorFunc:
|
|
||||||
if da_arg.name == 'u8':
|
|
||||||
return BytesExtractor(self.access)
|
|
||||||
|
|
||||||
return DynamicArrayExtractor(self.access, self(da_arg))
|
|
||||||
|
|
||||||
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> ExtractorFunc:
|
|
||||||
return StaticArrayExtractor(self.access, sa_len, self(sa_typ))
|
|
||||||
|
|
||||||
def when_struct(self, typ: Record) -> ExtractorFunc:
|
|
||||||
return StructExtractor(self.access, [(x_nam, self(x_typ)) for x_nam, x_typ in typ.fields])
|
|
||||||
|
|
||||||
def when_tuple(self, tp_args: list[TypeExpr]) -> ExtractorFunc:
|
|
||||||
return TupleExtractor(self.access, list(map(self, tp_args)))
|
|
||||||
|
|
||||||
class NoneExtractor:
|
|
||||||
__slots__ = ('alloc_size', )
|
|
||||||
|
|
||||||
alloc_size: int
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
# Do not set alloc_size, it should not be called
|
|
||||||
# this will generate an AttributeError
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __call__(self, wasm_value: Any) -> None:
|
|
||||||
assert wasm_value is None
|
|
||||||
|
|
||||||
class BoolExtractor:
|
|
||||||
__slots__ = ('alloc_size', )
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.alloc_size = 1
|
|
||||||
|
|
||||||
def __call__(self, wasm_value: Any) -> bool:
|
|
||||||
assert isinstance(wasm_value, int), wasm_value
|
|
||||||
|
|
||||||
return wasm_value != 0
|
|
||||||
|
|
||||||
class IntAllocator:
|
|
||||||
__slots__ = ('access', 'alloc_size', 'signed', )
|
|
||||||
|
|
||||||
def __init__(self, access: MemoryAccess, signed: bool, alloc_size: int) -> None:
|
|
||||||
self.access = access
|
|
||||||
self.signed = signed
|
|
||||||
self.alloc_size = alloc_size
|
|
||||||
|
|
||||||
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
|
|
||||||
if store_at_adr is None:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
assert isinstance(py_value, int), py_value
|
|
||||||
data = py_value.to_bytes(self.alloc_size, 'little', signed=self.signed)
|
|
||||||
self.access.interpreter_write_memory(store_at_adr, data)
|
|
||||||
|
|
||||||
return store_at_adr
|
|
||||||
|
|
||||||
class IntExtractor:
|
|
||||||
__slots__ = ('alloc_size', 'signed', )
|
|
||||||
|
|
||||||
def __init__(self, signed: bool, alloc_size: int) -> None:
|
|
||||||
self.signed = signed
|
|
||||||
self.alloc_size = alloc_size
|
|
||||||
|
|
||||||
def __call__(self, wasm_value: Any) -> int:
|
|
||||||
if isinstance(wasm_value, MemorySlice):
|
|
||||||
# Memory stored int
|
|
||||||
data = wasm_value(self.alloc_size)
|
|
||||||
else:
|
|
||||||
# Int received from the wasm interface
|
|
||||||
# Work around the fact that phasm has unsigned integers but wasm does not
|
|
||||||
# Use little endian since that matches with what WASM uses internally
|
|
||||||
assert isinstance(wasm_value, int), wasm_value
|
|
||||||
data = wasm_value.to_bytes(8, 'little', signed=True)
|
|
||||||
data = data[:self.alloc_size]
|
|
||||||
|
|
||||||
return int.from_bytes(data, 'little', signed=self.signed)
|
|
||||||
|
|
||||||
class PtrAllocator(IntAllocator):
|
|
||||||
def __init__(self, access: MemoryAccess) -> None:
|
|
||||||
super().__init__(access, False, 4)
|
|
||||||
|
|
||||||
class PtrExtractor(IntExtractor):
|
|
||||||
def __init__(self) -> None:
|
|
||||||
super().__init__(False, 4)
|
|
||||||
|
|
||||||
FLOAT_LETTER_MAP = {
|
|
||||||
4: 'f',
|
|
||||||
8: 'd'
|
|
||||||
}
|
|
||||||
|
|
||||||
class FloatAllocator:
|
|
||||||
__slots__ = ('access', 'alloc_size', )
|
|
||||||
|
|
||||||
def __init__(self, access: MemoryAccess, alloc_size: int) -> None:
|
|
||||||
self.access = access
|
|
||||||
self.alloc_size = alloc_size
|
|
||||||
|
|
||||||
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
|
|
||||||
if store_at_adr is None:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
assert isinstance(py_value, (float, int, )), py_value
|
|
||||||
data = struct.pack(f'<{FLOAT_LETTER_MAP[self.alloc_size]}', py_value)
|
|
||||||
self.access.interpreter_write_memory(store_at_adr, data)
|
|
||||||
|
|
||||||
return store_at_adr
|
|
||||||
|
|
||||||
class FloatExtractor:
|
|
||||||
__slots__ = ('alloc_size', )
|
|
||||||
|
|
||||||
def __init__(self, alloc_size: int) -> None:
|
|
||||||
self.alloc_size = alloc_size
|
|
||||||
|
|
||||||
def __call__(self, wasm_value: Any) -> float:
|
|
||||||
if isinstance(wasm_value, MemorySlice):
|
|
||||||
# Memory stored float
|
|
||||||
data = wasm_value(self.alloc_size)
|
|
||||||
wasm_value, = struct.unpack(f'<{FLOAT_LETTER_MAP[self.alloc_size]}', data)
|
|
||||||
|
|
||||||
assert isinstance(wasm_value, float), wasm_value
|
|
||||||
|
|
||||||
return wasm_value
|
|
||||||
|
|
||||||
class DynamicArrayAllocator:
|
|
||||||
__slots__ = ('access', 'alloc_size', 'sub_allocator', )
|
|
||||||
|
|
||||||
access: MemoryAccess
|
|
||||||
alloc_size: int
|
|
||||||
sub_allocator: AllocatorFunc
|
|
||||||
|
|
||||||
def __init__(self, access: MemoryAccess, sub_allocator: AllocatorFunc) -> None:
|
|
||||||
self.access = access
|
|
||||||
self.alloc_size = 4 # ptr
|
|
||||||
self.sub_allocator = sub_allocator
|
|
||||||
|
|
||||||
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
|
|
||||||
if store_at_adr is not None:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
assert isinstance(py_value, tuple), py_value
|
|
||||||
|
|
||||||
py_len = len(py_value)
|
|
||||||
|
|
||||||
alloc_size = 4 + py_len * self.sub_allocator.alloc_size
|
|
||||||
|
|
||||||
adr = self.access.call('stdlib.alloc.__alloc__', alloc_size)
|
|
||||||
assert isinstance(adr, int) # Type int
|
|
||||||
PtrAllocator(self.access)(py_len, adr)
|
|
||||||
|
|
||||||
for idx, el_value in enumerate(py_value):
|
|
||||||
offset = adr + 4 + idx * self.sub_allocator.alloc_size
|
|
||||||
self.sub_allocator(el_value, offset)
|
|
||||||
|
|
||||||
return adr
|
|
||||||
|
|
||||||
class DynamicArrayExtractor:
|
|
||||||
__slots__ = ('access', 'alloc_size', 'sub_extractor', )
|
|
||||||
|
|
||||||
access: MemoryAccess
|
|
||||||
alloc_size: int
|
|
||||||
sub_extractor: ExtractorFunc
|
|
||||||
|
|
||||||
def __init__(self, access: MemoryAccess, sub_extractor: ExtractorFunc) -> None:
|
|
||||||
self.access = access
|
|
||||||
self.sub_extractor = sub_extractor
|
|
||||||
|
|
||||||
def __call__(self, wasm_value: Any) -> Any:
|
|
||||||
assert isinstance(wasm_value, int), wasm_value
|
|
||||||
adr = wasm_value
|
|
||||||
del wasm_value
|
|
||||||
|
|
||||||
# wasm_value must be a pointer
|
|
||||||
# The first value at said pointer is the length of the array
|
|
||||||
|
|
||||||
read_bytes = self.access.interpreter_read_memory(adr, 4)
|
|
||||||
array_len, = struct.unpack('<I', read_bytes)
|
|
||||||
|
|
||||||
read_bytes = self.access.interpreter_read_memory(adr + 4, array_len * self.sub_extractor.alloc_size)
|
|
||||||
|
|
||||||
return tuple(
|
|
||||||
self.sub_extractor(MemorySlice(read_bytes, idx * self.sub_extractor.alloc_size))
|
|
||||||
for idx in range(array_len)
|
|
||||||
)
|
|
||||||
|
|
||||||
class BytesAllocator:
|
|
||||||
__slots__ = ('access', 'alloc_size', )
|
|
||||||
|
|
||||||
access: MemoryAccess
|
|
||||||
|
|
||||||
def __init__(self, access: MemoryAccess) -> None:
|
|
||||||
self.access = access
|
|
||||||
self.alloc_size = 4 # ptr
|
|
||||||
|
|
||||||
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
|
|
||||||
assert isinstance(py_value, bytes), py_value
|
|
||||||
|
|
||||||
adr = self.access.call('stdlib.types.__alloc_bytes__', len(py_value))
|
|
||||||
assert isinstance(adr, int)
|
|
||||||
self.access.interpreter_write_memory(adr + 4, py_value)
|
|
||||||
|
|
||||||
if store_at_adr is not None:
|
|
||||||
PtrAllocator(self.access)(adr, store_at_adr)
|
|
||||||
|
|
||||||
return adr
|
|
||||||
|
|
||||||
class BytesExtractor:
|
|
||||||
__slots__ = ('access', 'alloc_size', )
|
|
||||||
|
|
||||||
access: MemoryAccess
|
|
||||||
alloc_size: int
|
|
||||||
|
|
||||||
def __init__(self, access: MemoryAccess) -> None:
|
|
||||||
self.access = access
|
|
||||||
self.alloc_size = 4 # ptr
|
|
||||||
|
|
||||||
def __call__(self, wasm_value: Any) -> bytes:
|
|
||||||
if isinstance(wasm_value, MemorySlice):
|
|
||||||
wasm_value = PtrExtractor()(wasm_value)
|
|
||||||
|
|
||||||
assert isinstance(wasm_value, int), wasm_value
|
|
||||||
adr = wasm_value
|
|
||||||
del wasm_value
|
|
||||||
|
|
||||||
# wasm_value must be a pointer
|
|
||||||
# The first value at said pointer is the length of the array
|
|
||||||
|
|
||||||
read_bytes = self.access.interpreter_read_memory(adr, 4)
|
|
||||||
array_len, = struct.unpack('<I', read_bytes)
|
|
||||||
adr += 4
|
|
||||||
|
|
||||||
return self.access.interpreter_read_memory(adr, array_len)
|
|
||||||
|
|
||||||
class StaticArrayAllocator:
|
|
||||||
__slots__ = ('access', 'alloc_size', 'sa_len', 'sub_allocator', )
|
|
||||||
|
|
||||||
access: MemoryAccess
|
|
||||||
alloc_size: int
|
|
||||||
sa_len: int
|
|
||||||
sub_allocator: AllocatorFunc
|
|
||||||
|
|
||||||
def __init__(self, access: MemoryAccess, sa_len: int, sub_allocator: AllocatorFunc) -> None:
|
|
||||||
self.access = access
|
|
||||||
self.alloc_size = 4 # ptr
|
|
||||||
self.sa_len = sa_len
|
|
||||||
self.sub_allocator = sub_allocator
|
|
||||||
|
|
||||||
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
|
|
||||||
assert isinstance(py_value, tuple), py_value
|
|
||||||
assert len(py_value) == self.sa_len
|
|
||||||
|
|
||||||
alloc_size = self.sa_len * self.sub_allocator.alloc_size
|
|
||||||
|
|
||||||
adr = self.access.call('stdlib.alloc.__alloc__', alloc_size)
|
|
||||||
assert isinstance(adr, int) # Type int
|
|
||||||
|
|
||||||
for idx, el_value in enumerate(py_value):
|
|
||||||
sub_adr = adr + idx * self.sub_allocator.alloc_size
|
|
||||||
self.sub_allocator(el_value, sub_adr)
|
|
||||||
|
|
||||||
if store_at_adr is not None:
|
|
||||||
PtrAllocator(self.access)(adr, store_at_adr)
|
|
||||||
|
|
||||||
return adr
|
|
||||||
|
|
||||||
class StaticArrayExtractor:
|
|
||||||
__slots__ = ('access', 'alloc_size', 'sa_len', 'sub_extractor', )
|
|
||||||
|
|
||||||
access: MemoryAccess
|
|
||||||
alloc_size: int
|
|
||||||
sa_len: int
|
|
||||||
sub_extractor: ExtractorFunc
|
|
||||||
|
|
||||||
def __init__(self, access: MemoryAccess, sa_len: int, sub_extractor: ExtractorFunc) -> None:
|
|
||||||
self.access = access
|
|
||||||
self.alloc_size = 4 # ptr
|
|
||||||
self.sa_len = sa_len
|
|
||||||
self.sub_extractor = sub_extractor
|
|
||||||
|
|
||||||
def __call__(self, wasm_value: Any) -> Any:
|
|
||||||
if isinstance(wasm_value, MemorySlice):
|
|
||||||
wasm_value = PtrExtractor()(wasm_value)
|
|
||||||
|
|
||||||
assert isinstance(wasm_value, int), wasm_value
|
|
||||||
adr = wasm_value
|
|
||||||
del wasm_value
|
|
||||||
|
|
||||||
read_bytes = self.access.interpreter_read_memory(adr, self.sa_len * self.sub_extractor.alloc_size)
|
|
||||||
|
|
||||||
return tuple(
|
|
||||||
self.sub_extractor(MemorySlice(read_bytes, idx * self.sub_extractor.alloc_size))
|
|
||||||
for idx in range(self.sa_len)
|
|
||||||
)
|
|
||||||
|
|
||||||
class TupleAllocator:
|
|
||||||
__slots__ = ('access', 'alloc_size', 'sub_allocator_list', )
|
|
||||||
|
|
||||||
access: MemoryAccess
|
|
||||||
alloc_size: int
|
|
||||||
sub_allocator_list: list[AllocatorFunc]
|
|
||||||
|
|
||||||
def __init__(self, access: MemoryAccess, sub_allocator_list: list[AllocatorFunc]) -> None:
|
|
||||||
self.access = access
|
|
||||||
self.alloc_size = 4 # ptr
|
|
||||||
self.sub_allocator_list = sub_allocator_list
|
|
||||||
|
|
||||||
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
|
|
||||||
assert isinstance(py_value, tuple), py_value
|
|
||||||
|
|
||||||
total_alloc_size = sum(x.alloc_size for x in self.sub_allocator_list)
|
|
||||||
|
|
||||||
adr = self.access.call('stdlib.alloc.__alloc__', total_alloc_size)
|
|
||||||
assert isinstance(adr, int) # Type int
|
|
||||||
|
|
||||||
sub_adr = adr
|
|
||||||
for sub_allocator, sub_value in zip(self.sub_allocator_list, py_value, strict=True):
|
|
||||||
sub_allocator(sub_value, sub_adr)
|
|
||||||
sub_adr += sub_allocator.alloc_size
|
|
||||||
|
|
||||||
if store_at_adr is not None:
|
|
||||||
PtrAllocator(self.access)(adr, store_at_adr)
|
|
||||||
|
|
||||||
return adr
|
|
||||||
|
|
||||||
class TupleExtractor:
|
|
||||||
__slots__ = ('access', 'alloc_size', 'sub_extractor_list', )
|
|
||||||
|
|
||||||
access: MemoryAccess
|
|
||||||
alloc_size: int
|
|
||||||
sub_extractor_list: list[ExtractorFunc]
|
|
||||||
|
|
||||||
def __init__(self, access: MemoryAccess, sub_extractor_list: list[ExtractorFunc]) -> None:
|
|
||||||
self.access = access
|
|
||||||
self.alloc_size = 4 # ptr
|
|
||||||
self.sub_extractor_list = sub_extractor_list
|
|
||||||
|
|
||||||
def __call__(self, wasm_value: Any) -> tuple[Any]:
|
|
||||||
if isinstance(wasm_value, MemorySlice):
|
|
||||||
wasm_value = PtrExtractor()(wasm_value)
|
|
||||||
|
|
||||||
assert isinstance(wasm_value, int), wasm_value
|
|
||||||
adr = wasm_value
|
|
||||||
del wasm_value
|
|
||||||
|
|
||||||
total_alloc_size = sum(x.alloc_size for x in self.sub_extractor_list)
|
|
||||||
|
|
||||||
read_bytes = self.access.interpreter_read_memory(adr, total_alloc_size)
|
|
||||||
|
|
||||||
result = []
|
|
||||||
offset = 0
|
|
||||||
for sub_extractor in self.sub_extractor_list:
|
|
||||||
result.append(sub_extractor(MemorySlice(read_bytes, offset)))
|
|
||||||
offset += sub_extractor.alloc_size
|
|
||||||
return tuple(result)
|
|
||||||
|
|
||||||
class StructAllocator:
|
|
||||||
__slots__ = ('access', 'alloc_size', 'sub_allocator_list', )
|
|
||||||
|
|
||||||
access: MemoryAccess
|
|
||||||
alloc_size: int
|
|
||||||
sub_allocator_list: list[tuple[str, AllocatorFunc]]
|
|
||||||
|
|
||||||
def __init__(self, access: MemoryAccess, sub_allocator_list: list[tuple[str, AllocatorFunc]]) -> None:
|
|
||||||
self.access = access
|
|
||||||
self.alloc_size = 4 # ptr
|
|
||||||
self.sub_allocator_list = sub_allocator_list
|
|
||||||
|
|
||||||
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
|
|
||||||
assert isinstance(py_value, dict), py_value
|
|
||||||
|
|
||||||
total_alloc_size = sum(x.alloc_size for _, x in self.sub_allocator_list)
|
|
||||||
|
|
||||||
adr = self.access.call('stdlib.alloc.__alloc__', total_alloc_size)
|
|
||||||
assert isinstance(adr, int) # Type int
|
|
||||||
|
|
||||||
sub_adr = adr
|
|
||||||
for field_name, sub_allocator in self.sub_allocator_list:
|
|
||||||
sub_value = py_value[field_name]
|
|
||||||
sub_allocator(sub_value, sub_adr)
|
|
||||||
sub_adr += sub_allocator.alloc_size
|
|
||||||
|
|
||||||
if store_at_adr is not None:
|
|
||||||
PtrAllocator(self.access)(adr, store_at_adr)
|
|
||||||
|
|
||||||
return adr
|
|
||||||
|
|
||||||
class StructExtractor:
|
|
||||||
__slots__ = ('access', 'alloc_size', 'sub_extractor_list', )
|
|
||||||
|
|
||||||
access: MemoryAccess
|
|
||||||
alloc_size: int
|
|
||||||
sub_extractor_list: list[tuple[str, ExtractorFunc]]
|
|
||||||
|
|
||||||
def __init__(self, access: MemoryAccess, sub_extractor_list: list[tuple[str, ExtractorFunc]]) -> None:
|
|
||||||
self.access = access
|
|
||||||
self.alloc_size = 4 # ptr
|
|
||||||
self.sub_extractor_list = sub_extractor_list
|
|
||||||
|
|
||||||
def __call__(self, wasm_value: Any) -> dict[str, Any]:
|
|
||||||
if isinstance(wasm_value, MemorySlice):
|
|
||||||
wasm_value = PtrExtractor()(wasm_value)
|
|
||||||
|
|
||||||
assert isinstance(wasm_value, int), wasm_value
|
|
||||||
adr = wasm_value
|
|
||||||
del wasm_value
|
|
||||||
|
|
||||||
total_alloc_size = sum(x.alloc_size for _, x in self.sub_extractor_list)
|
|
||||||
|
|
||||||
read_bytes = self.access.interpreter_read_memory(adr, total_alloc_size)
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
offset = 0
|
|
||||||
for field_name, sub_extractor in self.sub_extractor_list:
|
|
||||||
result[field_name] = sub_extractor(MemorySlice(read_bytes, offset))
|
|
||||||
offset += sub_extractor.alloc_size
|
|
||||||
return result
|
|
||||||
@ -1,26 +1,28 @@
|
|||||||
"""
|
"""
|
||||||
Runners to help run WebAssembly code on various interpreters
|
Runners to help run WebAssembly code on various interpreters
|
||||||
"""
|
"""
|
||||||
import ctypes
|
|
||||||
from typing import Any, Callable, Dict, Iterable, Optional, TextIO
|
from typing import Any, Callable, Dict, Iterable, Optional, TextIO
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
import io
|
||||||
|
|
||||||
|
import pywasm.binary
|
||||||
|
import wasm3
|
||||||
|
import wasmer
|
||||||
import wasmtime
|
import wasmtime
|
||||||
|
|
||||||
from phasm import ourlang, wasm
|
|
||||||
from phasm.compiler import phasm_compile
|
from phasm.compiler import phasm_compile
|
||||||
from phasm.optimise.removeunusedfuncs import removeunusedfuncs
|
|
||||||
from phasm.parser import phasm_parse
|
from phasm.parser import phasm_parse
|
||||||
from phasm.type5.solver import phasm_type5
|
from phasm.type3.entry import phasm_type3
|
||||||
from phasm.wasmgenerator import Generator as WasmGenerator
|
from phasm import ourlang
|
||||||
|
from phasm import wasm
|
||||||
Imports = Optional[Dict[str, Callable[[Any], Any]]]
|
|
||||||
|
|
||||||
class RunnerBase:
|
class RunnerBase:
|
||||||
"""
|
"""
|
||||||
Base class
|
Base class
|
||||||
"""
|
"""
|
||||||
phasm_code: str
|
phasm_code: str
|
||||||
phasm_ast: ourlang.Module[WasmGenerator]
|
phasm_ast: ourlang.Module
|
||||||
wasm_ast: wasm.Module
|
wasm_ast: wasm.Module
|
||||||
wasm_asm: str
|
wasm_asm: str
|
||||||
wasm_bin: bytes
|
wasm_bin: bytes
|
||||||
@ -34,12 +36,12 @@ class RunnerBase:
|
|||||||
"""
|
"""
|
||||||
_dump_code(textio, self.phasm_code)
|
_dump_code(textio, self.phasm_code)
|
||||||
|
|
||||||
def parse(self, verbose: bool = True) -> None:
|
def parse(self) -> None:
|
||||||
"""
|
"""
|
||||||
Parses the Phasm code into an AST
|
Parses the Phasm code into an AST
|
||||||
"""
|
"""
|
||||||
self.phasm_ast = phasm_parse(self.phasm_code)
|
self.phasm_ast = phasm_parse(self.phasm_code)
|
||||||
phasm_type5(self.phasm_ast, verbose=verbose)
|
phasm_type3(self.phasm_ast, verbose=True)
|
||||||
|
|
||||||
def compile_ast(self) -> None:
|
def compile_ast(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -47,12 +49,6 @@ class RunnerBase:
|
|||||||
"""
|
"""
|
||||||
self.wasm_ast = phasm_compile(self.phasm_ast)
|
self.wasm_ast = phasm_compile(self.phasm_ast)
|
||||||
|
|
||||||
def optimise_wasm_ast(self) -> None:
|
|
||||||
"""
|
|
||||||
Optimises the WebAssembly AST
|
|
||||||
"""
|
|
||||||
removeunusedfuncs(self.wasm_ast)
|
|
||||||
|
|
||||||
def compile_wat(self) -> None:
|
def compile_wat(self) -> None:
|
||||||
"""
|
"""
|
||||||
Compiles the WebAssembly AST into WebAssembly Assembly code
|
Compiles the WebAssembly AST into WebAssembly Assembly code
|
||||||
@ -69,7 +65,7 @@ class RunnerBase:
|
|||||||
"""
|
"""
|
||||||
Compiles the WebAssembly AST into WebAssembly Binary
|
Compiles the WebAssembly AST into WebAssembly Binary
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
self.wasm_bin = wasmer.wat2wasm(self.wasm_asm)
|
||||||
|
|
||||||
def interpreter_setup(self) -> None:
|
def interpreter_setup(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -77,7 +73,7 @@ class RunnerBase:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def interpreter_load(self, imports: Imports = None) -> None:
|
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Loads the code into the interpreter
|
Loads the code into the interpreter
|
||||||
"""
|
"""
|
||||||
@ -107,6 +103,77 @@ class RunnerBase:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class RunnerPywasm(RunnerBase):
|
||||||
|
"""
|
||||||
|
Implements a runner for pywasm
|
||||||
|
|
||||||
|
See https://pypi.org/project/pywasm/
|
||||||
|
"""
|
||||||
|
module: pywasm.binary.Module
|
||||||
|
runtime: pywasm.Runtime
|
||||||
|
|
||||||
|
def interpreter_setup(self) -> None:
|
||||||
|
# Nothing to set up
|
||||||
|
pass
|
||||||
|
|
||||||
|
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
||||||
|
if imports is not None:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
bytesio = io.BytesIO(self.wasm_bin)
|
||||||
|
self.module = pywasm.binary.Module.from_reader(bytesio)
|
||||||
|
self.runtime = pywasm.Runtime(self.module, {}, None)
|
||||||
|
|
||||||
|
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
|
||||||
|
for idx, byt in enumerate(data):
|
||||||
|
self.runtime.store.memory_list[0].data[offset + idx] = byt
|
||||||
|
|
||||||
|
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
|
||||||
|
return self.runtime.store.memory_list[0].data[offset:length]
|
||||||
|
|
||||||
|
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
||||||
|
_dump_memory(textio, self.runtime.store.memory_list[0].data)
|
||||||
|
|
||||||
|
def call(self, function: str, *args: Any) -> Any:
|
||||||
|
return self.runtime.exec(function, [*args])
|
||||||
|
|
||||||
|
class RunnerPywasm3(RunnerBase):
|
||||||
|
"""
|
||||||
|
Implements a runner for pywasm3
|
||||||
|
|
||||||
|
See https://pypi.org/project/pywasm3/
|
||||||
|
"""
|
||||||
|
env: wasm3.Environment
|
||||||
|
rtime: wasm3.Runtime
|
||||||
|
mod: wasm3.Module
|
||||||
|
|
||||||
|
def interpreter_setup(self) -> None:
|
||||||
|
self.env = wasm3.Environment()
|
||||||
|
self.rtime = self.env.new_runtime(1024 * 1024)
|
||||||
|
|
||||||
|
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
||||||
|
if imports is not None:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
self.mod = self.env.parse_module(self.wasm_bin)
|
||||||
|
self.rtime.load(self.mod)
|
||||||
|
|
||||||
|
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
|
||||||
|
memory = self.rtime.get_memory(0)
|
||||||
|
|
||||||
|
for idx, byt in enumerate(data):
|
||||||
|
memory[offset + idx] = byt # type: ignore
|
||||||
|
|
||||||
|
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
|
||||||
|
memory = self.rtime.get_memory(0)
|
||||||
|
return memory[offset:length].tobytes()
|
||||||
|
|
||||||
|
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
||||||
|
_dump_memory(textio, self.rtime.get_memory(0))
|
||||||
|
|
||||||
|
def call(self, function: str, *args: Any) -> Any:
|
||||||
|
return self.rtime.find_function(function)(*args)
|
||||||
|
|
||||||
class RunnerWasmtime(RunnerBase):
|
class RunnerWasmtime(RunnerBase):
|
||||||
"""
|
"""
|
||||||
Implements a runner for wasmtime
|
Implements a runner for wasmtime
|
||||||
@ -117,48 +184,15 @@ class RunnerWasmtime(RunnerBase):
|
|||||||
module: wasmtime.Module
|
module: wasmtime.Module
|
||||||
instance: wasmtime.Instance
|
instance: wasmtime.Instance
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def func2type(cls, func: Callable[[Any], Any]) -> wasmtime.FuncType:
|
|
||||||
params: list[wasmtime.ValType] = []
|
|
||||||
|
|
||||||
code = func.__code__
|
|
||||||
for idx in range(code.co_argcount):
|
|
||||||
varname = code.co_varnames[idx]
|
|
||||||
vartype = func.__annotations__[varname]
|
|
||||||
|
|
||||||
if vartype is int:
|
|
||||||
params.append(wasmtime.ValType.i32())
|
|
||||||
elif vartype is float:
|
|
||||||
params.append(wasmtime.ValType.f32())
|
|
||||||
else:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
results: list[wasmtime.ValType] = []
|
|
||||||
if func.__annotations__['return'] is None:
|
|
||||||
pass # No return value
|
|
||||||
elif func.__annotations__['return'] is int:
|
|
||||||
results.append(wasmtime.ValType.i32())
|
|
||||||
elif func.__annotations__['return'] is float:
|
|
||||||
results.append(wasmtime.ValType.f32())
|
|
||||||
else:
|
|
||||||
raise NotImplementedError('Return type', func.__annotations__['return'])
|
|
||||||
|
|
||||||
return wasmtime.FuncType(params, results)
|
|
||||||
|
|
||||||
def interpreter_setup(self) -> None:
|
def interpreter_setup(self) -> None:
|
||||||
self.store = wasmtime.Store()
|
self.store = wasmtime.Store()
|
||||||
|
|
||||||
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
||||||
functions: list[wasmtime.Func] = []
|
|
||||||
|
|
||||||
if imports is not None:
|
if imports is not None:
|
||||||
functions = [
|
raise NotImplementedError
|
||||||
wasmtime.Func(self.store, self.__class__.func2type(f), f)
|
|
||||||
for f in imports.values()
|
|
||||||
]
|
|
||||||
|
|
||||||
self.module = wasmtime.Module(self.store.engine, self.wasm_asm)
|
self.module = wasmtime.Module(self.store.engine, self.wasm_bin)
|
||||||
self.instance = wasmtime.Instance(self.store, self.module, functions)
|
self.instance = wasmtime.Instance(self.store, self.module, [])
|
||||||
|
|
||||||
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
|
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
|
||||||
exports = self.instance.exports(self.store)
|
exports = self.instance.exports(self.store)
|
||||||
@ -183,7 +217,8 @@ class RunnerWasmtime(RunnerBase):
|
|||||||
data_len = memory.data_len(self.store)
|
data_len = memory.data_len(self.store)
|
||||||
|
|
||||||
raw = ctypes.string_at(data_ptr, data_len)
|
raw = ctypes.string_at(data_ptr, data_len)
|
||||||
return raw[offset:offset + length]
|
|
||||||
|
return raw[offset:length]
|
||||||
|
|
||||||
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
||||||
exports = self.instance.exports(self.store)
|
exports = self.instance.exports(self.store)
|
||||||
@ -202,6 +237,63 @@ class RunnerWasmtime(RunnerBase):
|
|||||||
|
|
||||||
return func(self.store, *args)
|
return func(self.store, *args)
|
||||||
|
|
||||||
|
class RunnerWasmer(RunnerBase):
|
||||||
|
"""
|
||||||
|
Implements a runner for wasmer
|
||||||
|
|
||||||
|
See https://pypi.org/project/wasmer/
|
||||||
|
"""
|
||||||
|
|
||||||
|
# pylint: disable=E1101
|
||||||
|
|
||||||
|
store: wasmer.Store
|
||||||
|
module: wasmer.Module
|
||||||
|
instance: wasmer.Instance
|
||||||
|
|
||||||
|
def interpreter_setup(self) -> None:
|
||||||
|
self.store = wasmer.Store()
|
||||||
|
|
||||||
|
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
||||||
|
import_object = wasmer.ImportObject()
|
||||||
|
if imports:
|
||||||
|
import_object.register('imports', {
|
||||||
|
k: wasmer.Function(self.store, v)
|
||||||
|
for k, v in (imports or {}).items()
|
||||||
|
})
|
||||||
|
|
||||||
|
self.module = wasmer.Module(self.store, self.wasm_bin)
|
||||||
|
self.instance = wasmer.Instance(self.module, import_object)
|
||||||
|
|
||||||
|
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
|
||||||
|
exports = self.instance.exports
|
||||||
|
memory = getattr(exports, 'memory')
|
||||||
|
assert isinstance(memory, wasmer.Memory)
|
||||||
|
view = memory.uint8_view(offset)
|
||||||
|
|
||||||
|
for idx, byt in enumerate(data):
|
||||||
|
view[idx] = byt
|
||||||
|
|
||||||
|
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
|
||||||
|
exports = self.instance.exports
|
||||||
|
memory = getattr(exports, 'memory')
|
||||||
|
assert isinstance(memory, wasmer.Memory)
|
||||||
|
view = memory.uint8_view(offset)
|
||||||
|
return bytes(view[offset:length])
|
||||||
|
|
||||||
|
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
||||||
|
exports = self.instance.exports
|
||||||
|
memory = getattr(exports, 'memory')
|
||||||
|
assert isinstance(memory, wasmer.Memory)
|
||||||
|
view = memory.uint8_view()
|
||||||
|
|
||||||
|
_dump_memory(textio, view) # type: ignore
|
||||||
|
|
||||||
|
def call(self, function: str, *args: Any) -> Any:
|
||||||
|
exports = self.instance.exports
|
||||||
|
func = getattr(exports, function)
|
||||||
|
|
||||||
|
return func(*args)
|
||||||
|
|
||||||
def _dump_memory(textio: TextIO, mem: bytes) -> None:
|
def _dump_memory(textio: TextIO, mem: bytes) -> None:
|
||||||
line_width = 16
|
line_width = 16
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +1,39 @@
|
|||||||
|
import binascii
|
||||||
|
import struct
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ..helpers import Suite
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow_integration_test
|
@pytest.mark.slow_integration_test
|
||||||
def test_crc32():
|
def test_crc32():
|
||||||
with open('examples/crc32.py', 'r', encoding='ASCII') as fil:
|
# FIXME: Stub
|
||||||
code_py = "\n" + fil.read()
|
# crc = 0xFFFFFFFF
|
||||||
|
# byt = 0x61
|
||||||
|
# => (crc >> 8) ^ _CRC32_Table[(crc & 0xFF) ^ byt]
|
||||||
|
# (crc >> 8) = 0x00FFFFFF
|
||||||
|
# => 0x00FFFFFF ^ _CRC32_Table[(crc & 0xFF) ^ byt]
|
||||||
|
# (crc & 0xFF) = 0xFF
|
||||||
|
# => 0x00FFFFFF ^ _CRC32_Table[0xFF ^ byt]
|
||||||
|
# 0xFF ^ 0x61 = 0x9E
|
||||||
|
# => 0x00FFFFFF ^ _CRC32_Table[0x9E]
|
||||||
|
# _CRC32_Table[0x9E] = 0x17b7be43
|
||||||
|
# => 0x00FFFFFF ^ 0x17b7be43
|
||||||
|
|
||||||
# https://reveng.sourceforge.io/crc-catalogue/legend.htm#crc.legend.params
|
code_py = """
|
||||||
in_put = b'123456789'
|
def _crc32_f(crc: u32, byt: u8) -> u32:
|
||||||
|
return 16777215 ^ 397917763
|
||||||
|
|
||||||
# https://reveng.sourceforge.io/crc-catalogue/17plus.htm#crc.cat.crc-32-iso-hdlc
|
def testEntry(data: bytes) -> u32:
|
||||||
check = 0xcbf43926
|
return 4294967295 ^ _crc32_f(4294967295, data[0])
|
||||||
|
"""
|
||||||
|
exp_result = binascii.crc32(b'a')
|
||||||
|
|
||||||
result = Suite(code_py).run_code(in_put, func_name='crc32', do_format_check=False)
|
result = Suite(code_py).run_code(b'a')
|
||||||
|
|
||||||
assert check == result.returned_value
|
# exp_result returns a unsigned integer, as is proper
|
||||||
|
exp_data = struct.pack('I', exp_result)
|
||||||
|
# ints extracted from WebAssembly are always signed
|
||||||
|
data = struct.pack('i', result.returned_value)
|
||||||
|
|
||||||
|
assert exp_data == data
|
||||||
|
|||||||
@ -2,12 +2,29 @@ import pytest
|
|||||||
|
|
||||||
from ..helpers import Suite
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow_integration_test
|
@pytest.mark.slow_integration_test
|
||||||
def test_fib():
|
def test_fib():
|
||||||
with open('./examples/fib.py', 'r', encoding='UTF-8') as fil:
|
code_py = """
|
||||||
code_py = "\n" + fil.read()
|
def helper(n: i32, a: i32, b: i32) -> i32:
|
||||||
|
if n < 1:
|
||||||
|
return a + b
|
||||||
|
|
||||||
result = Suite(code_py).run_code(40, func_name='fib')
|
return helper(n - 1, a + b, a)
|
||||||
|
|
||||||
|
def fib(n: i32) -> i32:
|
||||||
|
if n == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if n == 1:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return helper(n - 1, 0, 1)
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> i32:
|
||||||
|
return fib(40)
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
assert 102334155 == result.returned_value
|
assert 102334155 == result.returned_value
|
||||||
|
|||||||
@ -1,309 +0,0 @@
|
|||||||
# runtime_extract_value_literal
|
|
||||||
|
|
||||||
As a developer
|
|
||||||
I want to extract a $TYPE value
|
|
||||||
In order get the result I calculated with my phasm code
|
|
||||||
|
|
||||||
```py
|
|
||||||
@exported
|
|
||||||
def testEntry() -> $TYPE:
|
|
||||||
return $VAL0
|
|
||||||
```
|
|
||||||
|
|
||||||
```py
|
|
||||||
expect(VAL0)
|
|
||||||
```
|
|
||||||
|
|
||||||
# runtime_extract_value_round_trip
|
|
||||||
|
|
||||||
As a developer
|
|
||||||
I want to extract a $TYPE value that I've input
|
|
||||||
In order let the code select a value that I've predefined
|
|
||||||
|
|
||||||
```py
|
|
||||||
@exported
|
|
||||||
def testEntry(x: $TYPE) -> $TYPE:
|
|
||||||
return x
|
|
||||||
```
|
|
||||||
|
|
||||||
```py
|
|
||||||
expect(VAL0, given=[VAL0])
|
|
||||||
```
|
|
||||||
|
|
||||||
# module_constant_def_ok
|
|
||||||
|
|
||||||
As a developer
|
|
||||||
I want to define $TYPE module constants
|
|
||||||
In order to make hardcoded values more visible
|
|
||||||
and to make it easier to change hardcoded values
|
|
||||||
|
|
||||||
```py
|
|
||||||
CONSTANT: $TYPE = $VAL0
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return 9
|
|
||||||
```
|
|
||||||
|
|
||||||
```py
|
|
||||||
expect(9)
|
|
||||||
```
|
|
||||||
|
|
||||||
# module_constant_def_bad
|
|
||||||
|
|
||||||
As a developer
|
|
||||||
I want to receive a type error on an invalid assignment on a $TYPE module constant
|
|
||||||
In order to make debugging easier
|
|
||||||
|
|
||||||
```py
|
|
||||||
CONSTANT: (u32, ) = $VAL0
|
|
||||||
```
|
|
||||||
|
|
||||||
```py
|
|
||||||
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'):
|
|
||||||
expect_type_error('Tuple element count mismatch')
|
|
||||||
elif TYPE_NAME == 'bytes':
|
|
||||||
expect_type_error('Cannot convert from literal bytes')
|
|
||||||
elif TYPE_NAME == 'f32' or TYPE_NAME == 'f64':
|
|
||||||
expect_type_error('Cannot convert from literal float')
|
|
||||||
elif TYPE_NAME == 'i32' or TYPE_NAME == 'i64' or TYPE_NAME == 'u32' or TYPE_NAME == 'u64':
|
|
||||||
expect_type_error('Cannot convert from literal integer')
|
|
||||||
else:
|
|
||||||
expect_type_error('Not the same type')
|
|
||||||
```
|
|
||||||
|
|
||||||
# function_result_is_literal_ok
|
|
||||||
|
|
||||||
As a developer
|
|
||||||
I want to use return a literal from a function
|
|
||||||
In order to define constants in a more dynamic way
|
|
||||||
|
|
||||||
```py
|
|
||||||
def drop_arg_return_9(x: $TYPE) -> i32:
|
|
||||||
return 9
|
|
||||||
|
|
||||||
def constant() -> $TYPE:
|
|
||||||
return $VAL0
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return drop_arg_return_9(constant())
|
|
||||||
```
|
|
||||||
|
|
||||||
```py
|
|
||||||
expect(9)
|
|
||||||
```
|
|
||||||
|
|
||||||
# function_result_is_literal_bad
|
|
||||||
|
|
||||||
As a developer
|
|
||||||
I want to receive a type error when returning a $TYPE literal for a function that doesn't return that type
|
|
||||||
In order to make debugging easier
|
|
||||||
|
|
||||||
```py
|
|
||||||
def drop_arg_return_9(x: (u32, )) -> i32:
|
|
||||||
return 9
|
|
||||||
|
|
||||||
def constant() -> (u32, ):
|
|
||||||
return $VAL0
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return drop_arg_return_9(constant())
|
|
||||||
```
|
|
||||||
|
|
||||||
```py
|
|
||||||
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'):
|
|
||||||
expect_type_error('Tuple element count mismatch')
|
|
||||||
elif TYPE_NAME.startswith('struct_'):
|
|
||||||
expect_type_error('Not the same type')
|
|
||||||
elif TYPE_NAME == 'bytes':
|
|
||||||
expect_type_error('Cannot convert from literal bytes')
|
|
||||||
elif TYPE_NAME == 'f32' or TYPE_NAME == 'f64':
|
|
||||||
expect_type_error('Cannot convert from literal float')
|
|
||||||
elif TYPE_NAME == 'i32' or TYPE_NAME == 'i64' or TYPE_NAME == 'u32' or TYPE_NAME == 'u64':
|
|
||||||
expect_type_error('Cannot convert from literal integer')
|
|
||||||
else:
|
|
||||||
expect_type_error('Not the same type')
|
|
||||||
```
|
|
||||||
|
|
||||||
# function_result_is_module_constant_ok
|
|
||||||
|
|
||||||
As a developer
|
|
||||||
I want to use return a $TYPE module constant from a function
|
|
||||||
In order to use my module constants in return statements
|
|
||||||
|
|
||||||
```py
|
|
||||||
CONSTANT: $TYPE = $VAL0
|
|
||||||
|
|
||||||
def helper(x: $TYPE) -> i32:
|
|
||||||
return 9
|
|
||||||
|
|
||||||
def constant() -> $TYPE:
|
|
||||||
return CONSTANT
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return helper(constant())
|
|
||||||
```
|
|
||||||
|
|
||||||
```py
|
|
||||||
expect(9)
|
|
||||||
```
|
|
||||||
|
|
||||||
# function_result_is_module_constant_bad
|
|
||||||
|
|
||||||
As a developer
|
|
||||||
I want to receive a type error when returning a $TYPE module constant for a function that doesn't return that type
|
|
||||||
In order to make debugging easier
|
|
||||||
|
|
||||||
```py
|
|
||||||
CONSTANT: $TYPE = $VAL0
|
|
||||||
|
|
||||||
def drop_arg_return_9(x: (u32, )) -> i32:
|
|
||||||
return 9
|
|
||||||
|
|
||||||
def constant() -> (u32, ):
|
|
||||||
return CONSTANT
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return drop_arg_return_9(constant())
|
|
||||||
```
|
|
||||||
|
|
||||||
```py
|
|
||||||
expect_type_error('Not the same type')
|
|
||||||
```
|
|
||||||
|
|
||||||
# function_result_is_arg_ok
|
|
||||||
|
|
||||||
As a developer
|
|
||||||
I want to use return a $TYPE function argument
|
|
||||||
In order to make it possible to select a value using a function
|
|
||||||
|
|
||||||
```py
|
|
||||||
CONSTANT: $TYPE = $VAL0
|
|
||||||
|
|
||||||
def drop_arg_return_9(x: $TYPE) -> i32:
|
|
||||||
return 9
|
|
||||||
|
|
||||||
def select(x: $TYPE) -> $TYPE:
|
|
||||||
return x
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return drop_arg_return_9(select(CONSTANT))
|
|
||||||
```
|
|
||||||
|
|
||||||
```py
|
|
||||||
expect(9)
|
|
||||||
```
|
|
||||||
|
|
||||||
# function_result_is_arg_bad
|
|
||||||
|
|
||||||
As a developer
|
|
||||||
I want to receive a type error when returning a $TYPE argument for a function that doesn't return that type
|
|
||||||
In order to make debugging easier
|
|
||||||
|
|
||||||
```py
|
|
||||||
def drop_arg_return_9(x: (u32, )) -> i32:
|
|
||||||
return 9
|
|
||||||
|
|
||||||
def select(x: $TYPE) -> (u32, ):
|
|
||||||
return x
|
|
||||||
```
|
|
||||||
|
|
||||||
```py
|
|
||||||
expect_type_error('Not the same type')
|
|
||||||
```
|
|
||||||
|
|
||||||
# function_arg_literal_ok
|
|
||||||
|
|
||||||
As a developer
|
|
||||||
I want to use a $TYPE literal by passing it to a function
|
|
||||||
In order to use a pre-existing function with the values I specify
|
|
||||||
|
|
||||||
```py
|
|
||||||
def helper(x: $TYPE) -> i32:
|
|
||||||
return 9
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return helper($VAL0)
|
|
||||||
```
|
|
||||||
|
|
||||||
```py
|
|
||||||
expect(9)
|
|
||||||
```
|
|
||||||
|
|
||||||
# function_arg_literal_bad
|
|
||||||
|
|
||||||
As a developer
|
|
||||||
I want to receive a type error when passing a $TYPE literal to a function that does not accept it
|
|
||||||
In order to make debugging easier
|
|
||||||
|
|
||||||
```py
|
|
||||||
def helper(x: (u32, )) -> i32:
|
|
||||||
return 9
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return helper($VAL0)
|
|
||||||
```
|
|
||||||
|
|
||||||
```py
|
|
||||||
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'):
|
|
||||||
expect_type_error('Tuple element count mismatch')
|
|
||||||
elif TYPE_NAME.startswith('struct_'):
|
|
||||||
expect_type_error('Not the same type')
|
|
||||||
elif TYPE_NAME == 'bytes':
|
|
||||||
expect_type_error('Cannot convert from literal bytes')
|
|
||||||
elif TYPE_NAME == 'f32' or TYPE_NAME == 'f64':
|
|
||||||
expect_type_error('Cannot convert from literal float')
|
|
||||||
elif TYPE_NAME == 'i32' or TYPE_NAME == 'i64' or TYPE_NAME == 'u32' or TYPE_NAME == 'u64':
|
|
||||||
expect_type_error('Cannot convert from literal integer')
|
|
||||||
else:
|
|
||||||
expect_type_error('Not the same type')
|
|
||||||
```
|
|
||||||
|
|
||||||
# function_arg_module_constant_def_ok
|
|
||||||
|
|
||||||
As a developer
|
|
||||||
I want to use a $TYPE module constant by passing it to a function
|
|
||||||
In order to use my defined value with a pre-existing function
|
|
||||||
|
|
||||||
```py
|
|
||||||
CONSTANT: $TYPE = $VAL0
|
|
||||||
|
|
||||||
def helper(x: $TYPE) -> i32:
|
|
||||||
return 9
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return helper(CONSTANT)
|
|
||||||
```
|
|
||||||
|
|
||||||
```py
|
|
||||||
expect(9)
|
|
||||||
```
|
|
||||||
|
|
||||||
# function_arg_module_constant_def_bad
|
|
||||||
|
|
||||||
As a developer
|
|
||||||
I want to receive a type error when passing a $TYPE module constant to a function that does not accept it
|
|
||||||
In order to make debugging easier
|
|
||||||
|
|
||||||
```py
|
|
||||||
CONSTANT: $TYPE = $VAL0
|
|
||||||
|
|
||||||
def helper(x: (u32, )) -> i32:
|
|
||||||
return 9
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return helper(CONSTANT)
|
|
||||||
```
|
|
||||||
|
|
||||||
```py
|
|
||||||
expect_type_error('Not the same type')
|
|
||||||
```
|
|
||||||
@ -1,153 +0,0 @@
|
|||||||
import functools
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import marko
|
|
||||||
import marko.md_renderer
|
|
||||||
|
|
||||||
|
|
||||||
def get_tests(template):
|
|
||||||
test_data = None
|
|
||||||
for el in template.children:
|
|
||||||
if isinstance(el, marko.block.BlankLine):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(el, marko.block.Heading):
|
|
||||||
if test_data is not None:
|
|
||||||
yield test_data
|
|
||||||
|
|
||||||
test_data = []
|
|
||||||
test_data.append(el)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if test_data is not None:
|
|
||||||
test_data.append(el)
|
|
||||||
|
|
||||||
if test_data is not None:
|
|
||||||
yield test_data
|
|
||||||
|
|
||||||
def apply_settings(settings, txt):
|
|
||||||
for k, v in settings.items():
|
|
||||||
if k in ('CODE_HEADER', 'PYTHON'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
txt = txt.replace(f'${k}', v)
|
|
||||||
return txt
|
|
||||||
|
|
||||||
def generate_assertion_expect(result, arg, given=None):
|
|
||||||
given = given or []
|
|
||||||
|
|
||||||
result.append('result = Suite(code_py).run_code(' + ', '.join(repr(x) for x in given) + ')')
|
|
||||||
result.append(f'assert {repr(arg)} == result.returned_value')
|
|
||||||
|
|
||||||
def generate_assertion_expect_type_error(result, error_msg):
|
|
||||||
result.append(f'with pytest.raises(Type5SolverException, match={error_msg!r}):')
|
|
||||||
result.append(' Suite(code_py).run_code()')
|
|
||||||
|
|
||||||
def json_does_not_support_byte_or_tuple_values_fix(inp: Any):
|
|
||||||
if isinstance(inp, (int, float, )):
|
|
||||||
return inp
|
|
||||||
|
|
||||||
if isinstance(inp, str):
|
|
||||||
if inp.startswith('bytes:'):
|
|
||||||
return inp[6:].encode()
|
|
||||||
return inp
|
|
||||||
|
|
||||||
if isinstance(inp, list):
|
|
||||||
return tuple(map(json_does_not_support_byte_or_tuple_values_fix, inp))
|
|
||||||
|
|
||||||
if isinstance(inp, dict):
|
|
||||||
return {
|
|
||||||
key: json_does_not_support_byte_or_tuple_values_fix(val)
|
|
||||||
for key, val in inp.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
raise NotImplementedError(inp)
|
|
||||||
|
|
||||||
def generate_assertions(settings, result_code):
|
|
||||||
result = []
|
|
||||||
|
|
||||||
locals_ = {
|
|
||||||
'TYPE': settings['TYPE'],
|
|
||||||
'TYPE_NAME': settings['TYPE_NAME'],
|
|
||||||
'expect': functools.partial(generate_assertion_expect, result),
|
|
||||||
'expect_type_error': functools.partial(generate_assertion_expect_type_error, result),
|
|
||||||
}
|
|
||||||
|
|
||||||
if 'PYTHON' in settings:
|
|
||||||
locals_.update(json_does_not_support_byte_or_tuple_values_fix(settings['PYTHON']))
|
|
||||||
|
|
||||||
if 'VAL0' not in locals_:
|
|
||||||
locals_['VAL0'] = eval(settings['VAL0'])
|
|
||||||
|
|
||||||
exec(result_code, {}, locals_)
|
|
||||||
|
|
||||||
return ' ' + '\n '.join(result) + '\n'
|
|
||||||
|
|
||||||
def generate_code(markdown, template, settings):
|
|
||||||
type_name = settings['TYPE_NAME']
|
|
||||||
|
|
||||||
print('"""')
|
|
||||||
print('AUTO GENERATED')
|
|
||||||
print()
|
|
||||||
print('TEMPLATE:', sys.argv[1])
|
|
||||||
print('SETTINGS:', sys.argv[2])
|
|
||||||
print('"""')
|
|
||||||
print('import pytest')
|
|
||||||
print()
|
|
||||||
print('from phasm.type5.solver import Type5SolverException')
|
|
||||||
print()
|
|
||||||
print('from ..helpers import Suite')
|
|
||||||
print()
|
|
||||||
print()
|
|
||||||
|
|
||||||
for test in get_tests(template):
|
|
||||||
assert len(test) == 4, test
|
|
||||||
heading, paragraph, code_block1, code_block2 = test
|
|
||||||
|
|
||||||
assert isinstance(heading, marko.block.Heading)
|
|
||||||
assert isinstance(paragraph, marko.block.Paragraph)
|
|
||||||
assert isinstance(code_block1, marko.block.FencedCode)
|
|
||||||
assert isinstance(code_block2, marko.block.FencedCode)
|
|
||||||
|
|
||||||
test_id = apply_settings(settings, heading.children[0].children)
|
|
||||||
user_story = apply_settings(settings, markdown.renderer.render(paragraph))
|
|
||||||
inp_code = apply_settings(settings, code_block1.children[0].children)
|
|
||||||
|
|
||||||
result_code = markdown.renderer.render_children(code_block2)
|
|
||||||
|
|
||||||
print('@pytest.mark.integration_test')
|
|
||||||
print(f'def test_{type_name}_{test_id}():')
|
|
||||||
print(' """')
|
|
||||||
print(' ' + user_story.strip().replace('\n', '\n '))
|
|
||||||
print(' """')
|
|
||||||
print(' code_py = """')
|
|
||||||
if 'CODE_HEADER' in settings:
|
|
||||||
for lin in settings['CODE_HEADER']:
|
|
||||||
print(lin)
|
|
||||||
print()
|
|
||||||
print(inp_code.rstrip('\n'))
|
|
||||||
print('"""')
|
|
||||||
print()
|
|
||||||
|
|
||||||
print(generate_assertions(settings, result_code))
|
|
||||||
print()
|
|
||||||
|
|
||||||
def main():
|
|
||||||
markdown = marko.Markdown(
|
|
||||||
renderer=marko.md_renderer.MarkdownRenderer,
|
|
||||||
)
|
|
||||||
with open(sys.argv[1], 'r', encoding='utf-8') as fil:
|
|
||||||
template = markdown.parse(fil.read())
|
|
||||||
|
|
||||||
with open(sys.argv[2], 'r', encoding='utf-8') as fil:
|
|
||||||
settings = json.load(fil)
|
|
||||||
|
|
||||||
if 'TYPE_NAME' not in settings:
|
|
||||||
settings['TYPE_NAME'] = settings['TYPE']
|
|
||||||
|
|
||||||
generate_code(markdown, template, settings)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE": "bytes",
|
|
||||||
"VAL0": "b'ABCDEFG'"
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE_NAME": "dynamic_array_u64",
|
|
||||||
"TYPE": "u64[...]",
|
|
||||||
"VAL0": "(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, )"
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE": "f32",
|
|
||||||
"VAL0": "1000000.125"
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE": "f64",
|
|
||||||
"VAL0": "1000000.125"
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE": "i32",
|
|
||||||
"VAL0": "1000000"
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE": "i64",
|
|
||||||
"VAL0": "1000000"
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE_NAME": "static_array_tuple_u32_u32_3",
|
|
||||||
"TYPE": "(u32, u32, )[3]",
|
|
||||||
"VAL0": "((1, 100, ), (2, 200, ), (3, 300, ), )"
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE_NAME": "static_array_u64_32",
|
|
||||||
"TYPE": "u64[32]",
|
|
||||||
"VAL0": "(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, )"
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE_NAME": "static_array_with_structs",
|
|
||||||
"TYPE": "StructMain[3]",
|
|
||||||
"VAL0": "(StructMain(1, (StructCode(-4), 4, 4.0, ), (StructCode(-1), StructCode(-2), )), StructMain(2, (StructCode(-16), 16, 16.0, ), (StructCode(3), StructCode(14), )), StructMain(3, (StructCode(-256), 256, 256.0, ), (StructCode(-9), StructCode(-98), )), )",
|
|
||||||
"CODE_HEADER": [
|
|
||||||
"class StructCode:",
|
|
||||||
" code: i32",
|
|
||||||
"",
|
|
||||||
"class StructMain:",
|
|
||||||
" val00: u8",
|
|
||||||
" val01: (StructCode, u64, f32, )",
|
|
||||||
" val02: StructCode[2]"
|
|
||||||
],
|
|
||||||
"PYTHON": {
|
|
||||||
"VAL0": [
|
|
||||||
{
|
|
||||||
"val00": 1,
|
|
||||||
"val01": [{"code": -4}, 4, 4.0],
|
|
||||||
"val02": [{"code": -1}, {"code": -2}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"val00": 2,
|
|
||||||
"val01": [{"code": -16}, 16, 16.0],
|
|
||||||
"val02": [{"code": 3}, {"code": 14}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"val00": 3,
|
|
||||||
"val01": [{"code": -256}, 256, 256.0],
|
|
||||||
"val02": [{"code": -9}, {"code": -98}]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE_NAME": "struct_all_primitives",
|
|
||||||
"TYPE": "StructallPrimitives",
|
|
||||||
"VAL0": "StructallPrimitives(1, 2, 4, 8, 1, -1, 2, -2, 4, -4, 8, -8, 125.125, -125.125, 5000.5, -5000.5, b'Hello, world!')",
|
|
||||||
"CODE_HEADER": [
|
|
||||||
"class StructallPrimitives:",
|
|
||||||
" val00: u8",
|
|
||||||
" val03: u16",
|
|
||||||
" val01: u32",
|
|
||||||
" val02: u64",
|
|
||||||
" val10: i8",
|
|
||||||
" val11: i8",
|
|
||||||
" val16: i16",
|
|
||||||
" val17: i16",
|
|
||||||
" val12: i32",
|
|
||||||
" val13: i32",
|
|
||||||
" val14: i64",
|
|
||||||
" val15: i64",
|
|
||||||
" val20: f32",
|
|
||||||
" val21: f32",
|
|
||||||
" val22: f64",
|
|
||||||
" val23: f64",
|
|
||||||
" val30: bytes"
|
|
||||||
],
|
|
||||||
"PYTHON": {
|
|
||||||
"VAL0": {
|
|
||||||
"val00": 1,
|
|
||||||
"val03": 2,
|
|
||||||
"val01": 4,
|
|
||||||
"val02": 8,
|
|
||||||
"val10": 1,
|
|
||||||
"val11": -1,
|
|
||||||
"val16": 2,
|
|
||||||
"val17": -2,
|
|
||||||
"val12": 4,
|
|
||||||
"val13": -4,
|
|
||||||
"val14": 8,
|
|
||||||
"val15": -8,
|
|
||||||
"val20": 125.125,
|
|
||||||
"val21": -125.125,
|
|
||||||
"val22": 5000.5,
|
|
||||||
"val23": -5000.5,
|
|
||||||
"val30": "bytes:Hello, world!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE_NAME": "struct_nested",
|
|
||||||
"TYPE": "StructNested",
|
|
||||||
"VAL0": "StructNested(4, SubStruct(8, 16), 20)",
|
|
||||||
"CODE_HEADER": [
|
|
||||||
"class SubStruct:",
|
|
||||||
" val00: u8",
|
|
||||||
" val01: u8",
|
|
||||||
"",
|
|
||||||
"class StructNested:",
|
|
||||||
" val00: u64",
|
|
||||||
" val01: SubStruct",
|
|
||||||
" val02: u64"
|
|
||||||
],
|
|
||||||
"PYTHON": {
|
|
||||||
"VAL0": {
|
|
||||||
"val00": 4,
|
|
||||||
"val01": {
|
|
||||||
"val00": 8,
|
|
||||||
"val01": 16
|
|
||||||
},
|
|
||||||
"val02": 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE_NAME": "struct_one_field",
|
|
||||||
"TYPE": "StructOneField",
|
|
||||||
"VAL0": "StructOneField(4)",
|
|
||||||
"CODE_HEADER": [
|
|
||||||
"class StructOneField:",
|
|
||||||
" value: u32"
|
|
||||||
],
|
|
||||||
"PYTHON": {
|
|
||||||
"VAL0": {"value": 4}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE_NAME": "tuple_all_primitives",
|
|
||||||
"TYPE": "(u8, u32, u64, i8, i8, i32, i32, i64, i64, f32, f32, f64, f64, bytes, )",
|
|
||||||
"VAL0": "(1, 4, 8, 1, -1, 4, -4, 8, -8, 125.125, -125.125, 5000.5, -5000.5, b'Hello, world!', )"
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE_NAME": "tuple_nested",
|
|
||||||
"TYPE": "(u64, (u32, bytes, u32, ), (u8, u32[3], u8, ), )",
|
|
||||||
"VAL0": "(1000000, (1, b'test', 2, ), (1, (4, 4, 4, ), 1, ), )"
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE_NAME": "tuple_u64_u32_u8",
|
|
||||||
"TYPE": "(u64, u32, u8, )",
|
|
||||||
"VAL0": "(1000000, 1000, 1, )"
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE": "u32",
|
|
||||||
"VAL0": "1000000"
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"TYPE": "u64",
|
|
||||||
"VAL0": "1000000"
|
|
||||||
}
|
|
||||||
80
tests/integration/test_lang/test_builtins.py
Normal file
80
tests/integration/test_lang/test_builtins.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ..helpers import Suite, write_header
|
||||||
|
from ..runners import RunnerPywasm
|
||||||
|
|
||||||
|
def setup_interpreter(phash_code: str) -> RunnerPywasm:
|
||||||
|
runner = RunnerPywasm(phash_code)
|
||||||
|
|
||||||
|
runner.parse()
|
||||||
|
runner.compile_ast()
|
||||||
|
runner.compile_wat()
|
||||||
|
runner.compile_wasm()
|
||||||
|
runner.interpreter_setup()
|
||||||
|
runner.interpreter_load()
|
||||||
|
|
||||||
|
write_header(sys.stderr, 'Phasm')
|
||||||
|
runner.dump_phasm_code(sys.stderr)
|
||||||
|
write_header(sys.stderr, 'Assembly')
|
||||||
|
runner.dump_wasm_wat(sys.stderr)
|
||||||
|
|
||||||
|
return runner
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
def test_foldl_1():
|
||||||
|
code_py = """
|
||||||
|
def u8_or(l: u8, r: u8) -> u8:
|
||||||
|
return l | r
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry(b: bytes) -> u8:
|
||||||
|
return foldl(u8_or, 128, b)
|
||||||
|
"""
|
||||||
|
suite = Suite(code_py)
|
||||||
|
|
||||||
|
result = suite.run_code(b'')
|
||||||
|
assert 128 == result.returned_value
|
||||||
|
|
||||||
|
result = suite.run_code(b'\x80', runtime='pywasm')
|
||||||
|
assert 128 == result.returned_value
|
||||||
|
|
||||||
|
result = suite.run_code(b'\x80\x40', runtime='pywasm')
|
||||||
|
assert 192 == result.returned_value
|
||||||
|
|
||||||
|
result = suite.run_code(b'\x80\x40\x20\x10', runtime='pywasm')
|
||||||
|
assert 240 == result.returned_value
|
||||||
|
|
||||||
|
result = suite.run_code(b'\x80\x40\x20\x10\x08\x04\x02\x01', runtime='pywasm')
|
||||||
|
assert 255 == result.returned_value
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
def test_foldl_2():
|
||||||
|
code_py = """
|
||||||
|
def xor(l: u8, r: u8) -> u8:
|
||||||
|
return l ^ r
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry(a: bytes, b: bytes) -> u8:
|
||||||
|
return foldl(xor, 0, a) ^ foldl(xor, 0, b)
|
||||||
|
"""
|
||||||
|
suite = Suite(code_py)
|
||||||
|
|
||||||
|
result = suite.run_code(b'\x55\x0F', b'\x33\x80')
|
||||||
|
assert 233 == result.returned_value
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
def test_foldl_3():
|
||||||
|
code_py = """
|
||||||
|
def xor(l: u32, r: u8) -> u32:
|
||||||
|
return l ^ u32(r)
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry(a: bytes) -> u32:
|
||||||
|
return foldl(xor, 0, a)
|
||||||
|
"""
|
||||||
|
suite = Suite(code_py)
|
||||||
|
|
||||||
|
result = suite.run_code(b'\x55\x0F\x33\x80')
|
||||||
|
assert 233 == result.returned_value
|
||||||
@ -2,30 +2,52 @@ import pytest
|
|||||||
|
|
||||||
from ..helpers import Suite
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
def test_bytes_export_constant():
|
def test_bytes_address():
|
||||||
code_py = """
|
|
||||||
CONSTANT: bytes = b'Hello'
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> bytes:
|
|
||||||
return CONSTANT
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert b"Hello" == result.returned_value[0:5]
|
|
||||||
assert 5 == len(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_bytes_export_instantiation():
|
|
||||||
code_py = """
|
code_py = """
|
||||||
@exported
|
@exported
|
||||||
def testEntry() -> bytes:
|
def testEntry(f: bytes) -> bytes:
|
||||||
return b'Hello'
|
return f
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
result = Suite(code_py).run_code(b'This is a test')
|
||||||
|
|
||||||
assert b"Hello" == result.returned_value
|
# THIS DEPENDS ON THE ALLOCATOR
|
||||||
|
# A different allocator will return a different value
|
||||||
|
assert 20 == result.returned_value
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
def test_bytes_length():
|
||||||
|
code_py = """
|
||||||
|
@exported
|
||||||
|
def testEntry(f: bytes) -> i32:
|
||||||
|
return len(f)
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code(b'This is another test')
|
||||||
|
|
||||||
|
assert 20 == result.returned_value
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
def test_bytes_index():
|
||||||
|
code_py = """
|
||||||
|
@exported
|
||||||
|
def testEntry(f: bytes) -> u8:
|
||||||
|
return f[8]
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code(b'This is another test')
|
||||||
|
|
||||||
|
assert 0x61 == result.returned_value
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
def test_bytes_index_out_of_bounds():
|
||||||
|
code_py = """
|
||||||
|
@exported
|
||||||
|
def testEntry(f: bytes) -> u8:
|
||||||
|
return f[50]
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code(b'Short', b'Long' * 100)
|
||||||
|
|
||||||
|
assert 0 == result.returned_value
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user