Compare commits
19 Commits
allow-func
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1854d7a38 | ||
| 71691d68e9 | |||
|
|
7df9d5af12 | ||
| 3d504e3d79 | |||
|
|
6a1f4fc010 | ||
|
|
1a3bc19dce | ||
|
|
544bbfac72 | ||
|
|
8a1a6af3e7 | ||
|
|
3cb4860973 | ||
|
|
6f40276a9c | ||
|
|
38294497cb | ||
|
|
d97be81828 | ||
|
|
84e7c42ea4 | ||
|
|
d017ebe096 | ||
|
|
2c2a96c8a7 | ||
|
|
b670bb02ad | ||
|
|
56ab88db2c | ||
|
|
cfdcaa230d | ||
|
|
fdaa680572 |
5
Makefile
5
Makefile
@ -1,3 +1,4 @@
|
||||
TEST_FILES := tests
|
||||
WAT2WASM := venv/bin/python wat2wasm.py
|
||||
|
||||
%.wat: %.py $(shell find phasm -name '*.py') venv/.done
|
||||
@ -16,13 +17,13 @@ examples: venv/.done $(subst .py,.wasm,$(wildcard examples/*.py)) $(subst .py,.w
|
||||
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)))
|
||||
venv/bin/pytest tests $(TEST_FLAGS)
|
||||
venv/bin/pytest $(TEST_FILES) $(TEST_FLAGS)
|
||||
|
||||
lint: venv/.done
|
||||
venv/bin/ruff check phasm tests
|
||||
|
||||
typecheck: venv/.done
|
||||
venv/bin/mypy --strict phasm wat2wasm.py tests/integration/helpers.py tests/integration/runners.py
|
||||
venv/bin/mypy --strict phasm wat2wasm.py tests/integration/helpers.py tests/integration/memory.py tests/integration/runners.py
|
||||
|
||||
venv/.done: requirements.txt
|
||||
python3.12 -m venv venv
|
||||
|
||||
99
README.md
99
README.md
@ -3,46 +3,14 @@ phasm
|
||||
|
||||
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.
|
||||
|
||||
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.10,
|
||||
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.12 -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
|
||||
-------
|
||||
For more examples, see the examples directory.
|
||||
|
||||
From `examples/fib.py`:
|
||||
|
||||
```py
|
||||
def helper(n: u64, a: u64, b: u64) -> u64:
|
||||
if n < 1:
|
||||
@ -61,6 +29,55 @@ def fib(n: u64) -> u64:
|
||||
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
|
||||
--------
|
||||
- When importing and exporting unsigned values to WebAssembly, they will become
|
||||
@ -83,6 +100,9 @@ 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
|
||||
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
|
||||
-----------
|
||||
- p from python
|
||||
@ -96,10 +116,3 @@ References
|
||||
[3] https://webassembly.org/
|
||||
[4] https://www.w3.org/TR/wasm-core-1/
|
||||
[5] https://en.wikipedia.org/w/index.php?title=WebAssembly&oldid=1103639883
|
||||
[6] https://github.com/WebAssembly/wabt
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
- https://pengowray.github.io/wasm-ops/
|
||||
Shorthand overview for supported operations in WebAssembly.
|
||||
|
||||
14
TODO.md
14
TODO.md
@ -12,24 +12,16 @@
|
||||
- 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?
|
||||
- Does Subscript do what we want? It's a language feature rather a normal typed thing. How would you implement your own Subscript-able type?
|
||||
- Clean up Subscript implementation - it's half implemented in the compiler. Makes more sense to move more parts to stdlib_types.
|
||||
- Have a set of rules or guidelines for the constraint comments, they're messy.
|
||||
- Why is expression_subscript_bytes using a helper method but expression_subscript_static_array is not?
|
||||
- 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
|
||||
- Make prelude more an actual thing
|
||||
- Merge in compiler.INSTANCES
|
||||
- Make it less build in - have a environment class of some kind
|
||||
- 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
|
||||
- Filter out methods that aren't used; other the other way around (easier?) only add __ methods when needed
|
||||
- Move foldr into type class methods
|
||||
- Functions don't seem to be a thing on typing level yet?
|
||||
- Related to the FIXME in phasm_type3?
|
||||
- Type constuctor should also be able to constuct placeholders - somehow.
|
||||
|
||||
- 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.
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
<!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>
|
||||
@ -1,7 +0,0 @@
|
||||
@exported
|
||||
def index(inp: bytes, idx: u32) -> u8:
|
||||
return inp[idx]
|
||||
|
||||
@exported
|
||||
def length(inp: bytes) -> u32:
|
||||
return len(inp)
|
||||
@ -2,245 +2,68 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Examples - CRC32</title>
|
||||
<link rel="stylesheet" type="text/css" href="main.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Buffer</h1>
|
||||
<h1>Examples - CRC32</h1>
|
||||
|
||||
<a href="index.html">List</a> - <a href="crc32.py.html">Source</a> - <a href="crc32.wat.html">WebAssembly</a><br />
|
||||
<br />
|
||||
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="menu">
|
||||
<a href="index.html">List</a> - <a href="crc32.py.html">Source</a> - <a href="crc32.wat.html">WebAssembly</a><br />
|
||||
</div>
|
||||
|
||||
<h2>Measurement log</h2>
|
||||
<h3>AMD Ryzen 7 3700X 8-Core, Ubuntu 20.04, Linux 5.4.0-124-generic</h3>
|
||||
<h4>After 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>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>
|
||||
<div class="description">
|
||||
<p>
|
||||
This example shows a fold implementation of a <a href="https://en.wikipedia.org/wiki/Cyclic_redundancy_check">cyclic redundancy check</a>.
|
||||
There are actually many variations of these CRCs - this one is specifically know as CRC-32/ISO-HDLC.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<tr>
|
||||
<td>Lynx * 1048576</td>
|
||||
<td>Chromium 104.0.5112.101</td>
|
||||
<td>DevTools closed</td>
|
||||
<td>149.24</td>
|
||||
<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>
|
||||
<h3>Try it out!</h3>
|
||||
<div class="example">
|
||||
<textarea id="example-data" style="width: 75%; height: 4em;">The quick brown fox jumps over the lazy dog</textarea><br />
|
||||
<button type="click" id="example-click" disabled>Calculate</button>
|
||||
<input type="text" id="example-crc32" />
|
||||
</div>
|
||||
|
||||
<h4>Notes</h4>
|
||||
- Firefox seems faster than Chromium in my setup for Javascript, WebAssembly seems about the same.<br />
|
||||
- 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 />
|
||||
- WebAssembly in Firefox seems to slow down when doing a recording of the page load, which makes sense, but the Javascript does not.<br />
|
||||
<div class="example-list">
|
||||
<ul>
|
||||
<li><a href="#" data-n="123456789">crc32("123456789")</a> = cbf43926</li>
|
||||
<li><a href="#" data-n="Hello, world!">crc32("Hello, world!")</a> = ebe6c6e6</li>
|
||||
<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.
|
||||
|
||||
<script type="text/javascript" src="./include.js"></script>
|
||||
<script type="text/javascript">
|
||||
let importObject = {};
|
||||
The CRC of ASCII "123456789" is 0xcbf43926.
|
||||
|
||||
// Build up a JS version
|
||||
var makeCRCTable = function(){
|
||||
var c;
|
||||
var crcTable = [];
|
||||
for(var n =0; n < 256; n++){
|
||||
c = n;
|
||||
for(var k =0; k < 8; k++){
|
||||
c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
|
||||
}
|
||||
crcTable[n] = c;
|
||||
Examples of formats that use CRC-32/ISO-HDLC: ZIP, PNG, Gzip, ARJ.">crc32("CRC-32/ISO-HDLC...")</a> = 126afcf</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- We'll need to use some interface glue - WebAssembly doesn't let us pass strings directly. -->
|
||||
<script type="text/javascript" src="./include.js"></script>
|
||||
<script>
|
||||
let importObject = {};
|
||||
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();
|
||||
});
|
||||
}
|
||||
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>
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,55 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Examples - Fibonacci</title>
|
||||
<link rel="stylesheet" type="text/css" href="main.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Fibonacci</h1>
|
||||
<h1>Examples - Fibonacci</h1>
|
||||
|
||||
<a href="index.html">List</a> - <a href="fib.py.html">Source</a> - <a href="fib.wat.html">WebAssembly</a>
|
||||
<div class="menu">
|
||||
<a href="index.html">List</a> - <a href="fib.py.html">Source</a> - <a href="fib.wat.html">WebAssembly</a>
|
||||
</div>
|
||||
|
||||
<div style="white-space: pre;" id="results"></div>
|
||||
<div class="description">
|
||||
<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>
|
||||
|
||||
<script type="text/javascript" src="./include.js"></script>
|
||||
<script type="text/javascript">
|
||||
let importObject = {};
|
||||
<div class="example-list">
|
||||
<ul>
|
||||
<li><a href="#" data-n="5">fib(5)</a> = 5</li>
|
||||
<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>
|
||||
|
||||
function run_test(app, number, expected)
|
||||
{
|
||||
let actual = app.instance.exports.fib(BigInt(number));
|
||||
console.log(actual);
|
||||
<script>
|
||||
let importObject = {};
|
||||
let exampleN = document.querySelector('#example-n');
|
||||
let exampleClick = document.querySelector('#example-click');
|
||||
let exampleFib = document.querySelector('#example-fib');
|
||||
|
||||
test_result(BigInt(expected) == actual, {
|
||||
'summary': 'fib(' + number + ')',
|
||||
'attributes': {
|
||||
'expected': expected,
|
||||
'actual': actual
|
||||
},
|
||||
});
|
||||
}
|
||||
WebAssembly.instantiateStreaming(fetch('fib.wasm'), importObject)
|
||||
.then(app => {
|
||||
exampleClick.addEventListener('click', event => {
|
||||
let in_put = exampleN.value;
|
||||
let result = app.instance.exports.fib(BigInt(in_put));
|
||||
exampleFib.value = result;
|
||||
});
|
||||
exampleClick.removeAttribute('disabled');
|
||||
});
|
||||
|
||||
WebAssembly.instantiateStreaming(fetch('fib.wasm'), importObject)
|
||||
.then(app => {
|
||||
// 92: 7540113804746346429
|
||||
// i64: 9223372036854775807
|
||||
// 93: 12200160415121876738
|
||||
|
||||
run_test(app, 1, '1');
|
||||
run_test(app, 2, '1');
|
||||
run_test(app, 3, '2');
|
||||
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>
|
||||
for(let exmpl of document.querySelectorAll('a[data-n]') ) {
|
||||
exmpl.addEventListener('click', event => {
|
||||
exampleN.value = exmpl.getAttribute('data-n');
|
||||
exampleClick.click();
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -13,7 +13,3 @@ def fib(n: u64) -> u64:
|
||||
return 1
|
||||
|
||||
return helper(n - 1, 0, 1)
|
||||
|
||||
@exported
|
||||
def testEntry() -> u64:
|
||||
return fib(40)
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
<!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>
|
||||
@ -1,6 +0,0 @@
|
||||
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)
|
||||
@ -1,60 +0,0 @@
|
||||
<!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>
|
||||
@ -1,7 +0,0 @@
|
||||
@imported
|
||||
def log(no: i32) -> None:
|
||||
pass
|
||||
|
||||
@exported
|
||||
def mult_and_log(a: i32, b: i32) -> None:
|
||||
return log(a * b)
|
||||
@ -1,3 +1,6 @@
|
||||
/***
|
||||
* Allocates the given string in the given application's memory
|
||||
*/
|
||||
function alloc_bytes(app, data)
|
||||
{
|
||||
let stdlib_types___alloc_bytes__ = app.instance.exports['stdlib.types.__alloc_bytes__']
|
||||
@ -14,85 +17,12 @@ function alloc_bytes(app, data)
|
||||
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)
|
||||
{
|
||||
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);
|
||||
return n >>> 0;
|
||||
}
|
||||
|
||||
@ -1,19 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Examples</title>
|
||||
<link rel="stylesheet" type="text/css" href="main.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Examples</h1>
|
||||
<h2>Standard</h2>
|
||||
|
||||
<h2>Functions</h2>
|
||||
<ul>
|
||||
<li><a href="crc32.html">CRC32</a></li>
|
||||
<li><a href="fib.html">Fibonacci</a></li>
|
||||
</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>
|
||||
</html>
|
||||
|
||||
31
examples/main.css
Normal file
31
examples/main.css
Normal file
@ -0,0 +1,31 @@
|
||||
: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;
|
||||
}
|
||||
@ -5,8 +5,9 @@ Functions for using this module from CLI
|
||||
import sys
|
||||
|
||||
from .compiler import phasm_compile
|
||||
from .optimise.removeunusedfuncs import removeunusedfuncs
|
||||
from .parser import phasm_parse
|
||||
from .type3.entry import phasm_type3
|
||||
from .type5.solver import phasm_type5
|
||||
|
||||
|
||||
def main(source: str, sink: str) -> int:
|
||||
@ -18,8 +19,9 @@ def main(source: str, sink: str) -> int:
|
||||
code_py = fil.read()
|
||||
|
||||
our_module = phasm_parse(code_py)
|
||||
phasm_type3(our_module, verbose=False)
|
||||
phasm_type5(our_module, verbose=False)
|
||||
wasm_module = phasm_compile(our_module)
|
||||
removeunusedfuncs(wasm_module)
|
||||
code_wat = wasm_module.to_wat()
|
||||
|
||||
with open(sink, 'w') as fil:
|
||||
|
||||
445
phasm/build/base.py
Normal file
445
phasm/build/base.py
Normal file
@ -0,0 +1,445 @@
|
||||
"""
|
||||
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,
|
||||
)
|
||||
77
phasm/build/default.py
Normal file
77
phasm/build/default.py
Normal file
@ -0,0 +1,77 @@
|
||||
"""
|
||||
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)
|
||||
0
phasm/build/typeclasses/__init__.py
Normal file
0
phasm/build/typeclasses/__init__.py
Normal file
214
phasm/build/typeclasses/bits.py
Normal file
214
phasm/build/typeclasses/bits.py
Normal file
@ -0,0 +1,214 @@
|
||||
"""
|
||||
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,
|
||||
})
|
||||
143
phasm/build/typeclasses/convertable.py
Normal file
143
phasm/build/typeclasses/convertable.py
Normal file
@ -0,0 +1,143 @@
|
||||
"""
|
||||
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,
|
||||
})
|
||||
159
phasm/build/typeclasses/eq.py
Normal file
159
phasm/build/typeclasses/eq.py
Normal file
@ -0,0 +1,159 @@
|
||||
"""
|
||||
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,
|
||||
})
|
||||
216
phasm/build/typeclasses/extendable.py
Normal file
216
phasm/build/typeclasses/extendable.py
Normal file
@ -0,0 +1,216 @@
|
||||
"""
|
||||
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,
|
||||
})
|
||||
54
phasm/build/typeclasses/floating.py
Normal file
54
phasm/build/typeclasses/floating.py
Normal file
@ -0,0 +1,54 @@
|
||||
"""
|
||||
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,
|
||||
})
|
||||
643
phasm/build/typeclasses/foldable.py
Normal file
643
phasm/build/typeclasses/foldable.py
Normal file
@ -0,0 +1,643 @@
|
||||
"""
|
||||
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,
|
||||
})
|
||||
107
phasm/build/typeclasses/fractional.py
Normal file
107
phasm/build/typeclasses/fractional.py
Normal file
@ -0,0 +1,107 @@
|
||||
"""
|
||||
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,
|
||||
})
|
||||
88
phasm/build/typeclasses/integral.py
Normal file
88
phasm/build/typeclasses/integral.py
Normal file
@ -0,0 +1,88 @@
|
||||
"""
|
||||
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,
|
||||
})
|
||||
89
phasm/build/typeclasses/intnum.py
Normal file
89
phasm/build/typeclasses/intnum.py
Normal file
@ -0,0 +1,89 @@
|
||||
"""
|
||||
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,
|
||||
})
|
||||
227
phasm/build/typeclasses/natnum.py
Normal file
227
phasm/build/typeclasses/natnum.py
Normal file
@ -0,0 +1,227 @@
|
||||
"""
|
||||
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,
|
||||
})
|
||||
383
phasm/build/typeclasses/ord.py
Normal file
383
phasm/build/typeclasses/ord.py
Normal file
@ -0,0 +1,383 @@
|
||||
"""
|
||||
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,
|
||||
})
|
||||
58
phasm/build/typeclasses/promotable.py
Normal file
58
phasm/build/typeclasses/promotable.py
Normal file
@ -0,0 +1,58 @@
|
||||
"""
|
||||
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,
|
||||
})
|
||||
95
phasm/build/typeclasses/reinterpretable.py
Normal file
95
phasm/build/typeclasses/reinterpretable.py
Normal file
@ -0,0 +1,95 @@
|
||||
"""
|
||||
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,
|
||||
})
|
||||
61
phasm/build/typeclasses/sized.py
Normal file
61
phasm/build/typeclasses/sized.py
Normal file
@ -0,0 +1,61 @@
|
||||
"""
|
||||
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,
|
||||
})
|
||||
144
phasm/build/typeclasses/subscriptable.py
Normal file
144
phasm/build/typeclasses/subscriptable.py
Normal file
@ -0,0 +1,144 @@
|
||||
"""
|
||||
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,
|
||||
})
|
||||
86
phasm/build/typeclassregistry.py
Normal file
86
phasm/build/typeclassregistry.py
Normal file
@ -0,0 +1,86 @@
|
||||
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)
|
||||
154
phasm/build/typerouter.py
Normal file
154
phasm/build/typerouter.py
Normal file
@ -0,0 +1,154 @@
|
||||
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,13 +3,13 @@ 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
|
||||
"""
|
||||
from typing import Generator
|
||||
from typing import Any, Generator
|
||||
|
||||
from . import ourlang, prelude
|
||||
from .type3.types import Type3, TypeApplication_Struct
|
||||
from . import ourlang
|
||||
from .type5 import typeexpr as type5typeexpr
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
@ -17,32 +17,27 @@ def phasm_render(inp: ourlang.Module) -> str:
|
||||
|
||||
Statements = Generator[str, None, None]
|
||||
|
||||
def type3(inp: Type3) -> str:
|
||||
def type5(mod: ourlang.Module[Any], inp: type5typeexpr.TypeExpr) -> str:
|
||||
"""
|
||||
Render: type's name
|
||||
"""
|
||||
if inp is prelude.none:
|
||||
return 'None'
|
||||
return mod.build.type5_name(inp)
|
||||
|
||||
return inp.name
|
||||
|
||||
def struct_definition(inp: ourlang.StructDefinition) -> str:
|
||||
def struct_definition(mod: ourlang.Module[Any], inp: ourlang.StructDefinition) -> str:
|
||||
"""
|
||||
Render: TypeStruct's definition
|
||||
"""
|
||||
assert isinstance(inp.struct_type3.application, TypeApplication_Struct)
|
||||
|
||||
result = f'class {inp.struct_type3.name}:\n'
|
||||
for mem, typ in inp.struct_type3.application.arguments:
|
||||
result += f' {mem}: {type3(typ)}\n'
|
||||
result = f'class {inp.struct_type5.name}:\n'
|
||||
for mem, typ in inp.struct_type5.fields:
|
||||
result += f' {mem}: {type5(mod, typ)}\n'
|
||||
|
||||
return result
|
||||
|
||||
def constant_definition(inp: ourlang.ModuleConstantDef) -> str:
|
||||
def constant_definition(mod: ourlang.Module[Any], inp: ourlang.ModuleConstantDef) -> str:
|
||||
"""
|
||||
Render: Module Constant's definition
|
||||
"""
|
||||
return f'{inp.name}: {type3(inp.type3)} = {expression(inp.constant)}\n'
|
||||
return f'{inp.name}: {type5(mod, inp.type5)} = {expression(inp.constant)}\n'
|
||||
|
||||
def expression(inp: ourlang.Expression) -> str:
|
||||
"""
|
||||
@ -63,7 +58,7 @@ def expression(inp: ourlang.Expression) -> str:
|
||||
) + ', )'
|
||||
|
||||
if isinstance(inp, ourlang.ConstantStruct):
|
||||
return inp.struct_type3.name + '(' + ', '.join(
|
||||
return inp.struct_type5.name + '(' + ', '.join(
|
||||
expression(x)
|
||||
for x in inp.value
|
||||
) + ')'
|
||||
@ -81,7 +76,7 @@ def expression(inp: ourlang.Expression) -> str:
|
||||
)
|
||||
|
||||
if isinstance(inp.function, ourlang.StructConstructor):
|
||||
return f'{inp.function.struct_type3.name}({args})'
|
||||
return f'{inp.function.struct_type5.name}({args})'
|
||||
|
||||
return f'{inp.function.name}({args})'
|
||||
|
||||
@ -131,7 +126,7 @@ def statement(inp: ourlang.Statement) -> Statements:
|
||||
|
||||
raise NotImplementedError(statement, inp)
|
||||
|
||||
def function(inp: ourlang.Function) -> str:
|
||||
def function(mod: ourlang.Module[Any], inp: ourlang.Function) -> str:
|
||||
"""
|
||||
Render: Function body
|
||||
|
||||
@ -144,12 +139,17 @@ def function(inp: ourlang.Function) -> str:
|
||||
if inp.imported:
|
||||
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(
|
||||
f'{p.name}: {type3(p.type3)}'
|
||||
for p in inp.posonlyargs
|
||||
f'{arg_name}: {type5(mod, arg_type)}'
|
||||
for arg_name, arg_type in zip(inp.arg_names, fn_args, strict=True)
|
||||
)
|
||||
|
||||
result += f'def {inp.name}({args}) -> {type3(inp.returns_type3)}:\n'
|
||||
result += f'def {inp.name}({args}) -> {type5(mod, ret_type5)}:\n'
|
||||
|
||||
if inp.imported:
|
||||
result += ' pass\n'
|
||||
@ -161,7 +161,7 @@ def function(inp: ourlang.Function) -> str:
|
||||
return result
|
||||
|
||||
|
||||
def module(inp: ourlang.Module) -> str:
|
||||
def module(inp: ourlang.Module[Any]) -> str:
|
||||
"""
|
||||
Render: Module
|
||||
"""
|
||||
@ -170,20 +170,20 @@ def module(inp: ourlang.Module) -> str:
|
||||
for struct in inp.struct_definitions.values():
|
||||
if result:
|
||||
result += '\n'
|
||||
result += struct_definition(struct)
|
||||
result += struct_definition(inp, struct)
|
||||
|
||||
for cdef in inp.constant_defs.values():
|
||||
if result:
|
||||
result += '\n'
|
||||
result += constant_definition(cdef)
|
||||
result += constant_definition(inp, cdef)
|
||||
|
||||
for func in inp.functions.values():
|
||||
if func.lineno < 0:
|
||||
# Builtin (-2) or auto generated (-1)
|
||||
if isinstance(func, ourlang.StructConstructor):
|
||||
# Auto generated
|
||||
continue
|
||||
|
||||
if result:
|
||||
result += '\n'
|
||||
result += function(func)
|
||||
result += function(inp, func)
|
||||
|
||||
return result
|
||||
|
||||
@ -2,114 +2,126 @@
|
||||
This module contains the code to convert parsed Ourlang into WebAssembly code
|
||||
"""
|
||||
import struct
|
||||
from typing import List
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, List, TypeGuard
|
||||
|
||||
from . import ourlang, prelude, wasm
|
||||
from .runtime import calculate_alloc_size, calculate_member_offset
|
||||
from . import ourlang, wasm
|
||||
from .build.base import BuildBase, TypeInfo
|
||||
from .build.typerouter import BuildTypeRouter
|
||||
from .stdlib import alloc as stdlib_alloc
|
||||
from .stdlib import types as stdlib_types
|
||||
from .stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP
|
||||
from .type3.functions import FunctionArgument, TypeVariable
|
||||
from .type3.routers import NoRouteForTypeException, TypeApplicationRouter
|
||||
from .type3.typeclasses import Type3ClassMethod
|
||||
from .type3.types import (
|
||||
IntType3,
|
||||
Type3,
|
||||
TypeApplication_Struct,
|
||||
TypeApplication_Type,
|
||||
TypeApplication_TypeInt,
|
||||
TypeApplication_TypeStar,
|
||||
TypeConstructor_DynamicArray,
|
||||
TypeConstructor_Function,
|
||||
TypeConstructor_StaticArray,
|
||||
TypeConstructor_Tuple,
|
||||
from .type5.constrainedexpr import ConstrainedExpr
|
||||
from .type5.typeexpr import (
|
||||
AtomicType,
|
||||
TypeApplication,
|
||||
TypeExpr,
|
||||
TypeVariable,
|
||||
is_concrete,
|
||||
replace_variable,
|
||||
)
|
||||
from .wasm import (
|
||||
WasmTypeFloat32,
|
||||
WasmTypeFloat64,
|
||||
WasmTypeInt32,
|
||||
WasmTypeInt64,
|
||||
)
|
||||
from .wasmgenerator import Generator as WasmGenerator
|
||||
|
||||
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before your program can be compiled'
|
||||
TYPE5_ASSERTION_ERROR = 'You must call phasm_type5 after calling phasm_parse before your program can be compiled'
|
||||
|
||||
def phasm_compile(inp: ourlang.Module) -> wasm.Module:
|
||||
def phasm_compile(inp: ourlang.Module[WasmGenerator]) -> wasm.Module:
|
||||
"""
|
||||
Public method for compiling a parsed Phasm module into
|
||||
a WebAssembly module
|
||||
"""
|
||||
return module(inp)
|
||||
|
||||
def type3(inp: Type3) -> wasm.WasmType:
|
||||
def type5(mod: ourlang.Module[WasmGenerator], inp: TypeExpr) -> wasm.WasmType:
|
||||
"""
|
||||
Compile: type
|
||||
|
||||
Types are used for example in WebAssembly function parameters
|
||||
and return types.
|
||||
"""
|
||||
typ_info = TYPE_INFO_MAP.get(inp.name, TYPE_INFO_CONSTRUCTED)
|
||||
typ_info = mod.build.type_info_map.get(inp.name)
|
||||
if typ_info is None:
|
||||
typ_info = mod.build.type_info_constructed
|
||||
|
||||
return typ_info.wasm_type()
|
||||
|
||||
def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.TupleInstantiation) -> None:
|
||||
@dataclass
|
||||
class TupleInstantiationResult:
|
||||
args: list[TypeExpr]
|
||||
alloc_size: int
|
||||
header_value: int | None
|
||||
|
||||
class TupleInstantiationRouter(BuildTypeRouter[TupleInstantiationResult]):
|
||||
__slots__ = ('el_count', )
|
||||
|
||||
el_count: int
|
||||
|
||||
def __init__(self, build: BuildBase[Any], el_count: int) -> None:
|
||||
super().__init__(build)
|
||||
self.el_count = el_count
|
||||
|
||||
def when_dynamic_array(self, da_arg: TypeExpr) -> TupleInstantiationResult:
|
||||
return TupleInstantiationResult(
|
||||
args=[da_arg for _ in range(self.el_count)],
|
||||
alloc_size=5 + self.el_count * self.build.type5_alloc_size_member(da_arg),
|
||||
header_value=self.el_count,
|
||||
)
|
||||
|
||||
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> TupleInstantiationResult:
|
||||
return TupleInstantiationResult(
|
||||
args=[sa_typ for _ in range(sa_len)],
|
||||
alloc_size=5 + sa_len * self.build.type5_alloc_size_member(sa_typ),
|
||||
header_value=None,
|
||||
)
|
||||
|
||||
def when_tuple(self, tp_args: list[TypeExpr]) -> TupleInstantiationResult:
|
||||
return TupleInstantiationResult(
|
||||
args=tp_args,
|
||||
alloc_size=sum(
|
||||
self.build.type5_alloc_size_member(x)
|
||||
for x in tp_args
|
||||
),
|
||||
header_value=None,
|
||||
)
|
||||
|
||||
def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.TupleInstantiation) -> None:
|
||||
"""
|
||||
Compile: Instantiation (allocation) of a tuple
|
||||
"""
|
||||
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
|
||||
assert _is_concrete(inp.type5), TYPE5_ASSERTION_ERROR
|
||||
|
||||
args: tuple[Type3, ...]
|
||||
|
||||
alloc_size_header = None
|
||||
|
||||
if isinstance(inp.type3.application, TypeApplication_Type):
|
||||
# Possibly paranoid assert. If we have a future variadic type,
|
||||
# does it also do this tuple instantation like this?
|
||||
assert isinstance(inp.type3.application.constructor, TypeConstructor_DynamicArray)
|
||||
|
||||
sa_type, = inp.type3.application.arguments
|
||||
|
||||
args = tuple(sa_type for _ in inp.elements)
|
||||
# Can't use calculate_alloc_size directly since that doesn't
|
||||
# know the dynamic array's length
|
||||
alloc_size = 4 + calculate_alloc_size(sa_type, is_member=True) * len(inp.elements)
|
||||
alloc_size_header = len(inp.elements)
|
||||
elif isinstance(inp.type3.application, TypeApplication_TypeStar):
|
||||
# Possibly paranoid assert. If we have a future variadic type,
|
||||
# does it also do this tuple instantation like this?
|
||||
assert isinstance(inp.type3.application.constructor, TypeConstructor_Tuple)
|
||||
|
||||
args = inp.type3.application.arguments
|
||||
alloc_size = calculate_alloc_size(inp.type3, is_member=False)
|
||||
elif isinstance(inp.type3.application, TypeApplication_TypeInt):
|
||||
# Possibly paranoid assert. If we have a future type of kind * -> Int -> *,
|
||||
# does it also do this tuple instantation like this?
|
||||
assert isinstance(inp.type3.application.constructor, TypeConstructor_StaticArray)
|
||||
|
||||
sa_type, sa_len = inp.type3.application.arguments
|
||||
|
||||
args = tuple(sa_type for _ in range(sa_len.value))
|
||||
alloc_size = calculate_alloc_size(inp.type3, is_member=False)
|
||||
else:
|
||||
raise NotImplementedError('tuple_instantiation', inp.type3)
|
||||
result = TupleInstantiationRouter(mod.build, len(inp.elements))(inp.type5)
|
||||
|
||||
comment_elements = ''
|
||||
for element in inp.elements:
|
||||
assert element.type3 is not None, TYPE3_ASSERTION_ERROR
|
||||
comment_elements += f'{element.type3.name}, '
|
||||
assert _is_concrete(element.type5), TYPE5_ASSERTION_ERROR
|
||||
comment_elements += f'{mod.build.type5_name(element.type5)}, '
|
||||
|
||||
tmp_var = wgn.temp_var_i32('tuple_adr')
|
||||
wgn.add_statement('nop', comment=f'{tmp_var.name} := ({comment_elements})')
|
||||
|
||||
# Allocated the required amounts of bytes in memory
|
||||
wgn.i32.const(alloc_size)
|
||||
wgn.i32.const(result.alloc_size)
|
||||
wgn.call(stdlib_alloc.__alloc__)
|
||||
wgn.local.set(tmp_var)
|
||||
|
||||
if alloc_size_header is not None:
|
||||
if result.header_value is not None:
|
||||
wgn.local.get(tmp_var)
|
||||
wgn.i32.const(alloc_size_header)
|
||||
wgn.i32.const(result.header_value )
|
||||
wgn.i32.store()
|
||||
|
||||
# Store each element individually
|
||||
offset = 0 if alloc_size_header is None else 4
|
||||
for element, exp_type3 in zip(inp.elements, args, strict=True):
|
||||
assert element.type3 == exp_type3
|
||||
offset = 0 if result.header_value is None else 4
|
||||
for element in inp.elements:
|
||||
assert _is_concrete(element.type5), TYPE5_ASSERTION_ERROR
|
||||
|
||||
exp_type_info = TYPE_INFO_MAP.get(exp_type3.name, TYPE_INFO_CONSTRUCTED)
|
||||
exp_type_info = mod.build.type_info_map.get(element.type5.name)
|
||||
if exp_type_info is None:
|
||||
exp_type_info = mod.build.type_info_constructed
|
||||
|
||||
wgn.add_statement('nop', comment='PRE')
|
||||
wgn.local.get(tmp_var)
|
||||
@ -117,81 +129,139 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu
|
||||
wgn.add_statement(exp_type_info.wasm_store_func, 'offset=' + str(offset))
|
||||
wgn.add_statement('nop', comment='POST')
|
||||
|
||||
offset += calculate_alloc_size(exp_type3, is_member=True)
|
||||
offset += mod.build.type5_alloc_size_member(element.type5)
|
||||
|
||||
# Return the allocated address
|
||||
wgn.local.get(tmp_var)
|
||||
|
||||
def expression_subscript_bytes(
|
||||
attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript],
|
||||
) -> None:
|
||||
wgn, mod, inp = attrs
|
||||
|
||||
expression(wgn, mod, inp.varref)
|
||||
expression(wgn, mod, inp.index)
|
||||
wgn.call(stdlib_types.__subscript_bytes__)
|
||||
|
||||
def expression_subscript_static_array(
|
||||
attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript],
|
||||
args: tuple[Type3, IntType3],
|
||||
) -> None:
|
||||
wgn, mod, inp = attrs
|
||||
|
||||
el_type, el_len = args
|
||||
|
||||
# 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
|
||||
|
||||
expression(wgn, mod, inp.varref)
|
||||
|
||||
tmp_var = wgn.temp_var_i32('index')
|
||||
expression(wgn, mod, inp.index)
|
||||
wgn.local.tee(tmp_var)
|
||||
|
||||
# Out of bounds check based on el_len.value
|
||||
wgn.i32.const(el_len.value)
|
||||
wgn.i32.ge_u()
|
||||
with wgn.if_():
|
||||
wgn.unreachable(comment='Out of bounds')
|
||||
|
||||
el_type_info = TYPE_INFO_MAP.get(el_type.name, TYPE_INFO_CONSTRUCTED)
|
||||
|
||||
wgn.local.get(tmp_var)
|
||||
wgn.i32.const(el_type_info.alloc_size)
|
||||
wgn.i32.mul()
|
||||
wgn.i32.add()
|
||||
|
||||
wgn.add_statement(el_type_info.wasm_load_func)
|
||||
|
||||
def expression_subscript_tuple(
|
||||
attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript],
|
||||
args: tuple[Type3, ...],
|
||||
) -> None:
|
||||
wgn, mod, inp = attrs
|
||||
|
||||
def expression_subscript_tuple(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.Subscript) -> None:
|
||||
assert isinstance(inp.index, ourlang.ConstantPrimitive)
|
||||
assert isinstance(inp.index.value, int)
|
||||
|
||||
offset = 0
|
||||
for el_type in args[0:inp.index.value]:
|
||||
assert el_type is not None, TYPE3_ASSERTION_ERROR
|
||||
el_type_info = TYPE_INFO_MAP.get(el_type.name, TYPE_INFO_CONSTRUCTED)
|
||||
offset += el_type_info.alloc_size
|
||||
assert _is_concrete(inp.varref.type5), TYPE5_ASSERTION_ERROR
|
||||
args = mod.build.type5_is_tuple(inp.varref.type5)
|
||||
assert args is not None
|
||||
|
||||
offset = sum(map(
|
||||
mod.build.type5_alloc_size_member,
|
||||
args[0:inp.index.value]
|
||||
))
|
||||
|
||||
el_type = args[inp.index.value]
|
||||
assert el_type is not None, TYPE3_ASSERTION_ERROR
|
||||
el_type_info = mod.build.type_info_map.get(el_type.name)
|
||||
if el_type_info is None:
|
||||
el_type_info = mod.build.type_info_constructed
|
||||
|
||||
expression(wgn, mod, inp.varref)
|
||||
|
||||
el_type_info = TYPE_INFO_MAP.get(el_type.name, TYPE_INFO_CONSTRUCTED)
|
||||
wgn.add_statement(el_type_info.wasm_load_func, f'offset={offset}')
|
||||
|
||||
SUBSCRIPT_ROUTER = TypeApplicationRouter[tuple[WasmGenerator, ourlang.Module, ourlang.Subscript], None]()
|
||||
SUBSCRIPT_ROUTER.add_n(prelude.bytes_, expression_subscript_bytes)
|
||||
SUBSCRIPT_ROUTER.add(prelude.static_array, expression_subscript_static_array)
|
||||
SUBSCRIPT_ROUTER.add(prelude.tuple_, expression_subscript_tuple)
|
||||
def expression_subscript_operator(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.Subscript) -> None:
|
||||
assert _is_concrete(inp.type5), TYPE5_ASSERTION_ERROR
|
||||
|
||||
def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression) -> None:
|
||||
ftp5 = mod.build.type_classes['Subscriptable'].operators['[]']
|
||||
fn_args = mod.build.type5_is_function(ftp5)
|
||||
assert fn_args is not None
|
||||
t_a = fn_args[0]
|
||||
assert isinstance(t_a, TypeApplication)
|
||||
t = t_a.constructor
|
||||
a = t_a.argument
|
||||
|
||||
assert isinstance(t, TypeVariable)
|
||||
assert isinstance(a, TypeVariable)
|
||||
|
||||
assert isinstance(inp.varref.type5, TypeApplication)
|
||||
t_expr = inp.varref.type5.constructor
|
||||
a_expr = inp.varref.type5.argument
|
||||
|
||||
_expression_binary_operator_or_function_call(
|
||||
wgn,
|
||||
mod,
|
||||
ourlang.BuiltinFunction('[]', ftp5),
|
||||
{
|
||||
t: t_expr,
|
||||
a: a_expr,
|
||||
},
|
||||
[inp.varref, inp.index],
|
||||
inp.type5,
|
||||
)
|
||||
|
||||
def expression_binary_op(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.BinaryOp) -> None:
|
||||
assert _is_concrete(inp.type5), TYPE5_ASSERTION_ERROR
|
||||
|
||||
_expression_binary_operator_or_function_call(
|
||||
wgn,
|
||||
mod,
|
||||
inp.operator,
|
||||
inp.polytype_substitutions,
|
||||
[inp.left, inp.right],
|
||||
inp.type5,
|
||||
)
|
||||
|
||||
def expression_function_call(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.FunctionCall) -> None:
|
||||
assert _is_concrete(inp.type5), TYPE5_ASSERTION_ERROR
|
||||
|
||||
_expression_binary_operator_or_function_call(
|
||||
wgn,
|
||||
mod,
|
||||
inp.function,
|
||||
inp.polytype_substitutions,
|
||||
inp.arguments,
|
||||
inp.type5,
|
||||
)
|
||||
|
||||
def _expression_binary_operator_or_function_call(
|
||||
wgn: WasmGenerator,
|
||||
mod: ourlang.Module[WasmGenerator],
|
||||
function: ourlang.Function | ourlang.FunctionParam,
|
||||
polytype_substitutions: dict[TypeVariable, TypeExpr],
|
||||
arguments: list[ourlang.Expression],
|
||||
ret_type5: TypeExpr,
|
||||
) -> None:
|
||||
for arg in arguments:
|
||||
expression(wgn, mod, arg)
|
||||
|
||||
if isinstance(function, ourlang.BuiltinFunction):
|
||||
ftp5 = function.type5
|
||||
if isinstance(ftp5, ConstrainedExpr):
|
||||
cexpr = ftp5
|
||||
ftp5 = ftp5.expr
|
||||
for tvar in cexpr.variables:
|
||||
ftp5 = replace_variable(ftp5, tvar, polytype_substitutions[tvar])
|
||||
assert _is_concrete(ftp5), TYPE5_ASSERTION_ERROR
|
||||
|
||||
try:
|
||||
method_type, method_router = mod.build.methods[function.name]
|
||||
except KeyError:
|
||||
method_type, method_router = mod.build.operators[function.name]
|
||||
|
||||
impl_lookup = method_router.get((ftp5, ))
|
||||
assert impl_lookup is not None, (function.name, ftp5, )
|
||||
kwargs, impl = impl_lookup
|
||||
impl(wgn, kwargs)
|
||||
return
|
||||
|
||||
if isinstance(function, ourlang.FunctionParam):
|
||||
fn_args = mod.build.type5_is_function(function.type5)
|
||||
assert fn_args is not None, function.type5
|
||||
|
||||
params = [
|
||||
type5(mod, x)
|
||||
for x in fn_args
|
||||
]
|
||||
|
||||
result = params.pop()
|
||||
|
||||
wgn.add_statement('local.get', '${}'.format(function.name))
|
||||
wgn.call_indirect(params=params, result=result)
|
||||
return
|
||||
|
||||
# TODO: Do similar subsitutions like we do for BuiltinFunction
|
||||
# when we get user space polymorphic functions
|
||||
# And then do similar lookup, and ensure we generate code for that variant
|
||||
|
||||
wgn.call(function.name)
|
||||
|
||||
def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.Expression) -> None:
|
||||
"""
|
||||
Compile: Any expression
|
||||
"""
|
||||
@ -200,35 +270,30 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
|
||||
raise Exception
|
||||
|
||||
if isinstance(inp, ourlang.ConstantPrimitive):
|
||||
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
|
||||
assert _is_concrete(inp.type5), TYPE5_ASSERTION_ERROR
|
||||
|
||||
if inp.type3 in (prelude.i8, prelude.u8, ):
|
||||
# No native u8 type - treat as i32, with caution
|
||||
type_info = mod.build.type_info_map[inp.type5.name]
|
||||
if type_info.wasm_type is WasmTypeInt32:
|
||||
assert isinstance(inp.value, int)
|
||||
wgn.i32.const(inp.value)
|
||||
return
|
||||
|
||||
if inp.type3 in (prelude.i32, prelude.u32, ):
|
||||
assert isinstance(inp.value, int)
|
||||
wgn.i32.const(inp.value)
|
||||
return
|
||||
|
||||
if inp.type3 in (prelude.i64, prelude.u64, ):
|
||||
if type_info.wasm_type is WasmTypeInt64:
|
||||
assert isinstance(inp.value, int)
|
||||
wgn.i64.const(inp.value)
|
||||
return
|
||||
|
||||
if inp.type3 == prelude.f32:
|
||||
if type_info.wasm_type is WasmTypeFloat32:
|
||||
assert isinstance(inp.value, float)
|
||||
wgn.f32.const(inp.value)
|
||||
return
|
||||
|
||||
if inp.type3 == prelude.f64:
|
||||
if type_info.wasm_type is WasmTypeFloat64:
|
||||
assert isinstance(inp.value, float)
|
||||
wgn.f64.const(inp.value)
|
||||
return
|
||||
|
||||
raise NotImplementedError(f'Constants with type {inp.type3:s}')
|
||||
raise NotImplementedError(inp.type5)
|
||||
|
||||
if isinstance(inp, ourlang.ConstantBytes):
|
||||
assert inp.data_block.address is not None, 'Value not allocated'
|
||||
@ -241,9 +306,9 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
|
||||
return
|
||||
|
||||
if isinstance(inp.variable, ourlang.ModuleConstantDef):
|
||||
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
|
||||
assert _is_concrete(inp.type5), TYPE5_ASSERTION_ERROR
|
||||
|
||||
if inp.type3.name not in TYPE_INFO_MAP:
|
||||
if inp.type5.name not in mod.build.type_info_map:
|
||||
assert isinstance(inp.variable.constant, (ourlang.ConstantBytes, ourlang.ConstantStruct, ourlang.ConstantTuple, ))
|
||||
|
||||
address = inp.variable.constant.data_block.address
|
||||
@ -257,80 +322,11 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
|
||||
raise NotImplementedError(expression, inp.variable)
|
||||
|
||||
if isinstance(inp, ourlang.BinaryOp):
|
||||
expression(wgn, mod, inp.left)
|
||||
expression(wgn, mod, inp.right)
|
||||
|
||||
type_var_map: dict[TypeVariable, Type3] = {}
|
||||
|
||||
for type_var, arg_expr in zip(inp.operator.signature.args, [inp.left, inp.right, inp], strict=True):
|
||||
assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR
|
||||
|
||||
if isinstance(type_var, Type3):
|
||||
# Fixed type, not part of the lookup requirements
|
||||
continue
|
||||
|
||||
if isinstance(type_var, TypeVariable):
|
||||
type_var_map[type_var] = arg_expr.type3
|
||||
continue
|
||||
|
||||
if isinstance(type_var, FunctionArgument):
|
||||
# Fixed type, not part of the lookup requirements
|
||||
continue
|
||||
|
||||
raise NotImplementedError(type_var, arg_expr.type3)
|
||||
|
||||
router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.operator]
|
||||
router(wgn, type_var_map)
|
||||
expression_binary_op(wgn, mod, inp)
|
||||
return
|
||||
|
||||
if isinstance(inp, ourlang.FunctionCall):
|
||||
for arg in inp.arguments:
|
||||
expression(wgn, mod, arg)
|
||||
|
||||
if isinstance(inp.function, Type3ClassMethod):
|
||||
# FIXME: Duplicate code with BinaryOp
|
||||
type_var_map = {}
|
||||
|
||||
for type_var, arg_expr in zip(inp.function.signature.args, inp.arguments + [inp], strict=True):
|
||||
assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR
|
||||
|
||||
if isinstance(type_var, Type3):
|
||||
# Fixed type, not part of the lookup requirements
|
||||
continue
|
||||
|
||||
if isinstance(type_var, TypeVariable):
|
||||
type_var_map[type_var] = arg_expr.type3
|
||||
continue
|
||||
|
||||
if isinstance(type_var, FunctionArgument):
|
||||
# Fixed type, not part of the lookup requirements
|
||||
continue
|
||||
|
||||
raise NotImplementedError(type_var, arg_expr.type3)
|
||||
|
||||
router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.function]
|
||||
try:
|
||||
router(wgn, type_var_map)
|
||||
except NoRouteForTypeException:
|
||||
raise NotImplementedError(str(inp.function), type_var_map)
|
||||
return
|
||||
|
||||
if isinstance(inp.function, ourlang.FunctionParam):
|
||||
assert isinstance(inp.function.type3.application.constructor, TypeConstructor_Function)
|
||||
|
||||
params = [
|
||||
type3(x).to_wat()
|
||||
for x in inp.function.type3.application.arguments
|
||||
]
|
||||
|
||||
result = params.pop()
|
||||
|
||||
params_str = ' '.join(params)
|
||||
wgn.add_statement('local.get', '${}'.format(inp.function.name))
|
||||
wgn.add_statement(f'call_indirect (param {params_str}) (result {result})')
|
||||
return
|
||||
|
||||
wgn.add_statement('call', '${}'.format(inp.function.name))
|
||||
expression_function_call(wgn, mod, inp)
|
||||
return
|
||||
|
||||
if isinstance(inp, ourlang.FunctionReference):
|
||||
@ -347,43 +343,60 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
|
||||
return
|
||||
|
||||
if isinstance(inp, ourlang.Subscript):
|
||||
assert inp.varref.type3 is not None, TYPE3_ASSERTION_ERROR
|
||||
assert _is_concrete(inp.type5), TYPE5_ASSERTION_ERROR
|
||||
assert _is_concrete(inp.varref.type5), TYPE5_ASSERTION_ERROR
|
||||
assert _is_concrete(inp.index.type5), TYPE5_ASSERTION_ERROR
|
||||
|
||||
# Type checker guarantees we don't get routing errors
|
||||
SUBSCRIPT_ROUTER((wgn, mod, inp, ), inp.varref.type3)
|
||||
if mod.build.type5_is_tuple(inp.varref.type5):
|
||||
expression_subscript_tuple(wgn, mod, inp)
|
||||
return
|
||||
|
||||
expression_subscript_operator(wgn, mod, inp)
|
||||
return
|
||||
|
||||
if isinstance(inp, ourlang.AccessStructMember):
|
||||
assert inp.struct_type3 is not None, TYPE3_ASSERTION_ERROR
|
||||
assert _is_concrete(inp.varref.type5), TYPE5_ASSERTION_ERROR
|
||||
|
||||
assert isinstance(inp.struct_type3.application, TypeApplication_Struct)
|
||||
st_args = mod.build.type5_is_struct(inp.varref.type5)
|
||||
assert st_args is not None
|
||||
|
||||
member_type = dict(inp.struct_type3.application.arguments)[inp.member]
|
||||
member_type_info = TYPE_INFO_MAP.get(member_type.name, TYPE_INFO_CONSTRUCTED)
|
||||
member_type = dict(st_args)[inp.member]
|
||||
member_type_info = mod.build.type_info_map.get(member_type.name)
|
||||
if member_type_info is None:
|
||||
member_type_info = mod.build.type_info_constructed
|
||||
offset = _type5_struct_offset(mod.build, st_args, inp.member)
|
||||
|
||||
expression(wgn, mod, inp.varref)
|
||||
wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(calculate_member_offset(
|
||||
inp.struct_type3.name, inp.struct_type3.application.arguments, inp.member
|
||||
)))
|
||||
wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(offset))
|
||||
return
|
||||
|
||||
raise NotImplementedError(expression, inp)
|
||||
|
||||
def statement_return(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.StatementReturn) -> None:
|
||||
def statement_return(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun: ourlang.Function, inp: ourlang.StatementReturn) -> None:
|
||||
"""
|
||||
Compile: Return statement
|
||||
"""
|
||||
# Support tail calls
|
||||
# https://github.com/WebAssembly/tail-call
|
||||
# These help a lot with some functional programming techniques
|
||||
if isinstance(inp.value, ourlang.FunctionCall) and inp.value.function is fun:
|
||||
for arg in inp.value.arguments:
|
||||
expression(wgn, mod, arg)
|
||||
|
||||
wgn.add_statement('return_call', '${}'.format(inp.value.function.name))
|
||||
return
|
||||
|
||||
expression(wgn, mod, inp.value)
|
||||
wgn.return_()
|
||||
|
||||
def statement_if(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.StatementIf) -> None:
|
||||
def statement_if(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun: ourlang.Function, inp: ourlang.StatementIf) -> None:
|
||||
"""
|
||||
Compile: If statement
|
||||
"""
|
||||
expression(wgn, mod, inp.test)
|
||||
with wgn.if_():
|
||||
for stat in inp.statements:
|
||||
statement(wgn, mod, stat)
|
||||
statement(wgn, mod, fun, stat)
|
||||
|
||||
if inp.else_statements:
|
||||
raise NotImplementedError
|
||||
@ -391,16 +404,16 @@ def statement_if(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Statement
|
||||
# for stat in inp.else_statements:
|
||||
# statement(wgn, stat)
|
||||
|
||||
def statement(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Statement) -> None:
|
||||
def statement(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun: ourlang.Function, inp: ourlang.Statement) -> None:
|
||||
"""
|
||||
Compile: any statement
|
||||
"""
|
||||
if isinstance(inp, ourlang.StatementReturn):
|
||||
statement_return(wgn, mod, inp)
|
||||
statement_return(wgn, mod, fun, inp)
|
||||
return
|
||||
|
||||
if isinstance(inp, ourlang.StatementIf):
|
||||
statement_if(wgn, mod, inp)
|
||||
statement_if(wgn, mod, fun, inp)
|
||||
return
|
||||
|
||||
if isinstance(inp, ourlang.StatementPass):
|
||||
@ -408,111 +421,86 @@ def statement(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Statement) -
|
||||
|
||||
raise NotImplementedError(statement, inp)
|
||||
|
||||
def function_argument(inp: ourlang.FunctionParam) -> wasm.Param:
|
||||
"""
|
||||
Compile: function argument
|
||||
"""
|
||||
return (inp.name, type3(inp.type3), )
|
||||
|
||||
def import_(inp: ourlang.Function) -> wasm.Import:
|
||||
def import_(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.Import:
|
||||
"""
|
||||
Compile: imported function
|
||||
"""
|
||||
assert inp.imported
|
||||
|
||||
assert _is_concrete(inp.type5), TYPE5_ASSERTION_ERROR
|
||||
fn_args = mod.build.type5_is_function(inp.type5)
|
||||
assert fn_args is not None
|
||||
fn_ret = fn_args.pop()
|
||||
|
||||
return wasm.Import(
|
||||
inp.imported,
|
||||
inp.name,
|
||||
inp.name,
|
||||
[
|
||||
function_argument(x)
|
||||
for x in inp.posonlyargs
|
||||
(arg_name, type5(mod, arg_type5), )
|
||||
for arg_name, arg_type5 in zip(inp.arg_names, fn_args, strict=True)
|
||||
],
|
||||
type3(inp.returns_type3)
|
||||
type5(mod, fn_ret)
|
||||
)
|
||||
|
||||
def function(mod: ourlang.Module, inp: ourlang.Function) -> wasm.Function:
|
||||
def function(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.Function:
|
||||
"""
|
||||
Compile: function
|
||||
"""
|
||||
assert not inp.imported
|
||||
|
||||
assert _is_concrete(inp.type5), TYPE5_ASSERTION_ERROR
|
||||
fn_args = mod.build.type5_is_function(inp.type5)
|
||||
assert fn_args is not None
|
||||
fn_ret = fn_args.pop()
|
||||
|
||||
wgn = WasmGenerator()
|
||||
|
||||
if isinstance(inp, ourlang.StructConstructor):
|
||||
_generate_struct_constructor(wgn, inp)
|
||||
_generate_struct_constructor(wgn, mod, inp)
|
||||
else:
|
||||
for stat in inp.statements:
|
||||
statement(wgn, mod, stat)
|
||||
statement(wgn, mod, inp, stat)
|
||||
|
||||
return wasm.Function(
|
||||
inp.name,
|
||||
inp.name if inp.exported else None,
|
||||
[
|
||||
function_argument(x)
|
||||
for x in inp.posonlyargs
|
||||
(arg_name, type5(mod, arg_type5), )
|
||||
for arg_name, arg_type5 in zip(inp.arg_names, fn_args, strict=True)
|
||||
],
|
||||
[
|
||||
(k, v.wasm_type(), )
|
||||
for k, v in wgn.locals.items()
|
||||
],
|
||||
type3(inp.returns_type3),
|
||||
type5(mod, fn_ret),
|
||||
wgn.statements
|
||||
)
|
||||
|
||||
def module_data_u8(inp: int) -> bytes:
|
||||
"""
|
||||
Compile: module data, u8 value
|
||||
"""
|
||||
return struct.pack('<B', inp)
|
||||
def module_data_primitive(type_info: TypeInfo, inp: int | float) -> bytes:
|
||||
letter_map = {
|
||||
(WasmTypeInt32, 1, False): 'B',
|
||||
(WasmTypeInt32, 1, True): 'b',
|
||||
(WasmTypeInt32, 2, False): 'H',
|
||||
(WasmTypeInt32, 2, True): 'h',
|
||||
(WasmTypeInt32, 4, False): 'I',
|
||||
(WasmTypeInt32, 4, True): 'i',
|
||||
(WasmTypeInt64, 8, False): 'Q',
|
||||
(WasmTypeInt64, 8, True): 'q',
|
||||
(WasmTypeFloat32, 4, None): 'f',
|
||||
(WasmTypeFloat64, 8, None): 'd',
|
||||
}
|
||||
|
||||
def module_data_u32(inp: int) -> bytes:
|
||||
"""
|
||||
Compile: module data, u32 value
|
||||
"""
|
||||
return struct.pack('<I', inp)
|
||||
letter = letter_map[(type_info.wasm_type, type_info.alloc_size, type_info.signed, )]
|
||||
return struct.pack(f'<{letter}', inp)
|
||||
|
||||
def module_data_u64(inp: int) -> bytes:
|
||||
"""
|
||||
Compile: module data, u64 value
|
||||
"""
|
||||
return struct.pack('<Q', inp)
|
||||
|
||||
def module_data_i8(inp: int) -> bytes:
|
||||
"""
|
||||
Compile: module data, i8 value
|
||||
"""
|
||||
return struct.pack('<b', inp)
|
||||
|
||||
def module_data_i32(inp: int) -> bytes:
|
||||
"""
|
||||
Compile: module data, i32 value
|
||||
"""
|
||||
return struct.pack('<i', inp)
|
||||
|
||||
def module_data_i64(inp: int) -> bytes:
|
||||
"""
|
||||
Compile: module data, i64 value
|
||||
"""
|
||||
return struct.pack('<q', inp)
|
||||
|
||||
def module_data_f32(inp: float) -> bytes:
|
||||
"""
|
||||
Compile: module data, f32 value
|
||||
"""
|
||||
return struct.pack('<f', inp)
|
||||
|
||||
def module_data_f64(inp: float) -> bytes:
|
||||
"""
|
||||
Compile: module data, f64 value
|
||||
"""
|
||||
return struct.pack('<d', inp)
|
||||
|
||||
def module_data(inp: ourlang.ModuleData) -> bytes:
|
||||
def module_data(mod: ourlang.Module[WasmGenerator], inp: ourlang.ModuleData) -> bytes:
|
||||
"""
|
||||
Compile: module data
|
||||
"""
|
||||
unalloc_ptr = stdlib_alloc.UNALLOC_PTR
|
||||
u32_type_info = mod.build.type_info_map['u32']
|
||||
ptr_type_info = mod.build.type_info_constructed
|
||||
|
||||
allocated_data = b''
|
||||
|
||||
@ -522,105 +510,59 @@ def module_data(inp: ourlang.ModuleData) -> bytes:
|
||||
data_list: List[bytes] = []
|
||||
|
||||
for constant in block.data:
|
||||
assert constant.type3 is not None, TYPE3_ASSERTION_ERROR
|
||||
assert _is_concrete(constant.type5), TYPE5_ASSERTION_ERROR
|
||||
|
||||
if isinstance(constant, ourlang.ConstantBytes):
|
||||
data_list.append(module_data_primitive(u32_type_info, len(constant.value)))
|
||||
data_list.append(constant.value)
|
||||
continue
|
||||
|
||||
if isinstance(constant, ourlang.ConstantMemoryStored):
|
||||
if block is constant.data_block:
|
||||
raise NotImplementedError(block, constant)
|
||||
|
||||
if isinstance(constant, ourlang.ConstantMemoryStored) and block is not constant.data_block:
|
||||
# It's stored in a different block
|
||||
# We only need to store its address
|
||||
# This happens for example when a tuple refers
|
||||
# to a bytes constant
|
||||
assert constant.data_block.address is not None, 'Referred memory not yet stored'
|
||||
data_list.append(module_data_u32(constant.data_block.address))
|
||||
data_list.append(module_data_primitive(ptr_type_info, constant.data_block.address))
|
||||
continue
|
||||
|
||||
if constant.type3 == prelude.u8:
|
||||
assert isinstance(constant, ourlang.ConstantPrimitive)
|
||||
assert isinstance(constant.value, int)
|
||||
data_list.append(module_data_u8(constant.value))
|
||||
continue
|
||||
|
||||
if constant.type3 == prelude.u32:
|
||||
assert isinstance(constant, ourlang.ConstantPrimitive)
|
||||
assert isinstance(constant.value, int)
|
||||
data_list.append(module_data_u32(constant.value))
|
||||
continue
|
||||
|
||||
if constant.type3 == prelude.u64:
|
||||
assert isinstance(constant, ourlang.ConstantPrimitive)
|
||||
assert isinstance(constant.value, int)
|
||||
data_list.append(module_data_u64(constant.value))
|
||||
continue
|
||||
|
||||
if constant.type3 == prelude.i8:
|
||||
assert isinstance(constant, ourlang.ConstantPrimitive)
|
||||
assert isinstance(constant.value, int)
|
||||
data_list.append(module_data_i8(constant.value))
|
||||
continue
|
||||
|
||||
if constant.type3 == prelude.i32:
|
||||
assert isinstance(constant, ourlang.ConstantPrimitive)
|
||||
assert isinstance(constant.value, int)
|
||||
data_list.append(module_data_i32(constant.value))
|
||||
continue
|
||||
|
||||
if constant.type3 == prelude.i64:
|
||||
assert isinstance(constant, ourlang.ConstantPrimitive)
|
||||
assert isinstance(constant.value, int)
|
||||
data_list.append(module_data_i64(constant.value))
|
||||
continue
|
||||
|
||||
if constant.type3 == prelude.f32:
|
||||
assert isinstance(constant, ourlang.ConstantPrimitive)
|
||||
assert isinstance(constant.value, float)
|
||||
data_list.append(module_data_f32(constant.value))
|
||||
continue
|
||||
|
||||
if constant.type3 == prelude.f64:
|
||||
assert isinstance(constant, ourlang.ConstantPrimitive)
|
||||
assert isinstance(constant.value, float)
|
||||
data_list.append(module_data_f64(constant.value))
|
||||
continue
|
||||
|
||||
if constant.type3 == prelude.bytes_:
|
||||
assert isinstance(constant, ourlang.ConstantBytes)
|
||||
assert isinstance(constant.value, bytes)
|
||||
data_list.append(module_data_u32(len(constant.value)))
|
||||
data_list.append(constant.value)
|
||||
continue
|
||||
|
||||
raise NotImplementedError(constant, constant.type3)
|
||||
type_info = mod.build.type_info_map[constant.type5.name]
|
||||
data_list.append(module_data_primitive(type_info, constant.value))
|
||||
|
||||
block_data = b''.join(data_list)
|
||||
|
||||
allocated_data += module_data_u32(len(block_data)) + block_data
|
||||
allocated_data += module_data_primitive(u32_type_info, len(block_data)) + block_data
|
||||
|
||||
unalloc_ptr += 4 + len(block_data)
|
||||
|
||||
return (
|
||||
# Store that we've initialized the memory
|
||||
module_data_u32(stdlib_alloc.IDENTIFIER)
|
||||
module_data_primitive(u32_type_info, stdlib_alloc.IDENTIFIER)
|
||||
# Store the first reserved i32
|
||||
+ module_data_u32(0)
|
||||
+ module_data_primitive(u32_type_info, 0)
|
||||
# Store the pointer towards the first free block
|
||||
# In this case, 0 since we haven't freed any blocks yet
|
||||
+ module_data_u32(0)
|
||||
+ module_data_primitive(u32_type_info, 0)
|
||||
# Store the pointer towards the first unallocated block
|
||||
# In this case the end of the stdlib.alloc header at the start
|
||||
+ module_data_u32(unalloc_ptr)
|
||||
+ module_data_primitive(u32_type_info, unalloc_ptr)
|
||||
# Store the actual data
|
||||
+ allocated_data
|
||||
)
|
||||
|
||||
def module(inp: ourlang.Module) -> wasm.Module:
|
||||
def module(inp: ourlang.Module[WasmGenerator]) -> wasm.Module:
|
||||
"""
|
||||
Compile: module
|
||||
"""
|
||||
result = wasm.Module()
|
||||
|
||||
result.memory.data = module_data(inp.data)
|
||||
result.memory.data = module_data(inp, inp.data)
|
||||
|
||||
result.imports = [
|
||||
import_(x)
|
||||
import_(inp, x)
|
||||
for x in inp.functions.values()
|
||||
if x.imported
|
||||
]
|
||||
@ -629,20 +571,21 @@ def module(inp: ourlang.Module) -> wasm.Module:
|
||||
stdlib_alloc.__find_free_block__,
|
||||
stdlib_alloc.__alloc__,
|
||||
stdlib_types.__alloc_bytes__,
|
||||
stdlib_types.__subscript_bytes__,
|
||||
stdlib_types.__u32_ord_min__,
|
||||
stdlib_types.__u64_ord_min__,
|
||||
stdlib_types.__i32_ord_min__,
|
||||
stdlib_types.__i64_ord_min__,
|
||||
stdlib_types.__u32_ord_max__,
|
||||
stdlib_types.__u64_ord_max__,
|
||||
stdlib_types.__i32_ord_max__,
|
||||
stdlib_types.__i64_ord_max__,
|
||||
stdlib_types.__i32_intnum_abs__,
|
||||
stdlib_types.__i64_intnum_abs__,
|
||||
stdlib_types.__u32_min__,
|
||||
stdlib_types.__u64_min__,
|
||||
stdlib_types.__i32_min__,
|
||||
stdlib_types.__i64_min__,
|
||||
stdlib_types.__u32_max__,
|
||||
stdlib_types.__u64_max__,
|
||||
stdlib_types.__i32_max__,
|
||||
stdlib_types.__i64_max__,
|
||||
stdlib_types.__i32_abs__,
|
||||
stdlib_types.__i64_abs__,
|
||||
stdlib_types.__u32_pow2__,
|
||||
stdlib_types.__u8_rotl__,
|
||||
stdlib_types.__u8_rotr__,
|
||||
stdlib_types.__u16_rotl__,
|
||||
stdlib_types.__u16_rotr__,
|
||||
] + [
|
||||
function(inp, x)
|
||||
for x in inp.functions.values()
|
||||
@ -657,27 +600,56 @@ def module(inp: ourlang.Module) -> wasm.Module:
|
||||
|
||||
return result
|
||||
|
||||
def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None:
|
||||
assert isinstance(inp.struct_type3.application, TypeApplication_Struct)
|
||||
|
||||
st_args = inp.struct_type3.application.arguments
|
||||
def _generate_struct_constructor(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.StructConstructor) -> None:
|
||||
st_args = mod.build.type5_is_struct(inp.struct_type5)
|
||||
assert st_args is not None
|
||||
|
||||
tmp_var = wgn.temp_var_i32('struct_adr')
|
||||
|
||||
# Allocated the required amounts of bytes in memory
|
||||
wgn.i32.const(calculate_alloc_size(inp.struct_type3))
|
||||
wgn.i32.const(mod.build.type5_alloc_size_root(inp.struct_type5))
|
||||
wgn.call(stdlib_alloc.__alloc__)
|
||||
wgn.local.set(tmp_var)
|
||||
|
||||
# Store each member individually
|
||||
for memname, mtyp3 in st_args:
|
||||
mtyp3_info = TYPE_INFO_MAP.get(mtyp3.name, TYPE_INFO_CONSTRUCTED)
|
||||
offset = 0
|
||||
for memname, mtyp5 in st_args:
|
||||
mtyp5_info = mod.build.type_info_map.get(mtyp5.name)
|
||||
if mtyp5_info is None:
|
||||
mtyp5_info = mod.build.type_info_constructed
|
||||
|
||||
wgn.local.get(tmp_var)
|
||||
wgn.add_statement('local.get', f'${memname}')
|
||||
wgn.add_statement(mtyp3_info.wasm_store_func, 'offset=' + str(calculate_member_offset(
|
||||
inp.struct_type3.name, st_args, memname
|
||||
)))
|
||||
wgn.add_statement(mtyp5_info.wasm_store_func, 'offset=' + str(offset))
|
||||
|
||||
offset += mod.build.type5_alloc_size_member(mtyp5)
|
||||
|
||||
# Return the allocated address
|
||||
wgn.local.get(tmp_var)
|
||||
|
||||
def _is_concrete(type5: TypeExpr | ConstrainedExpr | None) -> TypeGuard[TypeExpr]:
|
||||
if type5 is None:
|
||||
return False
|
||||
|
||||
if isinstance(type5, ConstrainedExpr):
|
||||
type5 = type5.expr
|
||||
|
||||
return is_concrete(type5)
|
||||
|
||||
def _type5_struct_offset(
|
||||
build: BuildBase[Any],
|
||||
fields: tuple[tuple[str, AtomicType | TypeApplication], ...],
|
||||
needle: str,
|
||||
) -> int:
|
||||
"""
|
||||
Calculates the amount of bytes that should be skipped in memory befor reaching the struct's property with the given name.
|
||||
"""
|
||||
result = 0
|
||||
|
||||
for memnam, memtyp in fields:
|
||||
if needle == memnam:
|
||||
return result
|
||||
|
||||
result += build.type5_alloc_size_member(memtyp)
|
||||
|
||||
raise RuntimeError('Member not found')
|
||||
|
||||
5
phasm/optimise/__init__.py
Normal file
5
phasm/optimise/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
# 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.
|
||||
49
phasm/optimise/removeunusedfuncs.py
Normal file
49
phasm/optimise/removeunusedfuncs.py
Normal file
@ -0,0 +1,49 @@
|
||||
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
|
||||
]
|
||||
234
phasm/ourlang.py
234
phasm/ourlang.py
@ -1,24 +1,46 @@
|
||||
"""
|
||||
Contains the syntax tree for ourlang
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, Iterable, List, Optional, Union
|
||||
|
||||
from . import prelude
|
||||
from .type3.functions import FunctionSignature, TypeVariableContext
|
||||
from .type3.typeclasses import Type3ClassMethod
|
||||
from .type3.types import Type3, TypeApplication_Struct
|
||||
from .build.base import BuildBase
|
||||
from .type5 import constrainedexpr as type5constrainedexpr
|
||||
from .type5 import record as type5record
|
||||
from .type5 import typeexpr as type5typeexpr
|
||||
|
||||
|
||||
class SourceRef:
|
||||
__slots__ = ('filename', 'lineno', 'colno', )
|
||||
|
||||
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:
|
||||
"""
|
||||
An expression within a statement
|
||||
"""
|
||||
__slots__ = ('type3', )
|
||||
__slots__ = ('type5', 'sourceref', )
|
||||
|
||||
type3: Type3 | None
|
||||
sourceref: SourceRef
|
||||
type5: type5typeexpr.TypeExpr | None
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.type3 = None
|
||||
def __init__(self, *, sourceref: SourceRef) -> None:
|
||||
self.sourceref = sourceref
|
||||
self.type5 = None
|
||||
|
||||
class Constant(Expression):
|
||||
"""
|
||||
@ -34,10 +56,10 @@ class ConstantPrimitive(Constant):
|
||||
"""
|
||||
__slots__ = ('value', )
|
||||
|
||||
value: Union[int, float]
|
||||
value: int | float
|
||||
|
||||
def __init__(self, value: Union[int, float]) -> None:
|
||||
super().__init__()
|
||||
def __init__(self, value: int | float, sourceref: SourceRef) -> None:
|
||||
super().__init__(sourceref=sourceref)
|
||||
self.value = value
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@ -53,8 +75,8 @@ class ConstantMemoryStored(Constant):
|
||||
|
||||
data_block: 'ModuleDataBlock'
|
||||
|
||||
def __init__(self, data_block: 'ModuleDataBlock') -> None:
|
||||
super().__init__()
|
||||
def __init__(self, data_block: 'ModuleDataBlock', sourceref: SourceRef) -> None:
|
||||
super().__init__(sourceref=sourceref)
|
||||
self.data_block = data_block
|
||||
|
||||
class ConstantBytes(ConstantMemoryStored):
|
||||
@ -65,8 +87,8 @@ class ConstantBytes(ConstantMemoryStored):
|
||||
|
||||
value: bytes
|
||||
|
||||
def __init__(self, value: bytes, data_block: 'ModuleDataBlock') -> None:
|
||||
super().__init__(data_block)
|
||||
def __init__(self, value: bytes, data_block: 'ModuleDataBlock', sourceref: SourceRef) -> None:
|
||||
super().__init__(data_block, sourceref=sourceref)
|
||||
self.value = value
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@ -83,8 +105,8 @@ class ConstantTuple(ConstantMemoryStored):
|
||||
|
||||
value: List[Union[ConstantPrimitive, ConstantBytes, 'ConstantTuple', 'ConstantStruct']]
|
||||
|
||||
def __init__(self, value: List[Union[ConstantPrimitive, ConstantBytes, 'ConstantTuple', 'ConstantStruct']], data_block: 'ModuleDataBlock') -> None:
|
||||
super().__init__(data_block)
|
||||
def __init__(self, value: List[Union[ConstantPrimitive, ConstantBytes, 'ConstantTuple', 'ConstantStruct']], data_block: 'ModuleDataBlock', sourceref: SourceRef) -> None:
|
||||
super().__init__(data_block, sourceref=sourceref)
|
||||
self.value = value
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@ -97,21 +119,27 @@ class ConstantStruct(ConstantMemoryStored):
|
||||
"""
|
||||
A Struct constant value expression within a statement
|
||||
"""
|
||||
__slots__ = ('struct_type3', 'value', )
|
||||
__slots__ = ('struct_type5', 'value', )
|
||||
|
||||
struct_type3: Type3
|
||||
value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']]
|
||||
struct_type5: type5record.Record
|
||||
value: list[ConstantPrimitive | ConstantBytes | ConstantTuple | ConstantStruct]
|
||||
|
||||
def __init__(self, struct_type3: Type3, value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']], data_block: 'ModuleDataBlock') -> None:
|
||||
super().__init__(data_block)
|
||||
self.struct_type3 = struct_type3
|
||||
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_type3!r}, {self.value!r}, @{self.data_block.address!r})'
|
||||
return f'ConstantStruct({self.struct_type5!r}, {self.value!r}, @{self.data_block.address!r})'
|
||||
|
||||
class VariableReference(Expression):
|
||||
"""
|
||||
@ -121,24 +149,26 @@ class VariableReference(Expression):
|
||||
|
||||
variable: Union['ModuleConstantDef', 'FunctionParam'] # also possibly local
|
||||
|
||||
def __init__(self, variable: Union['ModuleConstantDef', 'FunctionParam']) -> None:
|
||||
super().__init__()
|
||||
def __init__(self, variable: Union['ModuleConstantDef', 'FunctionParam'], sourceref: SourceRef) -> None:
|
||||
super().__init__(sourceref=sourceref)
|
||||
self.variable = variable
|
||||
|
||||
class BinaryOp(Expression):
|
||||
"""
|
||||
A binary operator expression within a statement
|
||||
"""
|
||||
__slots__ = ('operator', 'left', 'right', )
|
||||
__slots__ = ('operator', 'polytype_substitutions', 'left', 'right', )
|
||||
|
||||
operator: Type3ClassMethod
|
||||
operator: Function | FunctionParam
|
||||
polytype_substitutions: dict[type5typeexpr.TypeVariable, type5typeexpr.TypeExpr]
|
||||
left: Expression
|
||||
right: Expression
|
||||
|
||||
def __init__(self, operator: Type3ClassMethod, left: Expression, right: Expression) -> None:
|
||||
super().__init__()
|
||||
def __init__(self, operator: Function | FunctionParam, left: Expression, right: Expression, sourceref: SourceRef) -> None:
|
||||
super().__init__(sourceref=sourceref)
|
||||
|
||||
self.operator = operator
|
||||
self.polytype_substitutions = {}
|
||||
self.left = left
|
||||
self.right = right
|
||||
|
||||
@ -149,15 +179,17 @@ class FunctionCall(Expression):
|
||||
"""
|
||||
A function call expression within a statement
|
||||
"""
|
||||
__slots__ = ('function', 'arguments', )
|
||||
__slots__ = ('function', 'polytype_substitutions', 'arguments', )
|
||||
|
||||
function: Union['Function', 'FunctionParam', Type3ClassMethod]
|
||||
function: Function | FunctionParam
|
||||
polytype_substitutions: dict[type5typeexpr.TypeVariable, type5typeexpr.TypeExpr]
|
||||
arguments: List[Expression]
|
||||
|
||||
def __init__(self, function: Union['Function', 'FunctionParam', Type3ClassMethod]) -> None:
|
||||
super().__init__()
|
||||
def __init__(self, function: Function | FunctionParam, sourceref: SourceRef) -> None:
|
||||
super().__init__(sourceref=sourceref)
|
||||
|
||||
self.function = function
|
||||
self.polytype_substitutions = {}
|
||||
self.arguments = []
|
||||
|
||||
class FunctionReference(Expression):
|
||||
@ -168,8 +200,8 @@ class FunctionReference(Expression):
|
||||
|
||||
function: 'Function'
|
||||
|
||||
def __init__(self, function: 'Function') -> None:
|
||||
super().__init__()
|
||||
def __init__(self, function: 'Function', sourceref: SourceRef) -> None:
|
||||
super().__init__(sourceref=sourceref)
|
||||
self.function = function
|
||||
|
||||
class TupleInstantiation(Expression):
|
||||
@ -180,8 +212,8 @@ class TupleInstantiation(Expression):
|
||||
|
||||
elements: List[Expression]
|
||||
|
||||
def __init__(self, elements: List[Expression]) -> None:
|
||||
super().__init__()
|
||||
def __init__(self, elements: List[Expression], sourceref: SourceRef) -> None:
|
||||
super().__init__(sourceref=sourceref)
|
||||
|
||||
self.elements = elements
|
||||
|
||||
@ -195,8 +227,8 @@ class Subscript(Expression):
|
||||
varref: VariableReference
|
||||
index: Expression
|
||||
|
||||
def __init__(self, varref: VariableReference, index: Expression) -> None:
|
||||
super().__init__()
|
||||
def __init__(self, varref: VariableReference, index: Expression, sourceref: SourceRef) -> None:
|
||||
super().__init__(sourceref=sourceref)
|
||||
|
||||
self.varref = varref
|
||||
self.index = index
|
||||
@ -205,24 +237,27 @@ class AccessStructMember(Expression):
|
||||
"""
|
||||
Access a struct member for reading of writing
|
||||
"""
|
||||
__slots__ = ('varref', 'struct_type3', 'member', )
|
||||
__slots__ = ('varref', 'member', )
|
||||
|
||||
varref: VariableReference
|
||||
struct_type3: Type3
|
||||
member: str
|
||||
|
||||
def __init__(self, varref: VariableReference, struct_type3: Type3, member: str) -> None:
|
||||
super().__init__()
|
||||
def __init__(self, varref: VariableReference, member: str, sourceref: SourceRef) -> None:
|
||||
super().__init__(sourceref=sourceref)
|
||||
|
||||
self.varref = varref
|
||||
self.struct_type3 = struct_type3
|
||||
self.member = member
|
||||
|
||||
class Statement:
|
||||
"""
|
||||
A statement within a function
|
||||
"""
|
||||
__slots__ = ()
|
||||
__slots__ = ("sourceref", )
|
||||
|
||||
sourceref: SourceRef
|
||||
|
||||
def __init__(self, *, sourceref: SourceRef) -> None:
|
||||
self.sourceref = sourceref
|
||||
|
||||
class StatementPass(Statement):
|
||||
"""
|
||||
@ -230,13 +265,18 @@ class StatementPass(Statement):
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, sourceref: SourceRef) -> None:
|
||||
super().__init__(sourceref=sourceref)
|
||||
|
||||
class StatementReturn(Statement):
|
||||
"""
|
||||
A return statement within a function
|
||||
"""
|
||||
__slots__ = ('value', )
|
||||
|
||||
def __init__(self, value: Expression) -> None:
|
||||
def __init__(self, value: Expression, sourceref: SourceRef) -> None:
|
||||
super().__init__(sourceref=sourceref)
|
||||
|
||||
self.value = value
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@ -261,55 +301,60 @@ class FunctionParam:
|
||||
"""
|
||||
A parameter for a Function
|
||||
"""
|
||||
__slots__ = ('name', 'type3', )
|
||||
__slots__ = ('name', 'type5', )
|
||||
|
||||
name: str
|
||||
type3: Type3
|
||||
type5: type5typeexpr.TypeExpr
|
||||
|
||||
def __init__(self, name: str, type5: type5typeexpr.TypeExpr) -> None:
|
||||
assert type5typeexpr.is_concrete(type5)
|
||||
|
||||
def __init__(self, name: str, type3: Type3) -> None:
|
||||
self.name = name
|
||||
self.type3 = type3
|
||||
self.type5 = type5
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'FunctionParam({self.name!r}, {self.type3!r})'
|
||||
return f'FunctionParam({self.name!r}, {self.type5!r})'
|
||||
|
||||
class Function:
|
||||
"""
|
||||
A function processes input and produces output
|
||||
"""
|
||||
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'signature', 'returns_type3', 'posonlyargs', )
|
||||
__slots__ = ('name', 'sourceref', 'exported', 'imported', 'statements', 'type5', 'arg_names', )
|
||||
|
||||
name: str
|
||||
lineno: int
|
||||
sourceref: SourceRef
|
||||
exported: bool
|
||||
imported: Optional[str]
|
||||
statements: List[Statement]
|
||||
signature: FunctionSignature
|
||||
returns_type3: Type3
|
||||
posonlyargs: List[FunctionParam]
|
||||
type5: type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr | None
|
||||
arg_names: list[str]
|
||||
|
||||
def __init__(self, name: str, lineno: int) -> None:
|
||||
def __init__(self, name: str, sourceref: SourceRef) -> None:
|
||||
self.name = name
|
||||
self.lineno = lineno
|
||||
self.sourceref = sourceref
|
||||
self.exported = False
|
||||
self.imported = None
|
||||
self.statements = []
|
||||
self.signature = FunctionSignature(TypeVariableContext(), [])
|
||||
self.returns_type3 = prelude.none # FIXME: This could be a placeholder
|
||||
self.posonlyargs = []
|
||||
self.type5 = None
|
||||
self.arg_names = []
|
||||
|
||||
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:
|
||||
"""
|
||||
The definition for a struct
|
||||
"""
|
||||
__slots__ = ('struct_type3', 'lineno', )
|
||||
__slots__ = ('struct_type5', 'sourceref', )
|
||||
|
||||
struct_type3: Type3
|
||||
lineno: int
|
||||
struct_type5: type5record.Record
|
||||
sourceref: SourceRef
|
||||
|
||||
def __init__(self, struct_type3: Type3, lineno: int) -> None:
|
||||
self.struct_type3 = struct_type3
|
||||
self.lineno = lineno
|
||||
def __init__(self, struct_type5: type5record.Record, sourceref: SourceRef) -> None:
|
||||
self.struct_type5 = struct_type5
|
||||
self.sourceref = sourceref
|
||||
|
||||
class StructConstructor(Function):
|
||||
"""
|
||||
@ -318,39 +363,32 @@ class StructConstructor(Function):
|
||||
A function will generated to instantiate a struct. The arguments
|
||||
will be the defaults
|
||||
"""
|
||||
__slots__ = ('struct_type3', )
|
||||
__slots__ = ('struct_type5', )
|
||||
|
||||
struct_type3: Type3
|
||||
struct_type5: type5record.Record
|
||||
|
||||
def __init__(self, struct_type3: Type3) -> None:
|
||||
super().__init__(f'@{struct_type3.name}@__init___@', -1)
|
||||
def __init__(self, struct_type5: type5record.Record, sourceref: SourceRef) -> None:
|
||||
super().__init__(f'@{struct_type5.name}@__init___@', sourceref)
|
||||
self.struct_type5 = struct_type5
|
||||
|
||||
assert isinstance(struct_type3.application, TypeApplication_Struct)
|
||||
|
||||
for mem, typ in struct_type3.application.arguments:
|
||||
self.posonlyargs.append(FunctionParam(mem, typ, ))
|
||||
self.signature.args.append(typ)
|
||||
|
||||
self.returns_type3 = struct_type3
|
||||
self.signature.args.append(struct_type3)
|
||||
|
||||
self.struct_type3 = struct_type3
|
||||
for mem, typ in struct_type5.fields:
|
||||
self.arg_names.append(mem)
|
||||
|
||||
class ModuleConstantDef:
|
||||
"""
|
||||
A constant definition within a module
|
||||
"""
|
||||
__slots__ = ('name', 'lineno', 'type3', 'constant', )
|
||||
__slots__ = ('name', 'sourceref', 'type5', 'constant', )
|
||||
|
||||
name: str
|
||||
lineno: int
|
||||
type3: Type3
|
||||
sourceref: SourceRef
|
||||
type5: type5typeexpr.TypeExpr
|
||||
constant: Constant
|
||||
|
||||
def __init__(self, name: str, lineno: int, type3: Type3, constant: Constant) -> None:
|
||||
def __init__(self, name: str, sourceref: SourceRef, type5: type5typeexpr.TypeExpr, constant: Constant) -> None:
|
||||
self.name = name
|
||||
self.lineno = lineno
|
||||
self.type3 = type3
|
||||
self.sourceref = sourceref
|
||||
self.type5 = type5
|
||||
self.constant = constant
|
||||
|
||||
class ModuleDataBlock:
|
||||
@ -366,6 +404,9 @@ class ModuleDataBlock:
|
||||
self.data = [*data]
|
||||
self.address = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'ModuleDataBlock({self.data!r}, {self.address!r})'
|
||||
|
||||
class ModuleData:
|
||||
"""
|
||||
The data for when a module is loaded into memory
|
||||
@ -377,25 +418,32 @@ class ModuleData:
|
||||
def __init__(self) -> None:
|
||||
self.blocks = []
|
||||
|
||||
class Module:
|
||||
class Module[G]:
|
||||
"""
|
||||
A module is a file and consists of functions
|
||||
"""
|
||||
__slots__ = ('data', 'types', 'struct_definitions', 'constant_defs', 'functions', 'operators', 'functions_table', )
|
||||
__slots__ = ('build', 'filename', 'data', 'types', 'type5s', 'struct_definitions', 'constant_defs', 'functions', 'methods', 'operators', 'functions_table', )
|
||||
|
||||
build: BuildBase[G]
|
||||
filename: str
|
||||
data: ModuleData
|
||||
types: dict[str, Type3]
|
||||
types: dict[str, type5typeexpr.TypeExpr]
|
||||
struct_definitions: Dict[str, StructDefinition]
|
||||
constant_defs: Dict[str, ModuleConstantDef]
|
||||
functions: Dict[str, Function]
|
||||
operators: Dict[str, Type3ClassMethod]
|
||||
methods: Dict[str, type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr]
|
||||
operators: Dict[str, type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr]
|
||||
functions_table: dict[Function, int]
|
||||
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, build: BuildBase[G], filename: str) -> None:
|
||||
self.build = build
|
||||
self.filename = filename
|
||||
|
||||
self.data = ModuleData()
|
||||
self.types = {}
|
||||
self.struct_definitions = {}
|
||||
self.constant_defs = {}
|
||||
self.functions = {}
|
||||
self.methods = {}
|
||||
self.operators = {}
|
||||
self.functions_table = {}
|
||||
|
||||
196
phasm/parser.py
196
phasm/parser.py
@ -4,11 +4,13 @@ Parses the source code from the plain text into a syntax tree
|
||||
import ast
|
||||
from typing import Any, Dict, NoReturn, Union
|
||||
|
||||
from . import prelude
|
||||
from .build.base import BuildBase
|
||||
from .build.default import BuildDefault
|
||||
from .exceptions import StaticError
|
||||
from .ourlang import (
|
||||
AccessStructMember,
|
||||
BinaryOp,
|
||||
BuiltinFunction,
|
||||
ConstantBytes,
|
||||
ConstantPrimitive,
|
||||
ConstantStruct,
|
||||
@ -21,6 +23,7 @@ from .ourlang import (
|
||||
Module,
|
||||
ModuleConstantDef,
|
||||
ModuleDataBlock,
|
||||
SourceRef,
|
||||
Statement,
|
||||
StatementIf,
|
||||
StatementPass,
|
||||
@ -31,12 +34,11 @@ from .ourlang import (
|
||||
TupleInstantiation,
|
||||
VariableReference,
|
||||
)
|
||||
from .prelude import PRELUDE_METHODS, PRELUDE_OPERATORS, PRELUDE_TYPES
|
||||
from .type3.typeclasses import Type3ClassMethod
|
||||
from .type3.types import IntType3, Type3
|
||||
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
|
||||
"""
|
||||
@ -44,7 +46,8 @@ def phasm_parse(source: str) -> Module:
|
||||
|
||||
res = OptimizerTransformer().visit(res)
|
||||
|
||||
our_visitor = OurVisitor()
|
||||
build = BuildDefault()
|
||||
our_visitor = OurVisitor(build)
|
||||
return our_visitor.visit_Module(res)
|
||||
|
||||
OurLocals = Dict[str, Union[FunctionParam]] # FIXME: Does it become easier if we add ModuleConstantDef to this dict?
|
||||
@ -75,7 +78,7 @@ class OptimizerTransformer(ast.NodeTransformer):
|
||||
return node.operand
|
||||
return node
|
||||
|
||||
class OurVisitor:
|
||||
class OurVisitor[G]:
|
||||
"""
|
||||
Class to visit a Python syntax tree and create an ourlang syntax tree
|
||||
|
||||
@ -90,14 +93,15 @@ class OurVisitor:
|
||||
|
||||
# pylint: disable=C0103,C0116,C0301,R0201,R0912
|
||||
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
def __init__(self, build: BuildBase[G]) -> None:
|
||||
self.build = build
|
||||
|
||||
def visit_Module(self, node: ast.Module) -> Module:
|
||||
module = Module()
|
||||
def visit_Module(self, node: ast.Module) -> Module[G]:
|
||||
module = Module(self.build, "-")
|
||||
|
||||
module.operators.update(PRELUDE_OPERATORS)
|
||||
module.types.update(PRELUDE_TYPES)
|
||||
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')
|
||||
|
||||
@ -109,26 +113,27 @@ class OurVisitor:
|
||||
if isinstance(res, ModuleConstantDef):
|
||||
if res.name in module.constant_defs:
|
||||
raise StaticError(
|
||||
f'{res.name} already defined on line {module.constant_defs[res.name].lineno}'
|
||||
f'{res.name} already defined on line {module.constant_defs[res.name].sourceref.lineno}'
|
||||
)
|
||||
|
||||
module.constant_defs[res.name] = res
|
||||
|
||||
if isinstance(res, StructDefinition):
|
||||
if res.struct_type3.name in module.types:
|
||||
if res.struct_type5.name in module.types:
|
||||
raise StaticError(
|
||||
f'{res.struct_type3.name} already defined as type'
|
||||
f'{res.struct_type5.name} already defined as type'
|
||||
)
|
||||
|
||||
module.types[res.struct_type3.name] = res.struct_type3
|
||||
module.functions[res.struct_type3.name] = StructConstructor(res.struct_type3)
|
||||
module.types[res.struct_type5.name] = res.struct_type5
|
||||
module.functions[res.struct_type5.name] = StructConstructor(res.struct_type5, res.sourceref)
|
||||
module.functions[res.struct_type5.name].type5 = module.build.type5_make_function([x[1] for x in res.struct_type5.fields] + [res.struct_type5])
|
||||
# Store that the definition was done in this module for the formatter
|
||||
module.struct_definitions[res.struct_type3.name] = res
|
||||
module.struct_definitions[res.struct_type5.name] = res
|
||||
|
||||
if isinstance(res, Function):
|
||||
if res.name in module.functions:
|
||||
raise StaticError(
|
||||
f'{res.name} already defined on line {module.functions[res.name].lineno}'
|
||||
f'{res.name} already defined on line {module.functions[res.name].sourceref.lineno}'
|
||||
)
|
||||
|
||||
module.functions[res.name] = res
|
||||
@ -140,7 +145,7 @@ class OurVisitor:
|
||||
|
||||
return module
|
||||
|
||||
def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, StructDefinition, ModuleConstantDef]:
|
||||
def pre_visit_Module_stmt(self, module: Module[G], node: ast.stmt) -> Union[Function, StructDefinition, ModuleConstantDef]:
|
||||
if isinstance(node, ast.FunctionDef):
|
||||
return self.pre_visit_Module_FunctionDef(module, node)
|
||||
|
||||
@ -152,25 +157,25 @@ class OurVisitor:
|
||||
|
||||
raise NotImplementedError(f'{node} on Module')
|
||||
|
||||
def pre_visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> Function:
|
||||
function = Function(node.name, node.lineno)
|
||||
def pre_visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> Function:
|
||||
function = Function(node.name, srf(module, node))
|
||||
|
||||
_not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs')
|
||||
|
||||
arg_type5_list = []
|
||||
|
||||
for arg in node.args.args:
|
||||
if arg.annotation is None:
|
||||
_raise_static_error(node, 'Must give a argument type')
|
||||
|
||||
arg_type = self.visit_type(module, arg.annotation)
|
||||
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.signature.args.append(arg_type)
|
||||
function.posonlyargs.append(FunctionParam(
|
||||
arg.arg,
|
||||
arg_type,
|
||||
))
|
||||
function.arg_names.append(arg.arg)
|
||||
|
||||
_not_implemented(not node.args.vararg, 'FunctionDef.args.vararg')
|
||||
_not_implemented(not node.args.kwonlyargs, 'FunctionDef.args.kwonlyargs')
|
||||
@ -212,21 +217,21 @@ class OurVisitor:
|
||||
|
||||
if node.returns is None: # Note: `-> None` would be a ast.Constant
|
||||
_raise_static_error(node, 'Must give a return type')
|
||||
return_type = self.visit_type(module, node.returns)
|
||||
function.signature.args.append(return_type)
|
||||
function.returns_type3 = return_type
|
||||
arg_type5_list.append(self.visit_type5(module, node.returns))
|
||||
|
||||
function.type5 = module.build.type5_make_function(arg_type5_list)
|
||||
|
||||
_not_implemented(not node.type_comment, 'FunctionDef.type_comment')
|
||||
|
||||
return function
|
||||
|
||||
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> StructDefinition:
|
||||
def pre_visit_Module_ClassDef(self, module: Module[G], node: ast.ClassDef) -> StructDefinition:
|
||||
|
||||
_not_implemented(not node.bases, 'ClassDef.bases')
|
||||
_not_implemented(not node.keywords, 'ClassDef.keywords')
|
||||
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
|
||||
|
||||
members: Dict[str, Type3] = {}
|
||||
members: Dict[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication] = {}
|
||||
|
||||
for stmt in node.body:
|
||||
if not isinstance(stmt, ast.AnnAssign):
|
||||
@ -244,11 +249,16 @@ class OurVisitor:
|
||||
if stmt.target.id in members:
|
||||
_raise_static_error(stmt, 'Struct members must have unique names')
|
||||
|
||||
members[stmt.target.id] = self.visit_type(module, stmt.annotation)
|
||||
field_type5 = self.visit_type5(module, stmt.annotation)
|
||||
assert isinstance(field_type5, (type5typeexpr.AtomicType, type5typeexpr.TypeApplication, ))
|
||||
members[stmt.target.id] = field_type5
|
||||
|
||||
return StructDefinition(prelude.struct(node.name, tuple(members.items())), node.lineno)
|
||||
return StructDefinition(
|
||||
module.build.type5_make_struct(node.name, tuple(members.items())),
|
||||
srf(module, node),
|
||||
)
|
||||
|
||||
def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef:
|
||||
def pre_visit_Module_AnnAssign(self, module: Module[G], node: ast.AnnAssign) -> ModuleConstantDef:
|
||||
if not isinstance(node.target, ast.Name):
|
||||
_raise_static_error(node.target, 'Must be name')
|
||||
if not isinstance(node.target.ctx, ast.Store):
|
||||
@ -259,8 +269,8 @@ class OurVisitor:
|
||||
|
||||
return ModuleConstantDef(
|
||||
node.target.id,
|
||||
node.lineno,
|
||||
self.visit_type(module, node.annotation),
|
||||
srf(module, node),
|
||||
self.visit_type5(module, node.annotation),
|
||||
value_data,
|
||||
)
|
||||
|
||||
@ -272,8 +282,8 @@ class OurVisitor:
|
||||
# Then return the constant as a pointer
|
||||
return ModuleConstantDef(
|
||||
node.target.id,
|
||||
node.lineno,
|
||||
self.visit_type(module, node.annotation),
|
||||
srf(module, node),
|
||||
self.visit_type5(module, node.annotation),
|
||||
value_data,
|
||||
)
|
||||
|
||||
@ -285,14 +295,14 @@ class OurVisitor:
|
||||
# Then return the constant as a pointer
|
||||
return ModuleConstantDef(
|
||||
node.target.id,
|
||||
node.lineno,
|
||||
self.visit_type(module, node.annotation),
|
||||
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, node: ast.stmt) -> None:
|
||||
def visit_Module_stmt(self, module: Module[G], node: ast.stmt) -> None:
|
||||
if isinstance(node, ast.FunctionDef):
|
||||
self.visit_Module_FunctionDef(module, node)
|
||||
return
|
||||
@ -305,12 +315,17 @@ class OurVisitor:
|
||||
|
||||
raise NotImplementedError(f'{node} on Module')
|
||||
|
||||
def visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> None:
|
||||
def visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> None:
|
||||
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 = {
|
||||
x.name: x
|
||||
for x in function.posonlyargs
|
||||
a_nam: FunctionParam(a_nam, a_typ)
|
||||
for a_nam, a_typ in zip(function.arg_names, func_arg_types, strict=True)
|
||||
}
|
||||
|
||||
for stmt in node.body:
|
||||
@ -318,14 +333,15 @@ class OurVisitor:
|
||||
self.visit_Module_FunctionDef_stmt(module, function, our_locals, stmt)
|
||||
)
|
||||
|
||||
def visit_Module_FunctionDef_stmt(self, module: Module, function: Function, our_locals: OurLocals, node: ast.stmt) -> Statement:
|
||||
def visit_Module_FunctionDef_stmt(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.stmt) -> Statement:
|
||||
if isinstance(node, ast.Return):
|
||||
if node.value is None:
|
||||
# TODO: Implement methods without return values
|
||||
_raise_static_error(node, 'Return must have an argument')
|
||||
|
||||
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):
|
||||
@ -346,13 +362,13 @@ class OurVisitor:
|
||||
return result
|
||||
|
||||
if isinstance(node, ast.Pass):
|
||||
return StatementPass()
|
||||
return StatementPass(srf(module, node))
|
||||
|
||||
raise NotImplementedError(f'{node} as stmt in FunctionDef')
|
||||
|
||||
def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
|
||||
def visit_Module_FunctionDef_expr(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
|
||||
if isinstance(node, ast.BinOp):
|
||||
operator: Union[str, Type3ClassMethod]
|
||||
operator: str
|
||||
|
||||
if isinstance(node.op, ast.Add):
|
||||
operator = '+'
|
||||
@ -383,9 +399,10 @@ class OurVisitor:
|
||||
raise NotImplementedError(f'Operator {operator}')
|
||||
|
||||
return BinaryOp(
|
||||
module.operators[operator],
|
||||
BuiltinFunction(operator, module.operators[operator]),
|
||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
|
||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.right),
|
||||
srf(module, node),
|
||||
)
|
||||
|
||||
if isinstance(node, ast.Compare):
|
||||
@ -411,9 +428,10 @@ class OurVisitor:
|
||||
raise NotImplementedError(f'Operator {operator}')
|
||||
|
||||
return BinaryOp(
|
||||
module.operators[operator],
|
||||
BuiltinFunction(operator, module.operators[operator]),
|
||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
|
||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.comparators[0]),
|
||||
srf(module, node),
|
||||
)
|
||||
|
||||
if isinstance(node, ast.Call):
|
||||
@ -440,15 +458,15 @@ class OurVisitor:
|
||||
|
||||
if node.id in our_locals:
|
||||
param = our_locals[node.id]
|
||||
return VariableReference(param)
|
||||
return VariableReference(param, srf(module, node))
|
||||
|
||||
if node.id in module.constant_defs:
|
||||
cdef = module.constant_defs[node.id]
|
||||
return VariableReference(cdef)
|
||||
return VariableReference(cdef, srf(module, node))
|
||||
|
||||
if node.id in module.functions:
|
||||
fun = module.functions[node.id]
|
||||
return FunctionReference(fun)
|
||||
return FunctionReference(fun, srf(module, node))
|
||||
|
||||
_raise_static_error(node, f'Undefined variable {node.id}')
|
||||
|
||||
@ -462,11 +480,11 @@ class OurVisitor:
|
||||
if len(arguments) != len(node.elts):
|
||||
raise NotImplementedError('Non-constant tuple members')
|
||||
|
||||
return TupleInstantiation(arguments)
|
||||
return TupleInstantiation(arguments, srf(module, node))
|
||||
|
||||
raise NotImplementedError(f'{node} as expr in FunctionDef')
|
||||
|
||||
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Call) -> Union[FunctionCall]:
|
||||
def visit_Module_FunctionDef_Call(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.Call) -> Union[FunctionCall]:
|
||||
if node.keywords:
|
||||
_raise_static_error(node, 'Keyword calling not supported') # Yet?
|
||||
|
||||
@ -475,10 +493,10 @@ class OurVisitor:
|
||||
if not isinstance(node.func.ctx, ast.Load):
|
||||
_raise_static_error(node, 'Must be load context')
|
||||
|
||||
func: Union[Function, FunctionParam, Type3ClassMethod]
|
||||
func: Union[Function, FunctionParam]
|
||||
|
||||
if node.func.id in PRELUDE_METHODS:
|
||||
func = PRELUDE_METHODS[node.func.id]
|
||||
if node.func.id in module.methods:
|
||||
func = BuiltinFunction(node.func.id, module.methods[node.func.id])
|
||||
elif node.func.id in our_locals:
|
||||
func = our_locals[node.func.id]
|
||||
else:
|
||||
@ -487,14 +505,14 @@ class OurVisitor:
|
||||
|
||||
func = module.functions[node.func.id]
|
||||
|
||||
result = FunctionCall(func)
|
||||
result = FunctionCall(func, sourceref=srf(module, node))
|
||||
result.arguments.extend(
|
||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_expr)
|
||||
for arg_expr in node.args
|
||||
)
|
||||
return result
|
||||
|
||||
def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Attribute) -> Expression:
|
||||
def visit_Module_FunctionDef_Attribute(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.Attribute) -> Expression:
|
||||
if not isinstance(node.value, ast.Name):
|
||||
_raise_static_error(node, 'Must reference a name')
|
||||
|
||||
@ -507,11 +525,11 @@ class OurVisitor:
|
||||
|
||||
return AccessStructMember(
|
||||
varref,
|
||||
varref.variable.type3,
|
||||
node.attr,
|
||||
srf(module, node),
|
||||
)
|
||||
|
||||
def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Subscript) -> Expression:
|
||||
def visit_Module_FunctionDef_Subscript(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.Subscript) -> Expression:
|
||||
if not isinstance(node.value, ast.Name):
|
||||
_raise_static_error(node, 'Must reference a name')
|
||||
|
||||
@ -524,10 +542,10 @@ class OurVisitor:
|
||||
varref: VariableReference
|
||||
if node.value.id in our_locals:
|
||||
param = our_locals[node.value.id]
|
||||
varref = VariableReference(param)
|
||||
varref = VariableReference(param, srf(module, node))
|
||||
elif node.value.id in module.constant_defs:
|
||||
constant_def = module.constant_defs[node.value.id]
|
||||
varref = VariableReference(constant_def)
|
||||
varref = VariableReference(constant_def, srf(module, node))
|
||||
else:
|
||||
_raise_static_error(node, f'Undefined variable {node.value.id}')
|
||||
|
||||
@ -535,9 +553,9 @@ class OurVisitor:
|
||||
module, function, our_locals, node.slice,
|
||||
)
|
||||
|
||||
return Subscript(varref, slice_expr)
|
||||
return Subscript(varref, slice_expr, srf(module, node))
|
||||
|
||||
def visit_Module_Constant(self, module: Module, node: Union[ast.Constant, ast.Tuple, ast.Call]) -> Union[ConstantPrimitive, ConstantBytes, ConstantTuple, ConstantStruct]:
|
||||
def visit_Module_Constant(self, module: Module[G], node: Union[ast.Constant, ast.Tuple, ast.Call]) -> Union[ConstantPrimitive, ConstantBytes, ConstantTuple, ConstantStruct]:
|
||||
if isinstance(node, ast.Tuple):
|
||||
tuple_data = [
|
||||
self.visit_Module_Constant(module, arg_node)
|
||||
@ -552,7 +570,7 @@ class OurVisitor:
|
||||
data_block = ModuleDataBlock(tuple_data)
|
||||
module.data.blocks.append(data_block)
|
||||
|
||||
return ConstantTuple(tuple_data, data_block)
|
||||
return ConstantTuple(tuple_data, data_block, srf(module, node))
|
||||
|
||||
if isinstance(node, ast.Call):
|
||||
# Struct constant
|
||||
@ -580,29 +598,29 @@ class OurVisitor:
|
||||
|
||||
data_block = ModuleDataBlock(struct_data)
|
||||
module.data.blocks.append(data_block)
|
||||
return ConstantStruct(struct_def.struct_type3, struct_data, data_block)
|
||||
return ConstantStruct(struct_def.struct_type5, struct_data, data_block, srf(module, node))
|
||||
|
||||
_not_implemented(node.kind is None, 'Constant.kind')
|
||||
|
||||
if isinstance(node.value, (int, float, )):
|
||||
return ConstantPrimitive(node.value)
|
||||
return ConstantPrimitive(node.value, srf(module, node))
|
||||
|
||||
if isinstance(node.value, bytes):
|
||||
data_block = ModuleDataBlock([])
|
||||
module.data.blocks.append(data_block)
|
||||
|
||||
result = ConstantBytes(node.value, 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')
|
||||
|
||||
def visit_type(self, module: Module, node: ast.expr) -> Type3:
|
||||
def visit_type5(self, module: Module[G], node: ast.expr) -> type5typeexpr.TypeExpr:
|
||||
if isinstance(node, ast.Constant):
|
||||
if node.value is None:
|
||||
return prelude.none
|
||||
return module.build.none_type5
|
||||
|
||||
_raise_static_error(node, f'Unrecognized type {node.value}')
|
||||
_raise_static_error(node, f'Unrecognized type {node.value!r}')
|
||||
|
||||
if isinstance(node, ast.Name):
|
||||
if not isinstance(node.ctx, ast.Load):
|
||||
@ -624,9 +642,8 @@ class OurVisitor:
|
||||
else:
|
||||
_raise_static_error(node, 'Must subscript using a list of types')
|
||||
|
||||
# Function type
|
||||
return prelude.function(*[
|
||||
self.visit_type(module, e)
|
||||
return module.build.type5_make_function([
|
||||
self.visit_type5(module, e)
|
||||
for e in func_arg_types
|
||||
])
|
||||
|
||||
@ -637,8 +654,8 @@ class OurVisitor:
|
||||
_raise_static_error(node, 'Must subscript using a constant index')
|
||||
|
||||
if node.slice.value is Ellipsis:
|
||||
return prelude.dynamic_array(
|
||||
self.visit_type(module, node.value),
|
||||
return module.build.type5_make_dynamic_array(
|
||||
self.visit_type5(module, node.value),
|
||||
)
|
||||
|
||||
if not isinstance(node.slice.value, int):
|
||||
@ -646,20 +663,20 @@ class OurVisitor:
|
||||
if not isinstance(node.ctx, ast.Load):
|
||||
_raise_static_error(node, 'Must be load context')
|
||||
|
||||
return prelude.static_array(
|
||||
self.visit_type(module, node.value),
|
||||
IntType3(node.slice.value),
|
||||
return module.build.type5_make_static_array(
|
||||
node.slice.value,
|
||||
self.visit_type5(module, node.value),
|
||||
)
|
||||
|
||||
if isinstance(node, ast.Tuple):
|
||||
if not isinstance(node.ctx, ast.Load):
|
||||
_raise_static_error(node, 'Must be load context')
|
||||
|
||||
return prelude.tuple_(
|
||||
*(self.visit_type(module, elt) for elt in node.elts)
|
||||
return module.build.type5_make_tuple(
|
||||
[self.visit_type5(module, elt) for elt in node.elts],
|
||||
)
|
||||
|
||||
raise NotImplementedError(f'{node} as type')
|
||||
raise NotImplementedError(node)
|
||||
|
||||
def _not_implemented(check: Any, msg: str) -> None:
|
||||
if not check:
|
||||
@ -669,3 +686,6 @@ def _raise_static_error(node: Union[ast.stmt, ast.expr], msg: str) -> NoReturn:
|
||||
raise StaticError(
|
||||
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,625 +0,0 @@
|
||||
"""
|
||||
The prelude are all the builtin types, type classes and methods
|
||||
"""
|
||||
from typing import Any, Callable
|
||||
from warnings import warn
|
||||
|
||||
from phasm.stdlib import types as stdtypes
|
||||
from phasm.wasmgenerator import Generator
|
||||
|
||||
from ..type3.functions import (
|
||||
Constraint_TypeClassInstanceExists,
|
||||
TypeConstructorVariable,
|
||||
TypeVariable,
|
||||
TypeVariableApplication_Nullary,
|
||||
)
|
||||
from ..type3.routers import TypeClassArgsRouter, TypeVariableLookup
|
||||
from ..type3.typeclasses import Type3Class, Type3ClassMethod
|
||||
from ..type3.types import (
|
||||
Type3,
|
||||
TypeApplication_Nullary,
|
||||
TypeConstructor_Base,
|
||||
TypeConstructor_DynamicArray,
|
||||
TypeConstructor_Function,
|
||||
TypeConstructor_StaticArray,
|
||||
TypeConstructor_Struct,
|
||||
TypeConstructor_Tuple,
|
||||
)
|
||||
|
||||
PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: set[tuple[Type3Class, tuple[Type3 | TypeConstructor_Base[Any], ...]]] = set()
|
||||
|
||||
PRELUDE_TYPE_CLASS_INSTANCE_METHODS: dict[Type3ClassMethod, TypeClassArgsRouter[Generator, None]] = {}
|
||||
|
||||
class MissingImplementationException(Exception):
|
||||
pass
|
||||
|
||||
class MissingImplementationWarning(Warning):
|
||||
pass
|
||||
|
||||
def instance_type_class(
|
||||
cls: Type3Class,
|
||||
*typ: Type3 | TypeConstructor_Base[Any],
|
||||
methods: dict[str, Callable[[Generator, TypeVariableLookup], None]] = {},
|
||||
operators: dict[str, Callable[[Generator, TypeVariableLookup], None]] = {},
|
||||
) -> None:
|
||||
global PRELUDE_TYPE_CLASS_INSTANCES_EXISTING
|
||||
global PRELUDE_TYPE_CLASS_INSTANCE_METHODS
|
||||
|
||||
assert len(cls.args) == len(typ)
|
||||
|
||||
tv_map: dict[TypeVariable, Type3] = {}
|
||||
tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]] = {}
|
||||
for arg_tv, arg_tp in zip(cls.args, typ, strict=True):
|
||||
if isinstance(arg_tv, TypeVariable):
|
||||
assert isinstance(arg_tp, Type3)
|
||||
tv_map[arg_tv] = arg_tp
|
||||
elif isinstance(arg_tv, TypeConstructorVariable):
|
||||
assert isinstance(arg_tp, TypeConstructor_Base)
|
||||
tc_map[arg_tv] = arg_tp
|
||||
else:
|
||||
raise NotImplementedError(arg_tv, arg_tp)
|
||||
|
||||
# TODO: Check for required existing instantiations
|
||||
|
||||
PRELUDE_TYPE_CLASS_INSTANCES_EXISTING.add((cls, tuple(typ), ))
|
||||
|
||||
for method_name, method in cls.methods.items():
|
||||
router = PRELUDE_TYPE_CLASS_INSTANCE_METHODS.get(method)
|
||||
if router is None:
|
||||
router = TypeClassArgsRouter[Generator, None](cls.args)
|
||||
PRELUDE_TYPE_CLASS_INSTANCE_METHODS[method] = router
|
||||
|
||||
try:
|
||||
generator = methods[method_name]
|
||||
except KeyError:
|
||||
warn(MissingImplementationWarning(str(method), cls.name + ' ' + ' '.join(x.name for x in typ)))
|
||||
continue
|
||||
|
||||
router.add(tv_map, tc_map, generator)
|
||||
|
||||
for operator_name, operator in cls.operators.items():
|
||||
router = PRELUDE_TYPE_CLASS_INSTANCE_METHODS.get(operator)
|
||||
if router is None:
|
||||
router = TypeClassArgsRouter[Generator, None](cls.args)
|
||||
PRELUDE_TYPE_CLASS_INSTANCE_METHODS[operator] = router
|
||||
|
||||
try:
|
||||
generator = operators[operator_name]
|
||||
except KeyError:
|
||||
warn(MissingImplementationWarning(str(operator), cls.name + ' ' + ' '.join(x.name for x in typ)))
|
||||
continue
|
||||
|
||||
router.add(tv_map, tc_map, generator)
|
||||
|
||||
none = Type3('none', TypeApplication_Nullary(None, None))
|
||||
"""
|
||||
The none type, for when functions simply don't return anything. e.g., IO().
|
||||
"""
|
||||
|
||||
bool_ = Type3('bool', TypeApplication_Nullary(None, None))
|
||||
"""
|
||||
The bool type, either True or False
|
||||
|
||||
Suffixes with an underscores, as it's a Python builtin
|
||||
"""
|
||||
|
||||
u8 = Type3('u8', TypeApplication_Nullary(None, None))
|
||||
"""
|
||||
The unsigned 8-bit integer type.
|
||||
|
||||
Operations on variables employ modular arithmetic, with modulus 2^8.
|
||||
"""
|
||||
|
||||
u32 = Type3('u32', TypeApplication_Nullary(None, None))
|
||||
"""
|
||||
The unsigned 32-bit integer type.
|
||||
|
||||
Operations on variables employ modular arithmetic, with modulus 2^32.
|
||||
"""
|
||||
|
||||
u64 = Type3('u64', TypeApplication_Nullary(None, None))
|
||||
"""
|
||||
The unsigned 64-bit integer type.
|
||||
|
||||
Operations on variables employ modular arithmetic, with modulus 2^64.
|
||||
"""
|
||||
|
||||
i8 = Type3('i8', TypeApplication_Nullary(None, None))
|
||||
"""
|
||||
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', TypeApplication_Nullary(None, None))
|
||||
"""
|
||||
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', TypeApplication_Nullary(None, None))
|
||||
"""
|
||||
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', TypeApplication_Nullary(None, None))
|
||||
"""
|
||||
A 32-bits IEEE 754 float, of 32 bits width.
|
||||
"""
|
||||
|
||||
f64 = Type3('f64', TypeApplication_Nullary(None, None))
|
||||
"""
|
||||
A 32-bits IEEE 754 float, of 64 bits width.
|
||||
"""
|
||||
|
||||
dynamic_array = TypeConstructor_DynamicArray('dynamic_array')
|
||||
"""
|
||||
This is a dynamic length piece of memory.
|
||||
|
||||
It should be applied with two arguments. It has a runtime
|
||||
determined length, and each argument is the same.
|
||||
"""
|
||||
|
||||
static_array = TypeConstructor_StaticArray('static_array')
|
||||
"""
|
||||
This is a fixed length piece of memory.
|
||||
|
||||
It should be applied with two arguments. It has a compile time
|
||||
determined length, and each argument is the same.
|
||||
"""
|
||||
|
||||
tuple_ = TypeConstructor_Tuple('tuple')
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
function = TypeConstructor_Function('function')
|
||||
"""
|
||||
This is a function.
|
||||
|
||||
It should be applied with one or more arguments. The last argument is the 'return' type.
|
||||
"""
|
||||
|
||||
struct = TypeConstructor_Struct('struct')
|
||||
"""
|
||||
This is like a tuple, but each argument is named, so that developers
|
||||
can get and set fields by name.
|
||||
"""
|
||||
|
||||
a = TypeVariable('a', TypeVariableApplication_Nullary(None, None))
|
||||
b = TypeVariable('b', TypeVariableApplication_Nullary(None, None))
|
||||
|
||||
t = TypeConstructorVariable('t')
|
||||
|
||||
Eq = Type3Class('Eq', (a, ), methods={}, operators={
|
||||
'==': [a, a, bool_],
|
||||
'!=': [a, a, bool_],
|
||||
# FIXME: Do we want to expose 'eqz'? Or is that a compiler optimization?
|
||||
})
|
||||
|
||||
instance_type_class(Eq, u8, operators={
|
||||
'==': stdtypes.u8_eq_equals,
|
||||
'!=': stdtypes.u8_eq_not_equals,
|
||||
})
|
||||
instance_type_class(Eq, u32, operators={
|
||||
'==': stdtypes.u32_eq_equals,
|
||||
'!=': stdtypes.u32_eq_not_equals,
|
||||
})
|
||||
instance_type_class(Eq, u64, operators={
|
||||
'==': stdtypes.u64_eq_equals,
|
||||
'!=': stdtypes.u64_eq_not_equals,
|
||||
})
|
||||
instance_type_class(Eq, i8, operators={
|
||||
'==': stdtypes.i8_eq_equals,
|
||||
'!=': stdtypes.i8_eq_not_equals,
|
||||
})
|
||||
instance_type_class(Eq, i32, operators={
|
||||
'==': stdtypes.i32_eq_equals,
|
||||
'!=': stdtypes.i32_eq_not_equals,
|
||||
})
|
||||
instance_type_class(Eq, i64, operators={
|
||||
'==': stdtypes.i64_eq_equals,
|
||||
'!=': stdtypes.i64_eq_not_equals,
|
||||
})
|
||||
instance_type_class(Eq, f32, operators={
|
||||
'==': stdtypes.f32_eq_equals,
|
||||
'!=': stdtypes.f32_eq_not_equals,
|
||||
})
|
||||
instance_type_class(Eq, f64, operators={
|
||||
'==': stdtypes.f64_eq_equals,
|
||||
'!=': stdtypes.f64_eq_not_equals,
|
||||
})
|
||||
|
||||
Ord = Type3Class('Ord', (a, ), methods={
|
||||
'min': [a, a, a],
|
||||
'max': [a, a, a],
|
||||
}, operators={
|
||||
'<': [a, a, bool_],
|
||||
'<=': [a, a, bool_],
|
||||
'>': [a, a, bool_],
|
||||
'>=': [a, a, bool_],
|
||||
}, inherited_classes=[Eq])
|
||||
|
||||
instance_type_class(Ord, u8, methods={
|
||||
'min': stdtypes.u8_ord_min,
|
||||
'max': stdtypes.u8_ord_max,
|
||||
}, operators={
|
||||
'<': stdtypes.u8_ord_less_than,
|
||||
'<=': stdtypes.u8_ord_less_than_or_equal,
|
||||
'>': stdtypes.u8_ord_greater_than,
|
||||
'>=': stdtypes.u8_ord_greater_than_or_equal,
|
||||
})
|
||||
instance_type_class(Ord, u32, methods={
|
||||
'min': stdtypes.u32_ord_min,
|
||||
'max': stdtypes.u32_ord_max,
|
||||
}, operators={
|
||||
'<': stdtypes.u32_ord_less_than,
|
||||
'<=': stdtypes.u32_ord_less_than_or_equal,
|
||||
'>': stdtypes.u32_ord_greater_than,
|
||||
'>=': stdtypes.u32_ord_greater_than_or_equal,
|
||||
})
|
||||
instance_type_class(Ord, u64, methods={
|
||||
'min': stdtypes.u64_ord_min,
|
||||
'max': stdtypes.u64_ord_max,
|
||||
}, operators={
|
||||
'<': stdtypes.u64_ord_less_than,
|
||||
'<=': stdtypes.u64_ord_less_than_or_equal,
|
||||
'>': stdtypes.u64_ord_greater_than,
|
||||
'>=': stdtypes.u64_ord_greater_than_or_equal,
|
||||
})
|
||||
instance_type_class(Ord, i8, methods={
|
||||
'min': stdtypes.i8_ord_min,
|
||||
'max': stdtypes.i8_ord_max,
|
||||
}, operators={
|
||||
'<': stdtypes.i8_ord_less_than,
|
||||
'<=': stdtypes.i8_ord_less_than_or_equal,
|
||||
'>': stdtypes.i8_ord_greater_than,
|
||||
'>=': stdtypes.i8_ord_greater_than_or_equal,
|
||||
})
|
||||
instance_type_class(Ord, i32, methods={
|
||||
'min': stdtypes.i32_ord_min,
|
||||
'max': stdtypes.i32_ord_max,
|
||||
}, operators={
|
||||
'<': stdtypes.i32_ord_less_than,
|
||||
'<=': stdtypes.i32_ord_less_than_or_equal,
|
||||
'>': stdtypes.i32_ord_greater_than,
|
||||
'>=': stdtypes.i32_ord_greater_than_or_equal,
|
||||
})
|
||||
instance_type_class(Ord, i64, methods={
|
||||
'min': stdtypes.i64_ord_min,
|
||||
'max': stdtypes.i64_ord_max,
|
||||
}, operators={
|
||||
'<': stdtypes.i64_ord_less_than,
|
||||
'<=': stdtypes.i64_ord_less_than_or_equal,
|
||||
'>': stdtypes.i64_ord_greater_than,
|
||||
'>=': stdtypes.i64_ord_greater_than_or_equal,
|
||||
})
|
||||
instance_type_class(Ord, f32, methods={
|
||||
'min': stdtypes.f32_ord_min,
|
||||
'max': stdtypes.f32_ord_max,
|
||||
}, operators={
|
||||
'<': stdtypes.f32_ord_less_than,
|
||||
'<=': stdtypes.f32_ord_less_than_or_equal,
|
||||
'>': stdtypes.f32_ord_greater_than,
|
||||
'>=': stdtypes.f32_ord_greater_than_or_equal,
|
||||
})
|
||||
instance_type_class(Ord, f64, methods={
|
||||
'min': stdtypes.f64_ord_min,
|
||||
'max': stdtypes.f64_ord_max,
|
||||
}, operators={
|
||||
'<': stdtypes.f64_ord_less_than,
|
||||
'<=': stdtypes.f64_ord_less_than_or_equal,
|
||||
'>': stdtypes.f64_ord_greater_than,
|
||||
'>=': stdtypes.f64_ord_greater_than_or_equal,
|
||||
})
|
||||
|
||||
Bits = Type3Class('Bits', (a, ), methods={
|
||||
'shl': [a, u32, a], # Logical shift left
|
||||
'shr': [a, u32, a], # Logical shift right
|
||||
'rotl': [a, u32, a], # Rotate bits left
|
||||
'rotr': [a, u32, a], # Rotate bits right
|
||||
# FIXME: Do we want to expose clz, ctz, popcnt?
|
||||
}, operators={
|
||||
'&': [a, a, a], # Bit-wise and
|
||||
'|': [a, a, a], # Bit-wise or
|
||||
'^': [a, a, a], # Bit-wise xor
|
||||
})
|
||||
|
||||
instance_type_class(Bits, u8, methods={
|
||||
'shl': stdtypes.u8_bits_logical_shift_left,
|
||||
'shr': stdtypes.u8_bits_logical_shift_right,
|
||||
'rotl': stdtypes.u8_bits_rotate_left,
|
||||
'rotr': stdtypes.u8_bits_rotate_right,
|
||||
}, operators={
|
||||
'&': stdtypes.u8_bits_bitwise_and,
|
||||
'|': stdtypes.u8_bits_bitwise_or,
|
||||
'^': stdtypes.u8_bits_bitwise_xor,
|
||||
})
|
||||
instance_type_class(Bits, u32, methods={
|
||||
'shl': stdtypes.u32_bits_logical_shift_left,
|
||||
'shr': stdtypes.u32_bits_logical_shift_right,
|
||||
'rotl': stdtypes.u32_bits_rotate_left,
|
||||
'rotr': stdtypes.u32_bits_rotate_right,
|
||||
}, operators={
|
||||
'&': stdtypes.u32_bits_bitwise_and,
|
||||
'|': stdtypes.u32_bits_bitwise_or,
|
||||
'^': stdtypes.u32_bits_bitwise_xor,
|
||||
})
|
||||
instance_type_class(Bits, u64, methods={
|
||||
'shl': stdtypes.u64_bits_logical_shift_left,
|
||||
'shr': stdtypes.u64_bits_logical_shift_right,
|
||||
'rotl': stdtypes.u64_bits_rotate_left,
|
||||
'rotr': stdtypes.u64_bits_rotate_right,
|
||||
}, operators={
|
||||
'&': stdtypes.u64_bits_bitwise_and,
|
||||
'|': stdtypes.u64_bits_bitwise_or,
|
||||
'^': stdtypes.u64_bits_bitwise_xor,
|
||||
})
|
||||
|
||||
NatNum = Type3Class('NatNum', (a, ), methods={}, operators={
|
||||
'+': [a, a, a],
|
||||
'-': [a, a, a],
|
||||
'*': [a, a, a],
|
||||
'<<': [a, u32, a], # Arithmic shift left
|
||||
'>>': [a, u32, a], # Arithmic shift right
|
||||
})
|
||||
|
||||
instance_type_class(NatNum, u32, operators={
|
||||
'+': stdtypes.u32_natnum_add,
|
||||
'-': stdtypes.u32_natnum_sub,
|
||||
'*': stdtypes.u32_natnum_mul,
|
||||
'<<': stdtypes.u32_natnum_arithmic_shift_left,
|
||||
'>>': stdtypes.u32_natnum_arithmic_shift_right,
|
||||
})
|
||||
instance_type_class(NatNum, u64, operators={
|
||||
'+': stdtypes.u64_natnum_add,
|
||||
'-': stdtypes.u64_natnum_sub,
|
||||
'*': stdtypes.u64_natnum_mul,
|
||||
'<<': stdtypes.u64_natnum_arithmic_shift_left,
|
||||
'>>': stdtypes.u64_natnum_arithmic_shift_right,
|
||||
})
|
||||
instance_type_class(NatNum, i32, operators={
|
||||
'+': stdtypes.i32_natnum_add,
|
||||
'-': stdtypes.i32_natnum_sub,
|
||||
'*': stdtypes.i32_natnum_mul,
|
||||
'<<': stdtypes.i32_natnum_arithmic_shift_left,
|
||||
'>>': stdtypes.i32_natnum_arithmic_shift_right,
|
||||
})
|
||||
instance_type_class(NatNum, i64, operators={
|
||||
'+': stdtypes.i64_natnum_add,
|
||||
'-': stdtypes.i64_natnum_sub,
|
||||
'*': stdtypes.i64_natnum_mul,
|
||||
'<<': stdtypes.i64_natnum_arithmic_shift_left,
|
||||
'>>': stdtypes.i64_natnum_arithmic_shift_right,
|
||||
})
|
||||
instance_type_class(NatNum, f32, operators={
|
||||
'+': stdtypes.f32_natnum_add,
|
||||
'-': stdtypes.f32_natnum_sub,
|
||||
'*': stdtypes.f32_natnum_mul,
|
||||
'<<': stdtypes.f32_natnum_arithmic_shift_left,
|
||||
'>>': stdtypes.f32_natnum_arithmic_shift_right,
|
||||
})
|
||||
instance_type_class(NatNum, f64, operators={
|
||||
'+': stdtypes.f64_natnum_add,
|
||||
'-': stdtypes.f64_natnum_sub,
|
||||
'*': stdtypes.f64_natnum_mul,
|
||||
'<<': stdtypes.f64_natnum_arithmic_shift_left,
|
||||
'>>': stdtypes.f64_natnum_arithmic_shift_right,
|
||||
})
|
||||
|
||||
IntNum = Type3Class('IntNum', (a, ), methods={
|
||||
'abs': [a, a],
|
||||
'neg': [a, a],
|
||||
}, operators={}, inherited_classes=[NatNum])
|
||||
|
||||
instance_type_class(IntNum, i32, methods={
|
||||
'abs': stdtypes.i32_intnum_abs,
|
||||
'neg': stdtypes.i32_intnum_neg,
|
||||
})
|
||||
instance_type_class(IntNum, i64, methods={
|
||||
'abs': stdtypes.i64_intnum_abs,
|
||||
'neg': stdtypes.i64_intnum_neg,
|
||||
})
|
||||
instance_type_class(IntNum, f32, methods={
|
||||
'abs': stdtypes.f32_intnum_abs,
|
||||
'neg': stdtypes.f32_intnum_neg,
|
||||
})
|
||||
instance_type_class(IntNum, f64, methods={
|
||||
'abs': stdtypes.f64_intnum_abs,
|
||||
'neg': stdtypes.f64_intnum_neg,
|
||||
})
|
||||
|
||||
Integral = Type3Class('Integral', (a, ), methods={
|
||||
}, operators={
|
||||
'//': [a, a, a],
|
||||
'%': [a, a, a],
|
||||
}, inherited_classes=[NatNum])
|
||||
|
||||
instance_type_class(Integral, u32, operators={
|
||||
'//': stdtypes.u32_integral_div,
|
||||
'%': stdtypes.u32_integral_rem,
|
||||
})
|
||||
instance_type_class(Integral, u64, operators={
|
||||
'//': stdtypes.u64_integral_div,
|
||||
'%': stdtypes.u64_integral_rem,
|
||||
})
|
||||
instance_type_class(Integral, i32, operators={
|
||||
'//': stdtypes.i32_integral_div,
|
||||
'%': stdtypes.i32_integral_rem,
|
||||
})
|
||||
instance_type_class(Integral, i64, operators={
|
||||
'//': stdtypes.i64_integral_div,
|
||||
'%': stdtypes.i64_integral_rem,
|
||||
})
|
||||
|
||||
Fractional = Type3Class('Fractional', (a, ), methods={
|
||||
'ceil': [a, a],
|
||||
'floor': [a, a],
|
||||
'trunc': [a, a],
|
||||
'nearest': [a, a],
|
||||
}, operators={
|
||||
'/': [a, a, a],
|
||||
}, inherited_classes=[NatNum])
|
||||
|
||||
instance_type_class(Fractional, f32, methods={
|
||||
'ceil': stdtypes.f32_fractional_ceil,
|
||||
'floor': stdtypes.f32_fractional_floor,
|
||||
'trunc': stdtypes.f32_fractional_trunc,
|
||||
'nearest': stdtypes.f32_fractional_nearest,
|
||||
}, operators={
|
||||
'/': stdtypes.f32_fractional_div,
|
||||
})
|
||||
instance_type_class(Fractional, f64, methods={
|
||||
'ceil': stdtypes.f64_fractional_ceil,
|
||||
'floor': stdtypes.f64_fractional_floor,
|
||||
'trunc': stdtypes.f64_fractional_trunc,
|
||||
'nearest': stdtypes.f64_fractional_nearest,
|
||||
}, operators={
|
||||
'/': stdtypes.f64_fractional_div,
|
||||
})
|
||||
|
||||
Floating = Type3Class('Floating', (a, ), methods={
|
||||
'sqrt': [a, a],
|
||||
}, operators={}, inherited_classes=[Fractional])
|
||||
|
||||
# FIXME: Do we want to expose copysign?
|
||||
|
||||
instance_type_class(Floating, f32, methods={
|
||||
'sqrt': stdtypes.f32_floating_sqrt,
|
||||
})
|
||||
instance_type_class(Floating, f64, methods={
|
||||
'sqrt': stdtypes.f64_floating_sqrt,
|
||||
})
|
||||
|
||||
Sized_ = Type3Class('Sized', (t, ), methods={
|
||||
'len': [t(a), u32],
|
||||
}, operators={}) # FIXME: Once we get type class families, add [] here
|
||||
|
||||
instance_type_class(Sized_, dynamic_array, methods={
|
||||
'len': stdtypes.dynamic_array_sized_len,
|
||||
})
|
||||
instance_type_class(Sized_, static_array, methods={
|
||||
'len': stdtypes.static_array_sized_len,
|
||||
})
|
||||
|
||||
Extendable = Type3Class('Extendable', (a, b, ), methods={
|
||||
'extend': [a, b],
|
||||
'wrap': [b, a],
|
||||
}, operators={})
|
||||
|
||||
instance_type_class(Extendable, u8, u32, methods={
|
||||
'extend': stdtypes.u8_u32_extend,
|
||||
'wrap': stdtypes.u8_u32_wrap,
|
||||
})
|
||||
instance_type_class(Extendable, u8, u64, methods={
|
||||
'extend': stdtypes.u8_u64_extend,
|
||||
'wrap': stdtypes.u8_u64_wrap,
|
||||
})
|
||||
instance_type_class(Extendable, u32, u64, methods={
|
||||
'extend': stdtypes.u32_u64_extend,
|
||||
'wrap': stdtypes.u32_u64_wrap,
|
||||
})
|
||||
instance_type_class(Extendable, i8, i32, methods={
|
||||
'extend': stdtypes.i8_i32_extend,
|
||||
'wrap': stdtypes.i8_i32_wrap,
|
||||
})
|
||||
instance_type_class(Extendable, i8, i64, methods={
|
||||
'extend': stdtypes.i8_i64_extend,
|
||||
'wrap': stdtypes.i8_i64_wrap,
|
||||
})
|
||||
instance_type_class(Extendable, i32, i64, methods={
|
||||
'extend': stdtypes.i32_i64_extend,
|
||||
'wrap': stdtypes.i32_i64_wrap,
|
||||
})
|
||||
|
||||
Promotable = Type3Class('Promotable', (a, b, ), methods={
|
||||
'promote': [a, b],
|
||||
'demote': [b, a],
|
||||
}, operators={})
|
||||
|
||||
instance_type_class(Promotable, f32, f64, methods={
|
||||
'promote': stdtypes.f32_f64_promote,
|
||||
'demote': stdtypes.f32_f64_demote,
|
||||
})
|
||||
|
||||
Foldable = Type3Class('Foldable', (t, ), methods={
|
||||
'sum': [t(a), a],
|
||||
'foldl': [[b, a, b], b, t(a), b],
|
||||
'foldr': [[a, b, b], b, t(a), b],
|
||||
}, operators={}, additional_context={
|
||||
'sum': [Constraint_TypeClassInstanceExists(NatNum, (a, ))],
|
||||
})
|
||||
|
||||
instance_type_class(Foldable, dynamic_array, methods={
|
||||
'sum': stdtypes.dynamic_array_sum,
|
||||
'foldl': stdtypes.dynamic_array_foldl,
|
||||
'foldr': stdtypes.dynamic_array_foldr,
|
||||
})
|
||||
instance_type_class(Foldable, static_array, methods={
|
||||
'sum': stdtypes.static_array_sum,
|
||||
'foldl': stdtypes.static_array_foldl,
|
||||
'foldr': stdtypes.static_array_foldr,
|
||||
})
|
||||
|
||||
bytes_ = dynamic_array(u8)
|
||||
|
||||
PRELUDE_TYPES: dict[str, Type3] = {
|
||||
'none': none,
|
||||
'bool': bool_,
|
||||
'u8': u8,
|
||||
'u32': u32,
|
||||
'u64': u64,
|
||||
'i8': i8,
|
||||
'i32': i32,
|
||||
'i64': i64,
|
||||
'f32': f32,
|
||||
'f64': f64,
|
||||
'bytes': bytes_,
|
||||
}
|
||||
|
||||
PRELUDE_TYPE_CLASSES = {
|
||||
'Eq': Eq,
|
||||
'Ord': Ord,
|
||||
'Bits': Bits,
|
||||
'NatNum': NatNum,
|
||||
'IntNum': IntNum,
|
||||
'Integral': Integral,
|
||||
'Fractional': Fractional,
|
||||
'Floating': Floating,
|
||||
'Extendable': Extendable,
|
||||
'Promotable': Promotable,
|
||||
}
|
||||
|
||||
PRELUDE_OPERATORS = {
|
||||
**Bits.operators,
|
||||
**Eq.operators,
|
||||
**Ord.operators,
|
||||
**Fractional.operators,
|
||||
**Integral.operators,
|
||||
**IntNum.operators,
|
||||
**NatNum.operators,
|
||||
}
|
||||
|
||||
PRELUDE_METHODS = {
|
||||
**Bits.methods,
|
||||
**Eq.methods,
|
||||
**Ord.methods,
|
||||
**Floating.methods,
|
||||
**Fractional.methods,
|
||||
**Integral.methods,
|
||||
**IntNum.methods,
|
||||
**NatNum.methods,
|
||||
**Sized_.methods,
|
||||
**Extendable.methods,
|
||||
**Promotable.methods,
|
||||
**Foldable.methods,
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
from . import prelude
|
||||
from .stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP
|
||||
from .type3.routers import NoRouteForTypeException, TypeApplicationRouter
|
||||
from .type3.types import IntType3, Type3
|
||||
|
||||
|
||||
def calculate_alloc_size_static_array(is_member: bool, args: tuple[Type3, IntType3]) -> int:
|
||||
if is_member:
|
||||
return TYPE_INFO_CONSTRUCTED.alloc_size
|
||||
|
||||
sa_type, sa_len = args
|
||||
|
||||
return sa_len.value * calculate_alloc_size(sa_type, is_member=True)
|
||||
|
||||
def calculate_alloc_size_tuple(is_member: bool, args: tuple[Type3, ...]) -> int:
|
||||
if is_member:
|
||||
return TYPE_INFO_CONSTRUCTED.alloc_size
|
||||
|
||||
return sum(
|
||||
calculate_alloc_size(x, is_member=True)
|
||||
for x in args
|
||||
)
|
||||
|
||||
def calculate_alloc_size_struct(is_member: bool, args: tuple[tuple[str, Type3], ...]) -> int:
|
||||
if is_member:
|
||||
return TYPE_INFO_CONSTRUCTED.alloc_size
|
||||
|
||||
return sum(
|
||||
calculate_alloc_size(x, is_member=True)
|
||||
for _, x in args
|
||||
)
|
||||
|
||||
ALLOC_SIZE_ROUTER = TypeApplicationRouter[bool, int]()
|
||||
ALLOC_SIZE_ROUTER.add(prelude.static_array, calculate_alloc_size_static_array)
|
||||
ALLOC_SIZE_ROUTER.add(prelude.struct, calculate_alloc_size_struct)
|
||||
ALLOC_SIZE_ROUTER.add(prelude.tuple_, calculate_alloc_size_tuple)
|
||||
|
||||
def calculate_alloc_size(typ: Type3, is_member: bool = False) -> int:
|
||||
typ_info = TYPE_INFO_MAP.get(typ.name)
|
||||
if typ_info is not None:
|
||||
return typ_info.alloc_size
|
||||
|
||||
try:
|
||||
return ALLOC_SIZE_ROUTER(is_member, typ)
|
||||
except NoRouteForTypeException:
|
||||
if is_member:
|
||||
# By default, 'boxed' or 'constructed' types are
|
||||
# stored as pointers when a member of a struct or tuple
|
||||
return TYPE_INFO_CONSTRUCTED.alloc_size
|
||||
|
||||
raise NotImplementedError(typ)
|
||||
|
||||
def calculate_member_offset(st_name: str, st_args: tuple[tuple[str, Type3], ...], needle: str) -> int:
|
||||
result = 0
|
||||
|
||||
for memnam, memtyp in st_args:
|
||||
if needle == memnam:
|
||||
return result
|
||||
|
||||
result += calculate_alloc_size(memtyp, is_member=True)
|
||||
|
||||
raise Exception(f'{needle} not in {st_name}')
|
||||
@ -15,7 +15,7 @@ UNALLOC_PTR = ADR_UNALLOC_PTR + 4
|
||||
|
||||
# For memory initialization see phasm.compiler.module_data
|
||||
|
||||
@func_wrapper(exported=False)
|
||||
@func_wrapper()
|
||||
def __find_free_block__(g: Generator, alloc_size: i32) -> i32:
|
||||
# Find out if we've freed any blocks at all so far
|
||||
g.i32.const(ADR_FREE_BLOCK_PTR)
|
||||
@ -32,7 +32,7 @@ def __find_free_block__(g: Generator, alloc_size: i32) -> i32:
|
||||
|
||||
return i32('return') # To satisfy mypy
|
||||
|
||||
@func_wrapper()
|
||||
@func_wrapper(exported=True)
|
||||
def __alloc__(g: Generator, alloc_size: i32) -> i32:
|
||||
result = i32('result')
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,692 +0,0 @@
|
||||
"""
|
||||
This module contains possible constraints generated based on the AST
|
||||
|
||||
These need to be resolved before the program can be compiled.
|
||||
"""
|
||||
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
|
||||
|
||||
from .. import ourlang, prelude
|
||||
from .functions import FunctionArgument, TypeVariable
|
||||
from .placeholders import PlaceholderForType, Type3OrPlaceholder
|
||||
from .routers import NoRouteForTypeException, TypeApplicationRouter
|
||||
from .typeclasses import Type3Class
|
||||
from .types import (
|
||||
IntType3,
|
||||
Type3,
|
||||
TypeApplication_Nullary,
|
||||
TypeApplication_Struct,
|
||||
TypeApplication_Type,
|
||||
TypeApplication_TypeInt,
|
||||
TypeApplication_TypeStar,
|
||||
TypeConstructor_Base,
|
||||
TypeConstructor_Struct,
|
||||
)
|
||||
|
||||
|
||||
class Error:
|
||||
"""
|
||||
An error returned by the check functions for a contraint
|
||||
|
||||
This means the programmer has to make some kind of chance to the
|
||||
typing of their program before the compiler can do its thing.
|
||||
"""
|
||||
def __init__(self, msg: str, *, comment: Optional[str] = None) -> None:
|
||||
self.msg = msg
|
||||
self.comment = comment
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Error({repr(self.msg)}, comment={repr(self.comment)})'
|
||||
|
||||
class RequireTypeSubstitutes:
|
||||
"""
|
||||
Returned by the check function for a contraint if they do not have all
|
||||
their types substituted yet.
|
||||
|
||||
Hopefully, another constraint will give the right information about the
|
||||
typing of the program, so this constraint can be updated.
|
||||
"""
|
||||
|
||||
SubstitutionMap = Dict[PlaceholderForType, Type3]
|
||||
|
||||
NewConstraintList = List['ConstraintBase']
|
||||
|
||||
CheckResult = Union[None, SubstitutionMap, Error, NewConstraintList, RequireTypeSubstitutes]
|
||||
|
||||
HumanReadableRet = Tuple[str, Dict[str, Union[None, int, str, ourlang.Expression, Type3, PlaceholderForType]]]
|
||||
|
||||
class Context:
|
||||
"""
|
||||
Context for constraints
|
||||
"""
|
||||
|
||||
__slots__ = ('type_class_instances_existing', )
|
||||
|
||||
# Constraint_TypeClassInstanceExists
|
||||
type_class_instances_existing: set[tuple[Type3Class, tuple[Union[Type3, TypeConstructor_Base[Any], TypeConstructor_Struct], ...]]]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.type_class_instances_existing = set()
|
||||
|
||||
class ConstraintBase:
|
||||
"""
|
||||
Base class for constraints
|
||||
"""
|
||||
__slots__ = ('comment', )
|
||||
|
||||
comment: Optional[str]
|
||||
"""
|
||||
A comment to help the programmer with debugging the types in their program
|
||||
"""
|
||||
|
||||
def __init__(self, comment: Optional[str] = None) -> None:
|
||||
self.comment = comment
|
||||
|
||||
def check(self) -> CheckResult:
|
||||
"""
|
||||
Checks if the constraint hold
|
||||
|
||||
This function can return an error, if the constraint does not hold,
|
||||
which indicates an error in the typing of the input program.
|
||||
|
||||
This function can return RequireTypeSubstitutes(), if we cannot deduce
|
||||
all the types yet.
|
||||
|
||||
This function can return a SubstitutionMap, if during the evaluation
|
||||
of the contraint we discovered new types. In this case, the constraint
|
||||
is expected to hold.
|
||||
|
||||
This function can return None, if the constraint holds, but no new
|
||||
information was deduced from evaluating this constraint.
|
||||
"""
|
||||
raise NotImplementedError(self.__class__, self.check)
|
||||
|
||||
def human_readable(self) -> HumanReadableRet:
|
||||
"""
|
||||
Returns a more human readable form of this constraint
|
||||
"""
|
||||
return repr(self), {}
|
||||
|
||||
class SameTypeConstraint(ConstraintBase):
|
||||
"""
|
||||
Verifies that a number of types all are the same type
|
||||
"""
|
||||
__slots__ = ('type_list', )
|
||||
|
||||
type_list: List[Type3OrPlaceholder]
|
||||
|
||||
def __init__(self, *type_list: Type3OrPlaceholder, comment: Optional[str] = None) -> None:
|
||||
super().__init__(comment=comment)
|
||||
|
||||
assert len(type_list) > 1
|
||||
self.type_list = [*type_list]
|
||||
|
||||
def check(self) -> CheckResult:
|
||||
known_types: List[Type3] = []
|
||||
phft_list = []
|
||||
for typ in self.type_list:
|
||||
if isinstance(typ, Type3):
|
||||
known_types.append(typ)
|
||||
continue
|
||||
|
||||
if isinstance(typ, PlaceholderForType):
|
||||
if typ.resolve_as is not None:
|
||||
known_types.append(typ.resolve_as)
|
||||
else:
|
||||
phft_list.append(typ)
|
||||
continue
|
||||
|
||||
raise NotImplementedError(typ)
|
||||
|
||||
if not known_types:
|
||||
return RequireTypeSubstitutes()
|
||||
|
||||
first_type = known_types[0]
|
||||
for ktyp in known_types[1:]:
|
||||
if ktyp != first_type:
|
||||
return Error(f'{ktyp:s} must be {first_type:s} instead', comment=self.comment)
|
||||
|
||||
if not phft_list:
|
||||
return None
|
||||
|
||||
for phft in phft_list:
|
||||
phft.resolve_as = first_type
|
||||
|
||||
return {
|
||||
typ: first_type
|
||||
for typ in phft_list
|
||||
}
|
||||
|
||||
def human_readable(self) -> HumanReadableRet:
|
||||
return (
|
||||
' == '.join('{t' + str(idx) + '}' for idx in range(len(self.type_list))),
|
||||
{
|
||||
't' + str(idx): typ
|
||||
for idx, typ in enumerate(self.type_list)
|
||||
},
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
args = ', '.join(repr(x) for x in self.type_list)
|
||||
|
||||
return f'SameTypeConstraint({args}, comment={repr(self.comment)})'
|
||||
|
||||
class SameTypeArgumentConstraint(ConstraintBase):
|
||||
__slots__ = ('tc_var', 'arg_var', )
|
||||
|
||||
tc_var: PlaceholderForType
|
||||
arg_var: PlaceholderForType
|
||||
|
||||
def __init__(self, tc_var: PlaceholderForType, arg_var: PlaceholderForType, *, comment: str) -> None:
|
||||
super().__init__(comment=comment)
|
||||
|
||||
self.tc_var = tc_var
|
||||
self.arg_var = arg_var
|
||||
|
||||
def check(self) -> CheckResult:
|
||||
if self.tc_var.resolve_as is None:
|
||||
return RequireTypeSubstitutes()
|
||||
|
||||
tc_typ = self.tc_var.resolve_as
|
||||
arg_typ = self.arg_var.resolve_as
|
||||
|
||||
if isinstance(tc_typ.application, TypeApplication_Nullary):
|
||||
return Error(f'{tc_typ:s} must be a constructed type instead')
|
||||
|
||||
if isinstance(tc_typ.application, TypeApplication_TypeStar):
|
||||
# Sure, it's a constructed type. But it's like a struct,
|
||||
# though without the way to implement type classes
|
||||
# Presumably, doing a naked `foo :: t a -> a`
|
||||
# doesn't work since you don't have any info on t
|
||||
# So we can let the MustImplementTypeClassConstraint handle it.
|
||||
return None
|
||||
|
||||
if isinstance(tc_typ.application, TypeApplication_Type):
|
||||
return [SameTypeConstraint(
|
||||
tc_typ.application.arguments[0],
|
||||
self.arg_var,
|
||||
comment=self.comment,
|
||||
)]
|
||||
|
||||
# FIXME: This feels sketchy. Shouldn't the type variable
|
||||
# have the exact same number as arguments?
|
||||
if isinstance(tc_typ.application, TypeApplication_TypeInt):
|
||||
return [SameTypeConstraint(
|
||||
tc_typ.application.arguments[0],
|
||||
self.arg_var,
|
||||
comment=self.comment,
|
||||
)]
|
||||
|
||||
raise NotImplementedError(tc_typ, arg_typ)
|
||||
|
||||
def human_readable(self) -> HumanReadableRet:
|
||||
return (
|
||||
'{tc_var}` == {arg_var}',
|
||||
{
|
||||
'tc_var': self.tc_var if self.tc_var.resolve_as is None else self.tc_var,
|
||||
'arg_var': self.arg_var if self.arg_var.resolve_as is None else self.arg_var,
|
||||
},
|
||||
)
|
||||
|
||||
class SameFunctionArgumentConstraint(ConstraintBase):
|
||||
__slots__ = ('type3', 'func_arg', 'type_var_map', )
|
||||
|
||||
type3: PlaceholderForType
|
||||
func_arg: FunctionArgument
|
||||
type_var_map: dict[TypeVariable, PlaceholderForType]
|
||||
|
||||
def __init__(self, type3: PlaceholderForType, func_arg: FunctionArgument, type_var_map: dict[TypeVariable, PlaceholderForType], *, comment: str) -> None:
|
||||
super().__init__(comment=comment)
|
||||
|
||||
self.type3 = type3
|
||||
self.func_arg = func_arg
|
||||
self.type_var_map = type_var_map
|
||||
|
||||
def check(self) -> CheckResult:
|
||||
if self.type3.resolve_as is None:
|
||||
return RequireTypeSubstitutes()
|
||||
|
||||
typ = self.type3.resolve_as
|
||||
|
||||
if isinstance(typ.application, TypeApplication_Nullary):
|
||||
return Error(f'{typ:s} must be a function instead')
|
||||
|
||||
if not isinstance(typ.application, TypeApplication_TypeStar):
|
||||
return Error(f'{typ:s} must be a function instead')
|
||||
|
||||
type_var_map = {
|
||||
x: y.resolve_as
|
||||
for x, y in self.type_var_map.items()
|
||||
if y.resolve_as is not None
|
||||
}
|
||||
|
||||
exp_type_arg_list = [
|
||||
tv if isinstance(tv, Type3) else type_var_map[tv]
|
||||
for tv in self.func_arg.args
|
||||
if isinstance(tv, Type3) or tv in type_var_map
|
||||
]
|
||||
|
||||
if len(exp_type_arg_list) != len(self.func_arg.args):
|
||||
return RequireTypeSubstitutes()
|
||||
|
||||
return [
|
||||
SameTypeConstraint(
|
||||
typ,
|
||||
prelude.function(*exp_type_arg_list),
|
||||
comment=self.comment,
|
||||
)
|
||||
]
|
||||
|
||||
def human_readable(self) -> HumanReadableRet:
|
||||
return (
|
||||
'{type3} == {func_arg}',
|
||||
{
|
||||
'type3': self.type3,
|
||||
'func_arg': self.func_arg.name,
|
||||
},
|
||||
)
|
||||
|
||||
class TupleMatchConstraint(ConstraintBase):
|
||||
__slots__ = ('exp_type', 'args', )
|
||||
|
||||
exp_type: Type3OrPlaceholder
|
||||
args: list[Type3OrPlaceholder]
|
||||
|
||||
def __init__(self, exp_type: Type3OrPlaceholder, args: Iterable[Type3OrPlaceholder], comment: str):
|
||||
super().__init__(comment=comment)
|
||||
|
||||
self.exp_type = exp_type
|
||||
self.args = list(args)
|
||||
|
||||
def _generate_dynamic_array(self, sa_args: tuple[Type3]) -> CheckResult:
|
||||
sa_type, = sa_args
|
||||
|
||||
return [
|
||||
SameTypeConstraint(arg, sa_type)
|
||||
for arg in self.args
|
||||
]
|
||||
|
||||
def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult:
|
||||
sa_type, sa_len = sa_args
|
||||
|
||||
if sa_len.value != len(self.args):
|
||||
return Error('Mismatch between applied types argument count', comment=self.comment)
|
||||
|
||||
return [
|
||||
SameTypeConstraint(arg, sa_type)
|
||||
for arg in self.args
|
||||
]
|
||||
|
||||
def _generate_tuple(self, tp_args: tuple[Type3, ...]) -> CheckResult:
|
||||
if len(tp_args) != len(self.args):
|
||||
return Error('Mismatch between applied types argument count', comment=self.comment)
|
||||
|
||||
return [
|
||||
SameTypeConstraint(arg, oth_arg)
|
||||
for arg, oth_arg in zip(self.args, tp_args, strict=True)
|
||||
]
|
||||
|
||||
GENERATE_ROUTER = TypeApplicationRouter['TupleMatchConstraint', CheckResult]()
|
||||
GENERATE_ROUTER.add(prelude.dynamic_array, _generate_dynamic_array)
|
||||
GENERATE_ROUTER.add(prelude.static_array, _generate_static_array)
|
||||
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)
|
||||
|
||||
def check(self) -> CheckResult:
|
||||
exp_type = self.exp_type
|
||||
if isinstance(exp_type, PlaceholderForType):
|
||||
if exp_type.resolve_as is None:
|
||||
return RequireTypeSubstitutes()
|
||||
|
||||
exp_type = exp_type.resolve_as
|
||||
|
||||
try:
|
||||
return self.__class__.GENERATE_ROUTER(self, exp_type)
|
||||
except NoRouteForTypeException:
|
||||
raise NotImplementedError(exp_type)
|
||||
|
||||
class MustImplementTypeClassConstraint(ConstraintBase):
|
||||
"""
|
||||
A type must implement a given type class
|
||||
"""
|
||||
__slots__ = ('context', 'type_class3', 'types', )
|
||||
|
||||
context: Context
|
||||
type_class3: Type3Class
|
||||
types: list[Type3OrPlaceholder]
|
||||
|
||||
def __init__(self, context: Context, type_class3: Type3Class, typ_list: list[Type3OrPlaceholder], comment: Optional[str] = None) -> None:
|
||||
super().__init__(comment=comment)
|
||||
|
||||
self.context = context
|
||||
self.type_class3 = type_class3
|
||||
self.types = typ_list
|
||||
|
||||
def check(self) -> CheckResult:
|
||||
typ_list: list[Type3 | TypeConstructor_Base[Any] | TypeConstructor_Struct] = []
|
||||
for typ in self.types:
|
||||
if isinstance(typ, PlaceholderForType) and typ.resolve_as is not None:
|
||||
typ = typ.resolve_as
|
||||
|
||||
if isinstance(typ, PlaceholderForType):
|
||||
return RequireTypeSubstitutes()
|
||||
|
||||
if isinstance(typ.application, (TypeApplication_Nullary, TypeApplication_Struct, )):
|
||||
typ_list.append(typ)
|
||||
continue
|
||||
|
||||
if isinstance(typ.application, (TypeApplication_Type, TypeApplication_TypeInt, TypeApplication_TypeStar)):
|
||||
typ_list.append(typ.application.constructor)
|
||||
continue
|
||||
|
||||
raise NotImplementedError(typ, typ.application)
|
||||
|
||||
assert len(typ_list) == len(self.types)
|
||||
|
||||
key = (self.type_class3, tuple(typ_list), )
|
||||
if key in self.context.type_class_instances_existing:
|
||||
return None
|
||||
|
||||
typ_cls_name = self.type_class3 if isinstance(self.type_class3, str) else self.type_class3.name
|
||||
typ_name_list = ' '.join(x.name for x in typ_list)
|
||||
return Error(f'Missing type class instantation: {typ_cls_name} {typ_name_list}')
|
||||
|
||||
def human_readable(self) -> HumanReadableRet:
|
||||
keys = {
|
||||
f'type{idx}': typ
|
||||
for idx, typ in enumerate(self.types)
|
||||
}
|
||||
|
||||
return (
|
||||
'Exists instance {type_class3} ' + ' '.join(f'{{{x}}}' for x in keys),
|
||||
{
|
||||
'type_class3': str(self.type_class3),
|
||||
**keys,
|
||||
},
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'MustImplementTypeClassConstraint({repr(self.type_class3)}, {repr(self.types)}, comment={repr(self.comment)})'
|
||||
|
||||
class LiteralFitsConstraint(ConstraintBase):
|
||||
"""
|
||||
A literal value fits a given type
|
||||
"""
|
||||
__slots__ = ('type3', 'literal', )
|
||||
|
||||
type3: Type3OrPlaceholder
|
||||
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
type3: Type3OrPlaceholder,
|
||||
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct],
|
||||
comment: Optional[str] = None,
|
||||
) -> None:
|
||||
super().__init__(comment=comment)
|
||||
|
||||
self.type3 = type3
|
||||
self.literal = literal
|
||||
|
||||
def _generate_dynamic_array(self, da_args: tuple[Type3]) -> CheckResult:
|
||||
if not isinstance(self.literal, ourlang.ConstantTuple):
|
||||
return Error('Must be tuple', comment=self.comment)
|
||||
|
||||
da_type, = da_args
|
||||
|
||||
res: list[ConstraintBase] = []
|
||||
|
||||
res.extend(
|
||||
LiteralFitsConstraint(da_type, y)
|
||||
for y in self.literal.value
|
||||
)
|
||||
|
||||
# Generate placeholders so each Literal expression
|
||||
# gets updated when we figure out the type of the
|
||||
# expression the literal is used in
|
||||
res.extend(
|
||||
SameTypeConstraint(da_type, PlaceholderForType([y]))
|
||||
for y in self.literal.value
|
||||
)
|
||||
|
||||
return res
|
||||
|
||||
def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult:
|
||||
if not isinstance(self.literal, ourlang.ConstantTuple):
|
||||
return Error('Must be tuple', comment=self.comment)
|
||||
|
||||
sa_type, sa_len = sa_args
|
||||
|
||||
if sa_len.value != len(self.literal.value):
|
||||
return Error('Member count mismatch', comment=self.comment)
|
||||
|
||||
res: list[ConstraintBase] = []
|
||||
|
||||
res.extend(
|
||||
LiteralFitsConstraint(sa_type, y)
|
||||
for y in self.literal.value
|
||||
)
|
||||
|
||||
# Generate placeholders so each Literal expression
|
||||
# gets updated when we figure out the type of the
|
||||
# expression the literal is used in
|
||||
res.extend(
|
||||
SameTypeConstraint(sa_type, PlaceholderForType([y]))
|
||||
for y in self.literal.value
|
||||
)
|
||||
|
||||
return res
|
||||
|
||||
def _generate_struct(self, st_args: tuple[tuple[str, Type3], ...]) -> CheckResult:
|
||||
if not isinstance(self.literal, ourlang.ConstantStruct):
|
||||
return Error('Must be struct')
|
||||
|
||||
if len(st_args) != len(self.literal.value):
|
||||
return Error('Struct element count mismatch')
|
||||
|
||||
res: list[ConstraintBase] = []
|
||||
|
||||
res.extend(
|
||||
LiteralFitsConstraint(x, y)
|
||||
for (_, x), y in zip(st_args, self.literal.value, strict=True)
|
||||
)
|
||||
|
||||
# Generate placeholders so each Literal expression
|
||||
# gets updated when we figure out the type of the
|
||||
# expression the literal is used in
|
||||
res.extend(
|
||||
SameTypeConstraint(x_t, PlaceholderForType([y]), comment=f'{self.literal.struct_type3.name}.{x_n}')
|
||||
for (x_n, x_t, ), y in zip(st_args, self.literal.value, strict=True)
|
||||
)
|
||||
|
||||
res.append(SameTypeConstraint(
|
||||
self.literal.struct_type3,
|
||||
self.type3,
|
||||
comment='Struct types must match',
|
||||
))
|
||||
|
||||
return res
|
||||
|
||||
def _generate_tuple(self, tp_args: tuple[Type3, ...]) -> CheckResult:
|
||||
if not isinstance(self.literal, ourlang.ConstantTuple):
|
||||
return Error('Must be tuple', comment=self.comment)
|
||||
|
||||
if len(tp_args) != len(self.literal.value):
|
||||
return Error('Tuple element count mismatch', comment=self.comment)
|
||||
|
||||
res: list[ConstraintBase] = []
|
||||
|
||||
res.extend(
|
||||
LiteralFitsConstraint(x, y)
|
||||
for x, y in zip(tp_args, self.literal.value, strict=True)
|
||||
)
|
||||
|
||||
# Generate placeholders so each Literal expression
|
||||
# gets updated when we figure out the type of the
|
||||
# expression the literal is used in
|
||||
res.extend(
|
||||
SameTypeConstraint(x, PlaceholderForType([y]))
|
||||
for x, y in zip(tp_args, self.literal.value, strict=True)
|
||||
)
|
||||
|
||||
return res
|
||||
|
||||
GENERATE_ROUTER = TypeApplicationRouter['LiteralFitsConstraint', CheckResult]()
|
||||
GENERATE_ROUTER.add(prelude.dynamic_array, _generate_dynamic_array)
|
||||
GENERATE_ROUTER.add(prelude.static_array, _generate_static_array)
|
||||
GENERATE_ROUTER.add(prelude.struct, _generate_struct)
|
||||
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
if isinstance(self.type3, PlaceholderForType):
|
||||
if self.type3.resolve_as is None:
|
||||
return RequireTypeSubstitutes()
|
||||
|
||||
self.type3 = self.type3.resolve_as
|
||||
|
||||
if self.type3.name in int_table:
|
||||
bts, sgn = int_table[self.type3.name]
|
||||
|
||||
if isinstance(self.literal.value, int):
|
||||
try:
|
||||
self.literal.value.to_bytes(bts, 'big', signed=sgn)
|
||||
except OverflowError:
|
||||
return Error(f'Must fit in {bts} byte(s)', comment=self.comment) # FIXME: Add line information
|
||||
|
||||
return None
|
||||
|
||||
return Error('Must be integer', comment=self.comment) # FIXME: Add line information
|
||||
|
||||
if self.type3.name in float_table:
|
||||
_ = float_table[self.type3.name]
|
||||
|
||||
if isinstance(self.literal.value, float):
|
||||
# FIXME: Bit check
|
||||
|
||||
return None
|
||||
|
||||
return Error('Must be real', comment=self.comment) # FIXME: Add line information
|
||||
|
||||
if self.type3 is prelude.bytes_:
|
||||
if isinstance(self.literal.value, bytes):
|
||||
return None
|
||||
|
||||
return Error('Must be bytes', comment=self.comment) # FIXME: Add line information
|
||||
|
||||
exp_type = self.type3
|
||||
|
||||
try:
|
||||
return self.__class__.GENERATE_ROUTER(self, exp_type)
|
||||
except NoRouteForTypeException:
|
||||
raise NotImplementedError(exp_type)
|
||||
|
||||
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)}, comment={repr(self.comment)})'
|
||||
|
||||
class CanBeSubscriptedConstraint(ConstraintBase):
|
||||
"""
|
||||
A value that is subscipted, i.e. a[0] (tuple) or a[b] (static array)
|
||||
"""
|
||||
__slots__ = ('ret_type3', 'type3', 'index_type3', 'index_const', )
|
||||
|
||||
ret_type3: PlaceholderForType
|
||||
type3: PlaceholderForType
|
||||
index_type3: PlaceholderForType
|
||||
index_const: int | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ret_type3: PlaceholderForType,
|
||||
type3: PlaceholderForType,
|
||||
index_type3: PlaceholderForType,
|
||||
index_const: int | None,
|
||||
comment: Optional[str] = None,
|
||||
) -> None:
|
||||
super().__init__(comment=comment)
|
||||
|
||||
self.ret_type3 = ret_type3
|
||||
self.type3 = type3
|
||||
self.index_type3 = index_type3
|
||||
self.index_const = index_const
|
||||
|
||||
def _generate_bytes(self) -> CheckResult:
|
||||
return [
|
||||
SameTypeConstraint(prelude.u32, self.index_type3, comment='([]) :: bytes -> u32 -> u8'),
|
||||
SameTypeConstraint(prelude.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'),
|
||||
]
|
||||
|
||||
def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult:
|
||||
sa_type, sa_len = sa_args
|
||||
|
||||
if self.index_const is not None and (self.index_const < 0 or sa_len.value <= self.index_const):
|
||||
return Error('Tuple index out of range')
|
||||
|
||||
return [
|
||||
SameTypeConstraint(prelude.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
|
||||
SameTypeConstraint(sa_type, self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
|
||||
]
|
||||
|
||||
def _generate_tuple(self, tp_args: tuple[Type3, ...]) -> CheckResult:
|
||||
# We special case tuples to allow for ease of use to the programmer
|
||||
# e.g. rather than having to do `fst a` and `snd a` and only have to-sized tuples
|
||||
# we use a[0] and a[1] and allow for a[2] and on.
|
||||
|
||||
if self.index_const is None:
|
||||
return Error('Must index with integer literal')
|
||||
|
||||
if self.index_const < 0 or len(tp_args) <= self.index_const:
|
||||
return Error('Tuple index out of range')
|
||||
|
||||
return [
|
||||
SameTypeConstraint(prelude.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
|
||||
SameTypeConstraint(tp_args[self.index_const], self.ret_type3, comment=f'Tuple subscript index {self.index_const}'),
|
||||
]
|
||||
|
||||
GENERATE_ROUTER = TypeApplicationRouter['CanBeSubscriptedConstraint', CheckResult]()
|
||||
GENERATE_ROUTER.add_n(prelude.bytes_, _generate_bytes)
|
||||
GENERATE_ROUTER.add(prelude.static_array, _generate_static_array)
|
||||
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)
|
||||
|
||||
def check(self) -> CheckResult:
|
||||
if self.type3.resolve_as is None:
|
||||
return RequireTypeSubstitutes()
|
||||
|
||||
exp_type = self.type3.resolve_as
|
||||
|
||||
try:
|
||||
return self.__class__.GENERATE_ROUTER(self, exp_type)
|
||||
except NoRouteForTypeException:
|
||||
return Error(f'{exp_type.name} cannot be subscripted')
|
||||
|
||||
def human_readable(self) -> HumanReadableRet:
|
||||
return (
|
||||
'{type3}[{index}]',
|
||||
{
|
||||
'type3': self.type3,
|
||||
'index': self.index_type3 if self.index_const is None else self.index_const,
|
||||
},
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'CanBeSubscriptedConstraint({self.ret_type3!r}, {self.type3!r}, {self.index_type3!r}, {self.index_const!r}, comment={repr(self.comment)})'
|
||||
@ -1,331 +0,0 @@
|
||||
"""
|
||||
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, prelude
|
||||
from .constraints import (
|
||||
CanBeSubscriptedConstraint,
|
||||
ConstraintBase,
|
||||
Context,
|
||||
LiteralFitsConstraint,
|
||||
MustImplementTypeClassConstraint,
|
||||
SameFunctionArgumentConstraint,
|
||||
SameTypeArgumentConstraint,
|
||||
SameTypeConstraint,
|
||||
TupleMatchConstraint,
|
||||
)
|
||||
from .functions import (
|
||||
Constraint_TypeClassInstanceExists,
|
||||
FunctionArgument,
|
||||
FunctionSignature,
|
||||
TypeVariable,
|
||||
TypeVariableApplication_Unary,
|
||||
TypeVariableContext,
|
||||
)
|
||||
from .placeholders import PlaceholderForType
|
||||
from .types import Type3, TypeApplication_Struct, TypeConstructor_Function
|
||||
|
||||
ConstraintGenerator = Generator[ConstraintBase, None, None]
|
||||
|
||||
def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]:
|
||||
ctx = Context()
|
||||
ctx.type_class_instances_existing.update(prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING)
|
||||
|
||||
return [*module(ctx, inp)]
|
||||
|
||||
def constant(ctx: Context, inp: ourlang.Constant, phft: PlaceholderForType) -> ConstraintGenerator:
|
||||
if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct)):
|
||||
yield LiteralFitsConstraint(
|
||||
phft, inp,
|
||||
comment='The given literal must fit the expected type'
|
||||
)
|
||||
return
|
||||
|
||||
raise NotImplementedError(constant, inp)
|
||||
|
||||
def expression_binary_op(ctx: Context, inp: ourlang.BinaryOp, phft: PlaceholderForType) -> ConstraintGenerator:
|
||||
return _expression_function_call(
|
||||
ctx,
|
||||
f'({inp.operator.name})',
|
||||
inp.operator.signature,
|
||||
[inp.left, inp.right],
|
||||
inp,
|
||||
phft,
|
||||
)
|
||||
|
||||
def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: PlaceholderForType) -> ConstraintGenerator:
|
||||
if isinstance(inp.function, ourlang.FunctionParam):
|
||||
assert isinstance(inp.function.type3.application.constructor, TypeConstructor_Function)
|
||||
signature = FunctionSignature(
|
||||
TypeVariableContext(),
|
||||
inp.function.type3.application.arguments,
|
||||
)
|
||||
else:
|
||||
signature = inp.function.signature
|
||||
|
||||
return _expression_function_call(
|
||||
ctx,
|
||||
inp.function.name,
|
||||
signature,
|
||||
inp.arguments,
|
||||
inp,
|
||||
phft,
|
||||
)
|
||||
|
||||
def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: PlaceholderForType) -> ConstraintGenerator:
|
||||
yield SameTypeConstraint(
|
||||
prelude.function(*(x.type3 for x in inp.function.posonlyargs), inp.function.returns_type3),
|
||||
phft,
|
||||
comment=f'typeOf("{inp.function.name}") == typeOf({inp.function.name})',
|
||||
)
|
||||
|
||||
def _expression_function_call(
|
||||
ctx: Context,
|
||||
func_name: str,
|
||||
signature: FunctionSignature,
|
||||
arguments: list[ourlang.Expression],
|
||||
return_expr: ourlang.Expression,
|
||||
return_phft: PlaceholderForType,
|
||||
) -> ConstraintGenerator:
|
||||
"""
|
||||
Generates all type-level constraints for a function call.
|
||||
|
||||
A Binary operator functions pretty much the same as a function call
|
||||
with two arguments - it's only a syntactic difference.
|
||||
"""
|
||||
# First create placeholders for all arguments, and generate their constraints
|
||||
arg_placeholders = {
|
||||
arg_expr: PlaceholderForType([arg_expr])
|
||||
for arg_expr in arguments
|
||||
}
|
||||
arg_placeholders[return_expr] = return_phft
|
||||
|
||||
for call_arg in arguments:
|
||||
yield from expression(ctx, call_arg, arg_placeholders[call_arg])
|
||||
|
||||
# Then generate placeholders the function signature
|
||||
# and apply constraints that the function requires
|
||||
# Skip any fully reference types
|
||||
# Making this a map ensures that if a function signature has
|
||||
# the same type on multiple arguments, we only get one
|
||||
# placeholder here. These don't need to update anything once
|
||||
# subsituted - that's done by arg_placeholders.
|
||||
type_var_map = {
|
||||
x: PlaceholderForType([])
|
||||
for x in signature.args
|
||||
if isinstance(x, TypeVariable)
|
||||
}
|
||||
|
||||
for constraint in signature.context.constraints:
|
||||
if isinstance(constraint, Constraint_TypeClassInstanceExists):
|
||||
yield MustImplementTypeClassConstraint(
|
||||
ctx,
|
||||
constraint.type_class3,
|
||||
[type_var_map[x] for x in constraint.types],
|
||||
)
|
||||
continue
|
||||
|
||||
raise NotImplementedError(constraint)
|
||||
|
||||
func_var_map = {
|
||||
x: PlaceholderForType([])
|
||||
for x in signature.args
|
||||
if isinstance(x, FunctionArgument)
|
||||
}
|
||||
|
||||
# If some of the function arguments are functions,
|
||||
# we need to deal with those separately.
|
||||
for sig_arg in signature.args:
|
||||
if not isinstance(sig_arg, FunctionArgument):
|
||||
continue
|
||||
|
||||
# Ensure that for all type variables in the function
|
||||
# there are also type variables available
|
||||
for func_arg in sig_arg.args:
|
||||
if isinstance(func_arg, Type3):
|
||||
continue
|
||||
|
||||
type_var_map.setdefault(func_arg, PlaceholderForType([]))
|
||||
|
||||
yield SameFunctionArgumentConstraint(
|
||||
func_var_map[sig_arg],
|
||||
sig_arg,
|
||||
type_var_map,
|
||||
comment=f'Ensure `{sig_arg.name}` matches in {signature}',
|
||||
)
|
||||
|
||||
# If some of the function arguments are type constructors,
|
||||
# we need to deal with those separately.
|
||||
# That is, given `foo :: t a -> a` we need to ensure
|
||||
# that both a's are the same.
|
||||
for sig_arg in signature.args:
|
||||
if isinstance(sig_arg, Type3):
|
||||
# Not a type variable at all
|
||||
continue
|
||||
|
||||
if isinstance(sig_arg, FunctionArgument):
|
||||
continue
|
||||
|
||||
if sig_arg.application.constructor is None:
|
||||
# Not a type variable for a type constructor
|
||||
continue
|
||||
|
||||
if not isinstance(sig_arg.application, TypeVariableApplication_Unary):
|
||||
raise NotImplementedError(sig_arg.application)
|
||||
|
||||
if sig_arg.application.arguments not in type_var_map:
|
||||
# e.g., len :: t a -> u32
|
||||
# i.e. "a" does not matter at all
|
||||
continue
|
||||
|
||||
yield SameTypeArgumentConstraint(
|
||||
type_var_map[sig_arg],
|
||||
type_var_map[sig_arg.application.arguments],
|
||||
comment=f'Ensure `{sig_arg.application.arguments.name}` matches in {signature}',
|
||||
)
|
||||
|
||||
# Lastly, tie the signature and expression together
|
||||
for arg_no, (sig_part, arg_expr) in enumerate(zip(signature.args, arguments + [return_expr], strict=True)):
|
||||
if arg_no == len(arguments):
|
||||
comment = f'The type of a function call to {func_name} is the same as the type that the function returns'
|
||||
else:
|
||||
comment = f'The type of the value passed to argument {arg_no} of function {func_name} should match the type of that argument'
|
||||
|
||||
if isinstance(sig_part, TypeVariable):
|
||||
yield SameTypeConstraint(type_var_map[sig_part], arg_placeholders[arg_expr], comment=comment)
|
||||
continue
|
||||
|
||||
if isinstance(sig_part, Type3):
|
||||
yield SameTypeConstraint(sig_part, arg_placeholders[arg_expr], comment=comment)
|
||||
continue
|
||||
|
||||
if isinstance(sig_part, FunctionArgument):
|
||||
yield SameTypeConstraint(func_var_map[sig_part], arg_placeholders[arg_expr], comment=comment)
|
||||
continue
|
||||
|
||||
raise NotImplementedError(sig_part)
|
||||
return
|
||||
|
||||
def expression(ctx: Context, inp: ourlang.Expression, phft: PlaceholderForType) -> ConstraintGenerator:
|
||||
if isinstance(inp, ourlang.Constant):
|
||||
yield from constant(ctx, inp, phft)
|
||||
return
|
||||
|
||||
if isinstance(inp, ourlang.VariableReference):
|
||||
yield SameTypeConstraint(inp.variable.type3, phft,
|
||||
comment=f'typeOf("{inp.variable.name}") == typeOf({inp.variable.name})')
|
||||
return
|
||||
|
||||
if isinstance(inp, ourlang.BinaryOp):
|
||||
yield from expression_binary_op(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):
|
||||
r_type = []
|
||||
for arg in inp.elements:
|
||||
arg_phft = PlaceholderForType([arg])
|
||||
yield from expression(ctx, arg, arg_phft)
|
||||
r_type.append(arg_phft)
|
||||
|
||||
yield TupleMatchConstraint(
|
||||
phft,
|
||||
r_type,
|
||||
comment='The type of a tuple is a combination of its members'
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
if isinstance(inp, ourlang.Subscript):
|
||||
varref_phft = PlaceholderForType([inp.varref])
|
||||
index_phft = PlaceholderForType([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(phft, varref_phft, index_phft, inp.index.value)
|
||||
else:
|
||||
yield CanBeSubscriptedConstraint(phft, varref_phft, index_phft, None)
|
||||
return
|
||||
|
||||
if isinstance(inp, ourlang.AccessStructMember):
|
||||
assert isinstance(inp.struct_type3.application, TypeApplication_Struct) # FIXME: See test_struct.py::test_struct_not_accessible
|
||||
|
||||
mem_typ = dict(inp.struct_type3.application.arguments)[inp.member]
|
||||
|
||||
yield from expression(ctx, inp.varref, PlaceholderForType([inp.varref])) # TODO
|
||||
yield SameTypeConstraint(mem_typ, phft,
|
||||
comment=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 statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementReturn) -> ConstraintGenerator:
|
||||
phft = PlaceholderForType([inp.value])
|
||||
|
||||
yield from expression(ctx, inp.value, phft)
|
||||
|
||||
yield SameTypeConstraint(fun.returns_type3, phft,
|
||||
comment=f'The type of the value returned from function {fun.name} should match its return type')
|
||||
|
||||
def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator:
|
||||
test_phft = PlaceholderForType([inp.test])
|
||||
|
||||
yield from expression(ctx, inp.test, test_phft)
|
||||
|
||||
yield SameTypeConstraint(test_phft, prelude.bool_,
|
||||
comment='Must pass a boolean expression to if')
|
||||
|
||||
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(statement, fun, inp)
|
||||
|
||||
def function(ctx: Context, inp: ourlang.Function) -> ConstraintGenerator:
|
||||
assert not inp.imported
|
||||
|
||||
if isinstance(inp, ourlang.StructConstructor):
|
||||
return
|
||||
|
||||
for stmt in inp.statements:
|
||||
yield from statement(ctx, inp, stmt)
|
||||
|
||||
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> ConstraintGenerator:
|
||||
phft = PlaceholderForType([inp.constant])
|
||||
|
||||
yield from constant(ctx, inp.constant, phft)
|
||||
yield SameTypeConstraint(inp.type3, phft,
|
||||
comment=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) -> 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)
|
||||
@ -1,159 +0,0 @@
|
||||
"""
|
||||
Entry point to the type3 system
|
||||
"""
|
||||
from typing import Dict, List
|
||||
|
||||
from .. import codestyle, ourlang
|
||||
from .constraints import (
|
||||
ConstraintBase,
|
||||
Error,
|
||||
RequireTypeSubstitutes,
|
||||
SameTypeConstraint,
|
||||
SubstitutionMap,
|
||||
)
|
||||
from .constraintsgenerator import phasm_type3_generate_constraints
|
||||
from .placeholders import (
|
||||
PlaceholderForType,
|
||||
Type3OrPlaceholder,
|
||||
)
|
||||
from .types import 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] = {}
|
||||
|
||||
error_list: List[Error] = []
|
||||
for _ in range(MAX_RESTACK_COUNT):
|
||||
if verbose:
|
||||
print()
|
||||
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
|
||||
|
||||
old_constraint_ids = {id(x) for x in constraint_list}
|
||||
old_placeholder_substitutes_len = len(placeholder_substitutes)
|
||||
|
||||
back_on_todo_list_count = 0
|
||||
|
||||
new_constraint_list = []
|
||||
for constraint in constraint_list:
|
||||
check_result = constraint.check()
|
||||
if check_result is None:
|
||||
if verbose:
|
||||
print_constraint(placeholder_id_map, constraint)
|
||||
print('-> Constraint checks out')
|
||||
continue
|
||||
|
||||
if isinstance(check_result, dict):
|
||||
placeholder_substitutes.update(check_result)
|
||||
|
||||
if verbose:
|
||||
print_constraint(placeholder_id_map, constraint)
|
||||
print('-> Constraint checks out, and gave us new information')
|
||||
continue
|
||||
|
||||
if isinstance(check_result, Error):
|
||||
error_list.append(check_result)
|
||||
if verbose:
|
||||
print_constraint(placeholder_id_map, constraint)
|
||||
print('-> Got an error')
|
||||
continue
|
||||
|
||||
if isinstance(check_result, RequireTypeSubstitutes):
|
||||
new_constraint_list.append(constraint)
|
||||
|
||||
back_on_todo_list_count += 1
|
||||
continue
|
||||
|
||||
if isinstance(check_result, list):
|
||||
new_constraint_list.extend(check_result)
|
||||
|
||||
if verbose:
|
||||
print_constraint(placeholder_id_map, constraint)
|
||||
print(f'-> Resulted in {len(check_result)} new constraints')
|
||||
continue
|
||||
|
||||
raise NotImplementedError(constraint, check_result)
|
||||
|
||||
if verbose and 0 < back_on_todo_list_count:
|
||||
print(f'{back_on_todo_list_count} constraints skipped for now')
|
||||
|
||||
if not new_constraint_list:
|
||||
constraint_list = new_constraint_list
|
||||
break
|
||||
|
||||
# Infinite loop detection
|
||||
new_constraint_ids = {id(x) for x in new_constraint_list}
|
||||
new_placeholder_substitutes_len = len(placeholder_substitutes)
|
||||
|
||||
if old_constraint_ids == new_constraint_ids and old_placeholder_substitutes_len == new_placeholder_substitutes_len:
|
||||
if error_list:
|
||||
raise Type3Exception(error_list)
|
||||
|
||||
raise Exception('Cannot type this program - not enough information')
|
||||
|
||||
constraint_list = new_constraint_list
|
||||
|
||||
if verbose:
|
||||
print()
|
||||
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
|
||||
|
||||
if constraint_list:
|
||||
raise Exception(f'Cannot type this program - tried {MAX_RESTACK_COUNT} iterations')
|
||||
|
||||
if error_list:
|
||||
raise Type3Exception(error_list)
|
||||
|
||||
# FIXME: This doesn't work with e.g. `:: [a] -> a`, as the placeholder is inside a type
|
||||
for plh, typ in placeholder_substitutes.items():
|
||||
for expr in plh.update_on_substitution:
|
||||
expr.type3 = typ
|
||||
|
||||
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) or isinstance(fmt_val, PlaceholderForType):
|
||||
fmt_val = get_printable_type_name(fmt_val, placeholder_id_map)
|
||||
|
||||
if not isinstance(fmt_val, str):
|
||||
fmt_val = repr(fmt_val)
|
||||
|
||||
act_fmt[fmt_key] = fmt_val
|
||||
|
||||
if constraint.comment is not None:
|
||||
print('- ' + txt.format(**act_fmt).ljust(40) + '; ' + constraint.comment)
|
||||
else:
|
||||
print('- ' + txt.format(**act_fmt))
|
||||
|
||||
def get_printable_type_name(inp: Type3OrPlaceholder, placeholder_id_map: Dict[int, str]) -> str:
|
||||
if isinstance(inp, Type3):
|
||||
return inp.name
|
||||
|
||||
if isinstance(inp, PlaceholderForType):
|
||||
placeholder_id = id(inp)
|
||||
if placeholder_id not in placeholder_id_map:
|
||||
placeholder_id_map[placeholder_id] = 'T' + str(len(placeholder_id_map) + 1)
|
||||
return placeholder_id_map[placeholder_id]
|
||||
|
||||
raise NotImplementedError(inp)
|
||||
|
||||
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, comment='Deduced type'))
|
||||
|
||||
for constraint in constraint_list:
|
||||
print_constraint(placeholder_id_map, constraint)
|
||||
print('=== ^ type3 constraint_list ^ === ')
|
||||
@ -1,188 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Hashable, Iterable, List
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .typeclasses import Type3Class
|
||||
from .types import Type3
|
||||
|
||||
|
||||
class TypeVariableApplication_Base[T: Hashable, S: Hashable]:
|
||||
"""
|
||||
Records the constructor and arguments used to create this type.
|
||||
|
||||
Nullary types, or types of kind *, have both arguments set to None.
|
||||
"""
|
||||
constructor: T
|
||||
arguments: S
|
||||
|
||||
def __init__(self, constructor: T, arguments: S) -> None:
|
||||
self.constructor = constructor
|
||||
self.arguments = arguments
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.constructor, self.arguments, ))
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, TypeVariableApplication_Base):
|
||||
raise NotImplementedError
|
||||
|
||||
return (self.constructor == other.constructor # type: ignore[no-any-return]
|
||||
and self.arguments == other.arguments)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})'
|
||||
|
||||
class TypeVariable:
|
||||
"""
|
||||
Types variable are used in function definition.
|
||||
|
||||
They are used in places where you don't know the exact type.
|
||||
They are different from PlaceholderForType, as those are instanced
|
||||
during type checking. These type variables are used solely in the
|
||||
function's definition
|
||||
"""
|
||||
__slots__ = ('name', 'application', )
|
||||
|
||||
name: str
|
||||
application: TypeVariableApplication_Base[Any, Any]
|
||||
|
||||
def __init__(self, name: str, application: TypeVariableApplication_Base[Any, Any]) -> None:
|
||||
self.name = name
|
||||
self.application = application
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.name, self.application, ))
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, TypeVariable):
|
||||
raise NotImplementedError
|
||||
|
||||
return (self.name == other.name
|
||||
and self.application == other.application)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'TypeVariable({repr(self.name)})'
|
||||
|
||||
class TypeVariableApplication_Nullary(TypeVariableApplication_Base[None, None]):
|
||||
"""
|
||||
For the type for this function argument it's not relevant if it was constructed.
|
||||
"""
|
||||
|
||||
class TypeConstructorVariable:
|
||||
"""
|
||||
Types constructor variable are used in function definition.
|
||||
|
||||
They are a lot like TypeVariable, except that they represent a
|
||||
type constructor rather than a type directly.
|
||||
|
||||
For now, we only have type constructor variables for kind
|
||||
* -> *.
|
||||
"""
|
||||
__slots__ = ('name', )
|
||||
|
||||
name: str
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
self.name = name
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.name, ))
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if other is None:
|
||||
return False
|
||||
|
||||
if not isinstance(other, TypeConstructorVariable):
|
||||
raise NotImplementedError
|
||||
|
||||
return (self.name == other.name)
|
||||
|
||||
def __call__(self, tvar: TypeVariable) -> 'TypeVariable':
|
||||
return TypeVariable(
|
||||
self.name + ' ' + tvar.name,
|
||||
TypeVariableApplication_Unary(self, tvar)
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'TypeConstructorVariable({self.name!r})'
|
||||
|
||||
class TypeVariableApplication_Unary(TypeVariableApplication_Base[TypeConstructorVariable, TypeVariable]):
|
||||
"""
|
||||
The type for this function argument should be constructed from a type constructor.
|
||||
|
||||
And we need to know what construtor that was, since that's the one we support.
|
||||
"""
|
||||
|
||||
class ConstraintBase:
|
||||
__slots__ = ()
|
||||
|
||||
class Constraint_TypeClassInstanceExists(ConstraintBase):
|
||||
__slots__ = ('type_class3', 'types', )
|
||||
|
||||
type_class3: 'Type3Class'
|
||||
types: list[TypeVariable]
|
||||
|
||||
def __init__(self, type_class3: 'Type3Class', types: Iterable[TypeVariable]) -> None:
|
||||
self.type_class3 = type_class3
|
||||
self.types = list(types)
|
||||
|
||||
# Sanity check. AFAIK, if you have a multi-parameter type class,
|
||||
# you can only add a constraint by supplying types for all variables
|
||||
assert len(self.type_class3.args) == len(self.types)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.type_class3.name + ' ' + ' '.join(x.name for x in self.types)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Constraint_TypeClassInstanceExists({self.type_class3.name}, {self.types!r})'
|
||||
|
||||
class TypeVariableContext:
|
||||
__slots__ = ('constraints', )
|
||||
|
||||
constraints: list[ConstraintBase]
|
||||
|
||||
def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None:
|
||||
self.constraints = list(constraints)
|
||||
|
||||
def __copy__(self) -> 'TypeVariableContext':
|
||||
return TypeVariableContext(self.constraints)
|
||||
|
||||
def __str__(self) -> str:
|
||||
if not self.constraints:
|
||||
return ''
|
||||
|
||||
return '(' + ', '.join(str(x) for x in self.constraints) + ') => '
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'TypeVariableContext({self.constraints!r})'
|
||||
|
||||
class FunctionArgument:
|
||||
__slots__ = ('args', 'name', )
|
||||
|
||||
args: list[Type3 | TypeVariable]
|
||||
name: str
|
||||
|
||||
def __init__(self, args: list[Type3 | TypeVariable]) -> None:
|
||||
self.args = args
|
||||
|
||||
self.name = '(' + ' -> '.join(x.name for x in args) + ')'
|
||||
|
||||
class FunctionSignature:
|
||||
__slots__ = ('context', 'args', )
|
||||
|
||||
context: TypeVariableContext
|
||||
args: List[Type3 | TypeVariable | FunctionArgument]
|
||||
|
||||
def __init__(self, context: TypeVariableContext, args: Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]) -> None:
|
||||
self.context = context.__copy__()
|
||||
self.args = list(
|
||||
FunctionArgument(x) if isinstance(x, list) else x
|
||||
for x in args
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.context) + ' -> '.join(x.name for x in self.args)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'FunctionSignature({self.context!r}, {self.args!r})'
|
||||
@ -1,66 +0,0 @@
|
||||
"""
|
||||
Contains the placeholder for types for use in Phasm.
|
||||
|
||||
These are temporary while the compiler is calculating all the types and validating them.
|
||||
"""
|
||||
from typing import Any, Iterable, List, Optional, Protocol, Union
|
||||
|
||||
from .types import Type3
|
||||
|
||||
|
||||
class ExpressionProtocol(Protocol):
|
||||
"""
|
||||
A protocol for classes that should be updated on substitution
|
||||
"""
|
||||
|
||||
type3: Type3 | None
|
||||
"""
|
||||
The type to update
|
||||
"""
|
||||
|
||||
class PlaceholderForType:
|
||||
"""
|
||||
A placeholder type, for when we don't know the final type yet
|
||||
"""
|
||||
__slots__ = ('update_on_substitution', 'resolve_as', )
|
||||
|
||||
update_on_substitution: List[ExpressionProtocol]
|
||||
resolve_as: Optional[Type3]
|
||||
|
||||
def __init__(self, update_on_substitution: Iterable[ExpressionProtocol]) -> None:
|
||||
self.update_on_substitution = [*update_on_substitution]
|
||||
self.resolve_as = None
|
||||
|
||||
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 isinstance(other, Type3):
|
||||
return False
|
||||
|
||||
if not isinstance(other, PlaceholderForType):
|
||||
raise NotImplementedError
|
||||
|
||||
return self is other
|
||||
|
||||
def __ne__(self, other: Any) -> bool:
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return 0 # Valid but performs badly
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
Type3OrPlaceholder = Union[Type3, PlaceholderForType]
|
||||
@ -1,142 +0,0 @@
|
||||
from typing import Any, Callable
|
||||
|
||||
from .functions import (
|
||||
TypeConstructorVariable,
|
||||
TypeVariable,
|
||||
TypeVariableApplication_Nullary,
|
||||
TypeVariableApplication_Unary,
|
||||
)
|
||||
from .typeclasses import Type3ClassArgs
|
||||
from .types import (
|
||||
KindArgument,
|
||||
Type3,
|
||||
TypeApplication_Type,
|
||||
TypeApplication_TypeInt,
|
||||
TypeConstructor_Base,
|
||||
)
|
||||
|
||||
|
||||
class NoRouteForTypeException(Exception):
|
||||
pass
|
||||
|
||||
class TypeApplicationRouter[S, R]:
|
||||
"""
|
||||
Helper class to find a method based on a constructed type
|
||||
"""
|
||||
__slots__ = ('by_constructor', 'by_type', )
|
||||
|
||||
by_constructor: dict[Any, Callable[[S, Any], R]]
|
||||
"""
|
||||
Contains all the added routing functions for constructed types
|
||||
"""
|
||||
|
||||
by_type: dict[Type3, Callable[[S], R]]
|
||||
"""
|
||||
Contains all the added routing functions for constructed types
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.by_constructor = {}
|
||||
self.by_type = {}
|
||||
|
||||
def add_n(self, typ: Type3, helper: Callable[[S], R]) -> None:
|
||||
"""
|
||||
Lets you route to types that were not constructed
|
||||
|
||||
Also known types of kind *
|
||||
"""
|
||||
self.by_type[typ] = helper
|
||||
|
||||
def add[T](self, constructor: TypeConstructor_Base[T], helper: Callable[[S, T], R]) -> None:
|
||||
self.by_constructor[constructor] = helper
|
||||
|
||||
def __call__(self, arg0: S, typ: Type3) -> R:
|
||||
t_helper = self.by_type.get(typ)
|
||||
if t_helper is not None:
|
||||
return t_helper(arg0)
|
||||
|
||||
c_helper = self.by_constructor.get(typ.application.constructor)
|
||||
if c_helper is not None:
|
||||
return c_helper(arg0, typ.application.arguments)
|
||||
|
||||
raise NoRouteForTypeException(arg0, typ)
|
||||
|
||||
TypeVariableLookup = tuple[
|
||||
dict[TypeVariable, KindArgument],
|
||||
dict[TypeConstructorVariable, TypeConstructor_Base[Any]],
|
||||
]
|
||||
|
||||
class TypeClassArgsRouter[S, R]:
|
||||
"""
|
||||
Helper class to find a method based on a type class argument list
|
||||
"""
|
||||
__slots__ = ('args', 'data', )
|
||||
|
||||
args: Type3ClassArgs
|
||||
|
||||
data: dict[tuple[Type3 | TypeConstructor_Base[Any], ...], Callable[[S, TypeVariableLookup], R]]
|
||||
|
||||
def __init__(self, args: Type3ClassArgs) -> None:
|
||||
self.args = args
|
||||
self.data = {}
|
||||
|
||||
def add(
|
||||
self,
|
||||
tv_map: dict[TypeVariable, Type3],
|
||||
tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]],
|
||||
helper: Callable[[S, TypeVariableLookup], R],
|
||||
) -> None:
|
||||
|
||||
key: list[Type3 | TypeConstructor_Base[Any]] = []
|
||||
|
||||
for tc_arg in self.args:
|
||||
if isinstance(tc_arg, TypeVariable):
|
||||
key.append(tv_map[tc_arg])
|
||||
else:
|
||||
key.append(tc_map[tc_arg])
|
||||
|
||||
self.data[tuple(key)] = helper
|
||||
|
||||
def __call__(self, arg0: S, tv_map: dict[TypeVariable, Type3]) -> R:
|
||||
key: list[Type3 | TypeConstructor_Base[Any]] = []
|
||||
arguments: TypeVariableLookup = (dict(tv_map), {}, )
|
||||
|
||||
for tc_arg in self.args:
|
||||
if isinstance(tc_arg, TypeVariable):
|
||||
key.append(tv_map[tc_arg])
|
||||
arguments[0][tc_arg] = tv_map[tc_arg]
|
||||
continue
|
||||
|
||||
for tvar, typ in tv_map.items():
|
||||
tvar_constructor = tvar.application.constructor
|
||||
if tvar_constructor != tc_arg:
|
||||
continue
|
||||
|
||||
key.append(typ.application.constructor)
|
||||
arguments[1][tc_arg] = typ.application.constructor
|
||||
|
||||
if isinstance(tvar.application, TypeVariableApplication_Unary):
|
||||
if isinstance(typ.application, TypeApplication_Type):
|
||||
da_type, = typ.application.arguments
|
||||
sa_type_tv = tvar.application.arguments
|
||||
arguments[0][sa_type_tv] = da_type
|
||||
continue
|
||||
|
||||
# FIXME: This feels sketchy. Shouldn't the type variable
|
||||
# have the exact same number as arguments?
|
||||
if isinstance(typ.application, TypeApplication_TypeInt):
|
||||
sa_type, sa_len = typ.application.arguments
|
||||
sa_type_tv = tvar.application.arguments
|
||||
sa_len_tv = TypeVariable(sa_type_tv.name + '*', TypeVariableApplication_Nullary(None, None))
|
||||
|
||||
arguments[0][sa_type_tv] = sa_type
|
||||
arguments[0][sa_len_tv] = sa_len
|
||||
continue
|
||||
|
||||
raise NotImplementedError(tvar.application, typ.application)
|
||||
|
||||
t_helper = self.data.get(tuple(key))
|
||||
if t_helper is not None:
|
||||
return t_helper(arg0, arguments)
|
||||
|
||||
raise NoRouteForTypeException(arg0, tv_map)
|
||||
@ -1,104 +0,0 @@
|
||||
from typing import Dict, Iterable, List, Mapping, Optional
|
||||
|
||||
from .functions import (
|
||||
Constraint_TypeClassInstanceExists,
|
||||
ConstraintBase,
|
||||
FunctionSignature,
|
||||
TypeConstructorVariable,
|
||||
TypeVariable,
|
||||
TypeVariableContext,
|
||||
)
|
||||
from .types import Type3
|
||||
|
||||
|
||||
class Type3ClassMethod:
|
||||
__slots__ = ('name', 'signature', )
|
||||
|
||||
name: str
|
||||
signature: FunctionSignature
|
||||
|
||||
def __init__(self, name: str, signature: FunctionSignature) -> None:
|
||||
self.name = name
|
||||
self.signature = signature
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} :: {self.signature}'
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Type3ClassMethod({repr(self.name)}, {repr(self.signature)})'
|
||||
|
||||
Type3ClassArgs = tuple[TypeVariable] | tuple[TypeVariable, TypeVariable] | tuple[TypeConstructorVariable]
|
||||
|
||||
class Type3Class:
|
||||
__slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', )
|
||||
|
||||
name: str
|
||||
args: Type3ClassArgs
|
||||
methods: Dict[str, Type3ClassMethod]
|
||||
operators: Dict[str, Type3ClassMethod]
|
||||
inherited_classes: List['Type3Class']
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
args: Type3ClassArgs,
|
||||
methods: Mapping[str, Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]],
|
||||
operators: Mapping[str, Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]],
|
||||
inherited_classes: Optional[List['Type3Class']] = None,
|
||||
additional_context: Optional[Mapping[str, Iterable[ConstraintBase]]] = None,
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.args = args
|
||||
|
||||
self.methods = {
|
||||
k: Type3ClassMethod(k, _create_signature(v, self))
|
||||
for k, v in methods.items()
|
||||
}
|
||||
self.operators = {
|
||||
k: Type3ClassMethod(k, _create_signature(v, self))
|
||||
for k, v in operators.items()
|
||||
}
|
||||
self.inherited_classes = inherited_classes or []
|
||||
|
||||
if additional_context:
|
||||
for func_name, constraint_list in additional_context.items():
|
||||
func = self.methods.get(func_name) or self.operators.get(func_name)
|
||||
assert func is not None # type hint
|
||||
|
||||
func.signature.context.constraints.extend(constraint_list)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def _create_signature(
|
||||
method_arg_list: Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]],
|
||||
type_class3: Type3Class,
|
||||
) -> FunctionSignature:
|
||||
context = TypeVariableContext()
|
||||
if not isinstance(type_class3.args[0], TypeConstructorVariable):
|
||||
context.constraints.append(Constraint_TypeClassInstanceExists(type_class3, type_class3.args))
|
||||
|
||||
signature_args: list[Type3 | TypeVariable | list[Type3 | TypeVariable]] = []
|
||||
for method_arg in method_arg_list:
|
||||
if isinstance(method_arg, Type3):
|
||||
signature_args.append(method_arg)
|
||||
continue
|
||||
|
||||
if isinstance(method_arg, list):
|
||||
signature_args.append(method_arg)
|
||||
continue
|
||||
|
||||
if isinstance(method_arg, TypeVariable):
|
||||
type_constructor = method_arg.application.constructor
|
||||
if type_constructor is None:
|
||||
signature_args.append(method_arg)
|
||||
continue
|
||||
|
||||
if (type_constructor, ) == type_class3.args:
|
||||
context.constraints.append(Constraint_TypeClassInstanceExists(type_class3, [method_arg]))
|
||||
signature_args.append(method_arg)
|
||||
continue
|
||||
|
||||
raise NotImplementedError(method_arg)
|
||||
|
||||
return FunctionSignature(context, signature_args)
|
||||
@ -1,290 +0,0 @@
|
||||
"""
|
||||
Contains the final types for use in Phasm, as well as construtors.
|
||||
"""
|
||||
from typing import (
|
||||
Any,
|
||||
Hashable,
|
||||
Self,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
)
|
||||
|
||||
S = TypeVar('S')
|
||||
T = TypeVar('T')
|
||||
|
||||
class KindArgument:
|
||||
pass
|
||||
|
||||
class TypeApplication_Base[T: Hashable, S: Hashable]:
|
||||
"""
|
||||
Records the constructor and arguments used to create this type.
|
||||
|
||||
Nullary types, or types of kind *, have both arguments set to None.
|
||||
"""
|
||||
constructor: T
|
||||
arguments: S
|
||||
|
||||
def __init__(self, constructor: T, arguments: S) -> None:
|
||||
self.constructor = constructor
|
||||
self.arguments = arguments
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.constructor, self.arguments, ))
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, TypeApplication_Base):
|
||||
raise NotImplementedError
|
||||
|
||||
return (self.constructor == other.constructor # type: ignore[no-any-return]
|
||||
and self.arguments == other.arguments)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})'
|
||||
|
||||
class Type3(KindArgument):
|
||||
"""
|
||||
Base class for the type3 types
|
||||
|
||||
(Having a separate name makes it easier to distinguish from
|
||||
Python's Type)
|
||||
"""
|
||||
__slots__ = ('name', 'application', )
|
||||
|
||||
name: str
|
||||
"""
|
||||
The name of the string, as parsed and outputted by codestyle.
|
||||
"""
|
||||
|
||||
application: TypeApplication_Base[Any, Any]
|
||||
"""
|
||||
How the type was constructed; i.e. which constructor was used and which
|
||||
type level arguments were applied to the constructor.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, application: TypeApplication_Base[Any, Any]) -> None:
|
||||
self.name = name
|
||||
self.application = application
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Type3({self.name!r}, {self.application!r})'
|
||||
|
||||
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:
|
||||
if not isinstance(other, Type3):
|
||||
raise NotImplementedError
|
||||
|
||||
return self is other
|
||||
|
||||
def __ne__(self, other: Any) -> bool:
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.name)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
class TypeApplication_Nullary(TypeApplication_Base[None, None]):
|
||||
"""
|
||||
There was no constructor used to create this type - it's a 'simple' type like u32
|
||||
"""
|
||||
|
||||
class IntType3(KindArgument):
|
||||
"""
|
||||
Sometimes you can have an int on the type level, e.g. when using static arrays
|
||||
|
||||
This is not the same as an int on the language level.
|
||||
[1.0, 1.2] :: f32[2] :: * -> Int -> *
|
||||
|
||||
That is to say, you can create a static array of size two with each element
|
||||
a f32 using f32[2].
|
||||
"""
|
||||
|
||||
__slots__ = ('value', )
|
||||
|
||||
value: int
|
||||
|
||||
def __init__(self, value: int) -> None:
|
||||
self.value = value
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'IntType3({self.value!r})'
|
||||
|
||||
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.value)
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if isinstance(other, IntType3):
|
||||
return self.value == other.value
|
||||
|
||||
if isinstance(other, KindArgument):
|
||||
return False
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.value)
|
||||
|
||||
class TypeConstructor_Base[T]:
|
||||
"""
|
||||
Base class for type construtors
|
||||
"""
|
||||
__slots__ = ('name', '_cache', )
|
||||
|
||||
name: str
|
||||
"""
|
||||
The name of the type constructor
|
||||
"""
|
||||
|
||||
_cache: dict[T, Type3]
|
||||
"""
|
||||
When constructing a type with the same arguments,
|
||||
it should produce the exact same result.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
self.name = name
|
||||
|
||||
self._cache = {}
|
||||
|
||||
def make_name(self, key: T) -> str:
|
||||
"""
|
||||
Renders the type's name based on the given arguments
|
||||
"""
|
||||
raise NotImplementedError('make_name', self)
|
||||
|
||||
def make_application(self, key: T) -> TypeApplication_Base[Self, T]:
|
||||
"""
|
||||
Records how the type was constructed into type.
|
||||
|
||||
The type checker and compiler will need to know what
|
||||
arguments where made to construct the type.
|
||||
"""
|
||||
raise NotImplementedError('make_application', self)
|
||||
|
||||
def construct(self, key: T) -> Type3:
|
||||
"""
|
||||
Constructs the type by applying the given arguments to this
|
||||
constructor.
|
||||
"""
|
||||
result = self._cache.get(key, None)
|
||||
if result is None:
|
||||
self._cache[key] = result = Type3(self.make_name(key), self.make_application(key))
|
||||
|
||||
return result
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'{self.__class__.__name__}({self.name!r}, ...)'
|
||||
|
||||
class TypeConstructor_Type(TypeConstructor_Base[Tuple[Type3]]):
|
||||
"""
|
||||
Base class type constructors of kind: * -> *
|
||||
|
||||
Notably, static array.
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def make_application(self, key: Tuple[Type3]) -> 'TypeApplication_Type':
|
||||
return TypeApplication_Type(self, key)
|
||||
|
||||
def make_name(self, key: Tuple[Type3]) -> str:
|
||||
return f'{self.name} {key[0].name} '
|
||||
|
||||
def __call__(self, arg0: Type3) -> Type3:
|
||||
return self.construct((arg0, ))
|
||||
|
||||
class TypeApplication_Type(TypeApplication_Base[TypeConstructor_Type, Tuple[Type3]]):
|
||||
pass
|
||||
|
||||
class TypeConstructor_TypeInt(TypeConstructor_Base[Tuple[Type3, IntType3]]):
|
||||
"""
|
||||
Base class type constructors of kind: * -> Int -> *
|
||||
|
||||
Notably, static array.
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def make_application(self, key: Tuple[Type3, IntType3]) -> 'TypeApplication_TypeInt':
|
||||
return TypeApplication_TypeInt(self, key)
|
||||
|
||||
def make_name(self, key: Tuple[Type3, IntType3]) -> str:
|
||||
return f'{self.name} {key[0].name} {key[1].value}'
|
||||
|
||||
def __call__(self, arg0: Type3, arg1: IntType3) -> Type3:
|
||||
return self.construct((arg0, arg1))
|
||||
|
||||
class TypeApplication_TypeInt(TypeApplication_Base[TypeConstructor_TypeInt, Tuple[Type3, IntType3]]):
|
||||
pass
|
||||
|
||||
class TypeConstructor_TypeStar(TypeConstructor_Base[Tuple[Type3, ...]]):
|
||||
"""
|
||||
Base class type constructors of variadic kind
|
||||
|
||||
Notably, tuple.
|
||||
"""
|
||||
def make_application(self, key: Tuple[Type3, ...]) -> 'TypeApplication_TypeStar':
|
||||
return TypeApplication_TypeStar(self, key)
|
||||
|
||||
def __call__(self, *args: Type3) -> Type3:
|
||||
key: Tuple[Type3, ...] = tuple(args)
|
||||
return self.construct(key)
|
||||
|
||||
class TypeApplication_TypeStar(TypeApplication_Base[TypeConstructor_TypeStar, Tuple[Type3, ...]]):
|
||||
pass
|
||||
|
||||
class TypeConstructor_DynamicArray(TypeConstructor_Type):
|
||||
def make_name(self, key: Tuple[Type3]) -> str:
|
||||
if 'u8' == key[0].name:
|
||||
return 'bytes'
|
||||
|
||||
return f'{key[0].name}[...]'
|
||||
|
||||
class TypeConstructor_StaticArray(TypeConstructor_TypeInt):
|
||||
def make_name(self, key: Tuple[Type3, IntType3]) -> str:
|
||||
return f'{key[0].name}[{key[1].value}]'
|
||||
|
||||
class TypeConstructor_Tuple(TypeConstructor_TypeStar):
|
||||
def make_name(self, key: Tuple[Type3, ...]) -> str:
|
||||
return '(' + ', '.join(x.name for x in key) + ', )'
|
||||
|
||||
class TypeConstructor_Function(TypeConstructor_TypeStar):
|
||||
def make_name(self, key: Tuple[Type3, ...]) -> str:
|
||||
return 'Callable[' + ', '.join(x.name for x in key) + ']'
|
||||
|
||||
class TypeConstructor_Struct(TypeConstructor_Base[tuple[tuple[str, Type3], ...]]):
|
||||
"""
|
||||
Constructs struct types
|
||||
"""
|
||||
def make_application(self, key: tuple[tuple[str, Type3], ...]) -> 'TypeApplication_Struct':
|
||||
return TypeApplication_Struct(self, key)
|
||||
|
||||
def make_name(self, key: tuple[tuple[str, Type3], ...]) -> str:
|
||||
return f'{self.name}(' + ', '.join(
|
||||
f'{n}: {t.name}'
|
||||
for n, t in key
|
||||
) + ')'
|
||||
|
||||
def construct(self, key: T) -> Type3:
|
||||
"""
|
||||
Constructs the type by applying the given arguments to this
|
||||
constructor.
|
||||
"""
|
||||
raise Exception('This does not work with the caching system')
|
||||
|
||||
def __call__(self, name: str, args: tuple[tuple[str, Type3], ...]) -> Type3:
|
||||
result = Type3(name, self.make_application(args))
|
||||
return result
|
||||
|
||||
class TypeApplication_Struct(TypeApplication_Base[TypeConstructor_Struct, tuple[tuple[str, Type3], ...]]):
|
||||
pass
|
||||
0
phasm/type5/__init__.py
Normal file
0
phasm/type5/__init__.py
Normal file
63
phasm/type5/constrainedexpr.py
Normal file
63
phasm/type5/constrainedexpr.py
Normal file
@ -0,0 +1,63 @@
|
||||
"""
|
||||
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
|
||||
638
phasm/type5/constraints.py
Normal file
638
phasm/type5/constraints.py
Normal file
@ -0,0 +1,638 @@
|
||||
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)
|
||||
290
phasm/type5/fromast.py
Normal file
290
phasm/type5/fromast.py
Normal file
@ -0,0 +1,290 @@
|
||||
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?
|
||||
57
phasm/type5/kindexpr.py
Normal file
57
phasm/type5/kindexpr.py
Normal file
@ -0,0 +1,57 @@
|
||||
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
|
||||
43
phasm/type5/record.py
Normal file
43
phasm/type5/record.py
Normal file
@ -0,0 +1,43 @@
|
||||
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
|
||||
122
phasm/type5/solver.py
Normal file
122
phasm/type5/solver.py
Normal file
@ -0,0 +1,122 @@
|
||||
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
|
||||
201
phasm/type5/typeexpr.py
Normal file
201
phasm/type5/typeexpr.py
Normal file
@ -0,0 +1,201 @@
|
||||
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)
|
||||
50
phasm/type5/typerouter.py
Normal file
50
phasm/type5/typerouter.py
Normal file
@ -0,0 +1,50 @@
|
||||
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)
|
||||
43
phasm/typeclass/__init__.py
Normal file
43
phasm/typeclass/__init__.py
Normal file
@ -0,0 +1,43 @@
|
||||
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
|
||||
]
|
||||
)
|
||||
@ -105,11 +105,17 @@ class Import(WatSerializable):
|
||||
else f' (result {self.result.to_wat()})'
|
||||
)
|
||||
|
||||
class Statement(WatSerializable):
|
||||
class StatementBase(WatSerializable):
|
||||
pass
|
||||
|
||||
class Statement(StatementBase ):
|
||||
"""
|
||||
Represents a Web Assembly statement
|
||||
"""
|
||||
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.args = args
|
||||
self.comment = comment
|
||||
@ -120,6 +126,16 @@ class Statement(WatSerializable):
|
||||
|
||||
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):
|
||||
"""
|
||||
Represents a Web Assembly function
|
||||
@ -131,7 +147,7 @@ class Function(WatSerializable):
|
||||
params: Iterable[Param],
|
||||
locals_: Iterable[Param],
|
||||
result: WasmType,
|
||||
statements: Iterable[Statement],
|
||||
statements: Iterable[StatementBase],
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.exported_name = exported_name
|
||||
|
||||
@ -68,6 +68,11 @@ class Generator_i32i64:
|
||||
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.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
|
||||
self.load = functools.partial(self.generator.add_statement, f'{prefix}.load')
|
||||
self.load8_u = functools.partial(self.generator.add_statement, f'{prefix}.load8_u')
|
||||
@ -81,6 +86,7 @@ class Generator_i32(Generator_i32i64):
|
||||
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):
|
||||
@ -90,6 +96,7 @@ class Generator_i64(Generator_i32i64):
|
||||
# 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:
|
||||
def __init__(self, prefix: str, generator: 'Generator') -> None:
|
||||
@ -143,6 +150,7 @@ class Generator_f32(Generator_f32f64):
|
||||
|
||||
# 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):
|
||||
def __init__(self, generator: 'Generator') -> None:
|
||||
@ -150,6 +158,8 @@ class Generator_f64(Generator_f32f64):
|
||||
|
||||
# 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:
|
||||
def __init__(self, generator: 'Generator') -> None:
|
||||
@ -186,16 +196,17 @@ class GeneratorBlock:
|
||||
|
||||
def __enter__(self) -> None:
|
||||
stmt = self.name
|
||||
args: list[str] = []
|
||||
if self.params:
|
||||
stmt = f'{stmt} ' + ' '.join(
|
||||
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()
|
||||
stmt = f'{stmt} (result {result})'
|
||||
args.append(f'(result {result})')
|
||||
|
||||
self.generator.add_statement(stmt, comment=self.comment)
|
||||
self.generator.add_statement(stmt, *args, comment=self.comment)
|
||||
|
||||
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
||||
if not exc_type:
|
||||
@ -203,7 +214,7 @@ class GeneratorBlock:
|
||||
|
||||
class Generator:
|
||||
def __init__(self) -> None:
|
||||
self.statements: List[wasm.Statement] = []
|
||||
self.statements: List[wasm.StatementBase] = []
|
||||
self.locals: Dict[str, VarType_Base] = {}
|
||||
|
||||
self.i32 = Generator_i32(self)
|
||||
@ -228,7 +239,7 @@ class Generator:
|
||||
# br_table
|
||||
self.return_ = functools.partial(self.add_statement, 'return')
|
||||
# call - see below
|
||||
# call_indirect
|
||||
# call_indirect - see below
|
||||
|
||||
def br_if(self, idx: int) -> None:
|
||||
self.add_statement('br_if', f'{idx}')
|
||||
@ -237,17 +248,19 @@ class Generator:
|
||||
if isinstance(function, wasm.Function):
|
||||
function = function.name
|
||||
|
||||
self.add_statement('call', f'${function}')
|
||||
self.statements.append(wasm.StatementCall(function))
|
||||
|
||||
def call_indirect(self, params: Iterable[Type[wasm.WasmType]], result: Type[wasm.WasmType]) -> None:
|
||||
def call_indirect(self, params: Iterable[Type[wasm.WasmType] | wasm.WasmType], result: Type[wasm.WasmType] | wasm.WasmType) -> None:
|
||||
param_str = ' '.join(
|
||||
x().to_wat()
|
||||
(x() if isinstance(x, type) else x).to_wat()
|
||||
for x in params
|
||||
)
|
||||
|
||||
result_str = result().to_wat()
|
||||
if isinstance(result, type):
|
||||
result = result()
|
||||
result_str = result.to_wat()
|
||||
|
||||
self.add_statement(f'call_indirect (param {param_str}) (result {result_str})')
|
||||
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:
|
||||
self.statements.append(wasm.Statement(name, *args, comment=comment))
|
||||
@ -287,7 +300,7 @@ class Generator:
|
||||
def temp_var_u8(self, infix: str) -> VarType_u8:
|
||||
return self.temp_var(VarType_u8(infix))
|
||||
|
||||
def func_wrapper(exported: bool = True) -> Callable[[Any], wasm.Function]:
|
||||
def func_wrapper(exported: bool = False) -> Callable[[Any], wasm.Function]:
|
||||
"""
|
||||
This wrapper will execute the function and return
|
||||
a wasm Function with the generated Statements
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
marko==2.1.3
|
||||
mypy==1.15.0
|
||||
mypy==1.17.1
|
||||
pygments==2.19.1
|
||||
pytest==8.3.5
|
||||
pytest-integration==0.2.2
|
||||
ruff==0.11.4
|
||||
ruff==0.12.7
|
||||
|
||||
|
||||
wasmtime==31.0.0
|
||||
|
||||
@ -1,21 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
from typing import Any, Callable, Generator, Iterable, List, TextIO, Union
|
||||
from typing import Any, Callable, List, TextIO, Union
|
||||
|
||||
from phasm import compiler, prelude
|
||||
from phasm.codestyle import phasm_render
|
||||
from phasm.runtime import (
|
||||
calculate_alloc_size,
|
||||
calculate_alloc_size_static_array,
|
||||
calculate_alloc_size_struct,
|
||||
calculate_alloc_size_tuple,
|
||||
from phasm.wasm import (
|
||||
WasmTypeFloat32,
|
||||
WasmTypeFloat64,
|
||||
WasmTypeInt32,
|
||||
WasmTypeInt64,
|
||||
)
|
||||
from phasm.stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP
|
||||
from phasm.type3 import types as type3types
|
||||
from phasm.type3.routers import NoRouteForTypeException, TypeApplicationRouter
|
||||
|
||||
from . import runners
|
||||
from . import memory, runners
|
||||
|
||||
DASHES = '-' * 16
|
||||
|
||||
@ -100,6 +97,7 @@ class Suite:
|
||||
|
||||
runner.parse(verbose=verbose)
|
||||
runner.compile_ast()
|
||||
runner.optimise_wasm_ast()
|
||||
runner.compile_wat()
|
||||
|
||||
if verbose:
|
||||
@ -109,11 +107,17 @@ class Suite:
|
||||
runner.interpreter_setup()
|
||||
runner.interpreter_load(imports)
|
||||
|
||||
allocator_generator = memory.Allocator(runner.phasm_ast.build, runner)
|
||||
|
||||
# Check if code formatting works
|
||||
if do_format_check:
|
||||
assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests
|
||||
|
||||
func_args = [x.type3 for x in runner.phasm_ast.functions[func_name].posonlyargs]
|
||||
func = runner.phasm_ast.functions[func_name]
|
||||
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}')
|
||||
|
||||
@ -124,26 +128,21 @@ class Suite:
|
||||
runner.interpreter_dump_memory(sys.stderr)
|
||||
|
||||
for arg, arg_typ in zip(args, func_args, strict=True):
|
||||
if arg_typ in (prelude.u8, prelude.u32, prelude.u64, ):
|
||||
arg_typ_info = runner.phasm_ast.build.type_info_map.get(arg_typ.name)
|
||||
|
||||
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)
|
||||
continue
|
||||
|
||||
if arg_typ in (prelude.i8, prelude.i32, prelude.i64, ):
|
||||
assert isinstance(arg, int)
|
||||
wasm_args.append(arg)
|
||||
continue
|
||||
|
||||
if arg_typ in (prelude.f32, prelude.f64, ):
|
||||
if arg_typ_info and (arg_typ_info.wasm_type is WasmTypeFloat32 or arg_typ_info.wasm_type is WasmTypeFloat64):
|
||||
assert isinstance(arg, float)
|
||||
wasm_args.append(arg)
|
||||
continue
|
||||
|
||||
try:
|
||||
adr = ALLOCATE_MEMORY_STORED_ROUTER((runner, arg), arg_typ)
|
||||
wasm_args.append(adr)
|
||||
except NoRouteForTypeException:
|
||||
raise NotImplementedError(arg_typ, arg)
|
||||
allocator = allocator_generator(arg_typ)
|
||||
adr = allocator(arg)
|
||||
wasm_args.append(adr)
|
||||
|
||||
if verbose:
|
||||
write_header(sys.stderr, 'Memory (pre run)')
|
||||
@ -152,11 +151,8 @@ class Suite:
|
||||
result = SuiteResult()
|
||||
result.returned_value = runner.call(func_name, *wasm_args)
|
||||
|
||||
result.returned_value = _load_memory_stored_returned_value(
|
||||
runner,
|
||||
func_name,
|
||||
result.returned_value,
|
||||
)
|
||||
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)')
|
||||
@ -166,306 +162,3 @@ class Suite:
|
||||
|
||||
def write_header(textio: TextIO, msg: str) -> None:
|
||||
textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n')
|
||||
|
||||
WRITE_LOOKUP_MAP = {
|
||||
'u8': compiler.module_data_u8,
|
||||
'u32': compiler.module_data_u32,
|
||||
'u64': compiler.module_data_u64,
|
||||
'i8': compiler.module_data_i8,
|
||||
'i32': compiler.module_data_i32,
|
||||
'i64': compiler.module_data_i64,
|
||||
'f32': compiler.module_data_f32,
|
||||
'f64': compiler.module_data_f64,
|
||||
}
|
||||
|
||||
def _write_memory_stored_value(
|
||||
runner: runners.RunnerBase,
|
||||
adr: int,
|
||||
val_typ: type3types.Type3,
|
||||
val: Any,
|
||||
) -> int:
|
||||
try:
|
||||
adr2 = ALLOCATE_MEMORY_STORED_ROUTER((runner, val), val_typ)
|
||||
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
|
||||
return TYPE_INFO_CONSTRUCTED.alloc_size
|
||||
except NoRouteForTypeException:
|
||||
to_write = WRITE_LOOKUP_MAP[val_typ.name](val)
|
||||
runner.interpreter_write_memory(adr, to_write)
|
||||
return len(to_write)
|
||||
|
||||
def _allocate_memory_stored_bytes(attrs: tuple[runners.RunnerBase, bytes]) -> int:
|
||||
runner, val = attrs
|
||||
|
||||
assert isinstance(val, bytes)
|
||||
|
||||
adr = runner.call('stdlib.types.__alloc_bytes__', len(val))
|
||||
assert isinstance(adr, int)
|
||||
|
||||
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
|
||||
runner.interpreter_write_memory(adr + 4, val)
|
||||
return adr
|
||||
|
||||
def _allocate_memory_stored_dynamic_array(attrs: tuple[runners.RunnerBase, Any], da_args: tuple[type3types.Type3]) -> int:
|
||||
runner, val = attrs
|
||||
|
||||
da_type, = da_args
|
||||
|
||||
if not isinstance(val, tuple):
|
||||
raise InvalidArgumentException(f'Expected tuple; got {val!r} instead')
|
||||
|
||||
alloc_size = 4 + len(val) * calculate_alloc_size(da_type, True)
|
||||
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
|
||||
assert isinstance(adr, int) # Type int
|
||||
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
|
||||
|
||||
offset = adr
|
||||
offset += _write_memory_stored_value(runner, offset, prelude.u32, len(val))
|
||||
for val_el_val in val:
|
||||
offset += _write_memory_stored_value(runner, offset, da_type, val_el_val)
|
||||
return adr
|
||||
|
||||
def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> int:
|
||||
runner, val = attrs
|
||||
|
||||
sa_type, sa_len = sa_args
|
||||
|
||||
if not isinstance(val, tuple):
|
||||
raise InvalidArgumentException(f'Expected tuple of length {sa_len.value}; got {val!r} instead')
|
||||
if sa_len.value != len(val):
|
||||
raise InvalidArgumentException(f'Expected tuple of length {sa_len.value}; got {val!r} instead')
|
||||
|
||||
alloc_size = calculate_alloc_size_static_array(False, sa_args)
|
||||
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
|
||||
assert isinstance(adr, int) # Type int
|
||||
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
|
||||
|
||||
offset = adr
|
||||
for val_el_val in val:
|
||||
offset += _write_memory_stored_value(runner, offset, sa_type, val_el_val)
|
||||
return adr
|
||||
|
||||
def _allocate_memory_stored_struct(attrs: tuple[runners.RunnerBase, Any], st_args: tuple[tuple[str, type3types.Type3], ...]) -> int:
|
||||
runner, val = attrs
|
||||
|
||||
assert isinstance(val, dict)
|
||||
|
||||
alloc_size = calculate_alloc_size_struct(False, st_args)
|
||||
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
|
||||
assert isinstance(adr, int)
|
||||
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
|
||||
|
||||
offset = adr
|
||||
for val_el_name, val_el_typ in st_args:
|
||||
assert val_el_name in val, f'Missing key value {val_el_name}'
|
||||
val_el_val = val.pop(val_el_name)
|
||||
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
|
||||
|
||||
assert not val, f'Additional values: {list(val)!r}'
|
||||
|
||||
return adr
|
||||
|
||||
def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args: tuple[type3types.Type3, ...]) -> int:
|
||||
runner, val = attrs
|
||||
|
||||
assert isinstance(val, tuple)
|
||||
|
||||
alloc_size = calculate_alloc_size_tuple(False, tp_args)
|
||||
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
|
||||
assert isinstance(adr, int)
|
||||
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
|
||||
|
||||
assert len(val) == len(tp_args)
|
||||
|
||||
offset = adr
|
||||
for val_el_val, val_el_typ in zip(val, tp_args, strict=True):
|
||||
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
|
||||
return adr
|
||||
|
||||
ALLOCATE_MEMORY_STORED_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, Any], Any]()
|
||||
ALLOCATE_MEMORY_STORED_ROUTER.add_n(prelude.bytes_, _allocate_memory_stored_bytes)
|
||||
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.dynamic_array, _allocate_memory_stored_dynamic_array)
|
||||
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.static_array, _allocate_memory_stored_static_array)
|
||||
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.struct, _allocate_memory_stored_struct)
|
||||
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.tuple_, _allocate_memory_stored_tuple)
|
||||
|
||||
def _load_memory_stored_returned_value(
|
||||
runner: runners.RunnerBase,
|
||||
func_name: str,
|
||||
wasm_value: Any,
|
||||
) -> Any:
|
||||
ret_type3 = runner.phasm_ast.functions[func_name].returns_type3
|
||||
|
||||
if ret_type3 is prelude.none:
|
||||
return None
|
||||
|
||||
if ret_type3 is prelude.bool_:
|
||||
assert isinstance(wasm_value, int), wasm_value
|
||||
return 0 != wasm_value
|
||||
|
||||
if ret_type3 in (prelude.i8, prelude.i32, prelude.i64):
|
||||
assert isinstance(wasm_value, int), wasm_value
|
||||
|
||||
if ret_type3 is prelude.i8:
|
||||
# Values are actually i32
|
||||
# Have to reinterpret to load proper value
|
||||
data = struct.pack('<i', wasm_value)
|
||||
wasm_value, = struct.unpack('<bxxx', data)
|
||||
|
||||
return wasm_value
|
||||
|
||||
if ret_type3 in (prelude.u8, prelude.u32, prelude.u64):
|
||||
assert isinstance(wasm_value, int), wasm_value
|
||||
|
||||
if wasm_value < 0:
|
||||
# WASM does not support unsigned values through its interface
|
||||
# Cast and then reinterpret
|
||||
|
||||
letter = {
|
||||
'u32': 'i',
|
||||
'u64': 'q',
|
||||
}[ret_type3.name]
|
||||
|
||||
data = struct.pack(f'<{letter}', wasm_value)
|
||||
wasm_value, = struct.unpack(f'<{letter.upper()}', data)
|
||||
|
||||
return wasm_value
|
||||
|
||||
if ret_type3 in (prelude.f32, prelude.f64, ):
|
||||
assert isinstance(wasm_value, float), wasm_value
|
||||
return wasm_value
|
||||
|
||||
assert isinstance(wasm_value, int), wasm_value
|
||||
|
||||
return LOAD_FROM_ADDRESS_ROUTER((runner, wasm_value), ret_type3)
|
||||
|
||||
def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any:
|
||||
typ_info = TYPE_INFO_MAP.get(typ.name, TYPE_INFO_CONSTRUCTED)
|
||||
|
||||
assert len(inp) == typ_info.alloc_size
|
||||
|
||||
if typ is prelude.u8:
|
||||
return struct.unpack('<B', inp)[0]
|
||||
|
||||
if typ is prelude.u32:
|
||||
return struct.unpack('<I', inp)[0]
|
||||
|
||||
if typ is prelude.u64:
|
||||
return struct.unpack('<Q', inp)[0]
|
||||
|
||||
if typ is prelude.i8:
|
||||
return struct.unpack('<b', inp)[0]
|
||||
|
||||
if typ is prelude.i32:
|
||||
return struct.unpack('<i', inp)[0]
|
||||
|
||||
if typ is prelude.i64:
|
||||
return struct.unpack('<q', inp)[0]
|
||||
|
||||
if typ is prelude.f32:
|
||||
return struct.unpack('<f', inp)[0]
|
||||
|
||||
if typ is prelude.f64:
|
||||
return struct.unpack('<d', inp)[0]
|
||||
|
||||
if typ_info is TYPE_INFO_CONSTRUCTED:
|
||||
# Note: For applied types, inp should contain a 4 byte pointer
|
||||
adr = struct.unpack('<I', inp)[0]
|
||||
|
||||
return LOAD_FROM_ADDRESS_ROUTER((runner, adr), typ)
|
||||
|
||||
raise NotImplementedError(typ, inp)
|
||||
|
||||
def _load_bytes_from_address(attrs: tuple[runners.RunnerBase, int]) -> bytes:
|
||||
runner, adr = attrs
|
||||
|
||||
sys.stderr.write(f'Reading 0x{adr:08x} bytes\n')
|
||||
read_bytes = runner.interpreter_read_memory(adr, 4)
|
||||
bytes_len, = struct.unpack('<I', read_bytes)
|
||||
|
||||
adr += 4
|
||||
return runner.interpreter_read_memory(adr, bytes_len)
|
||||
|
||||
def _split_read_bytes(all_bytes: bytes, split_sizes: Iterable[int]) -> Generator[bytes, None, None]:
|
||||
offset = 0
|
||||
for size in split_sizes:
|
||||
yield all_bytes[offset:offset + size]
|
||||
offset += size
|
||||
|
||||
def _load_dynamic_array_from_address(attrs: tuple[runners.RunnerBase, int], da_args: tuple[type3types.Type3]) -> Any:
|
||||
runner, adr = attrs
|
||||
da_type, = da_args
|
||||
|
||||
sys.stderr.write(f'Reading 0x{adr:08x} {da_type:s}[...]\n')
|
||||
|
||||
read_bytes = runner.interpreter_read_memory(adr, 4)
|
||||
array_len, = struct.unpack('<I', read_bytes)
|
||||
adr += 4
|
||||
|
||||
arg_size_1 = calculate_alloc_size(da_type, is_member=True)
|
||||
arg_sizes = [arg_size_1 for _ in range(array_len)] # _split_read_bytes requires one arg per value
|
||||
|
||||
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
|
||||
|
||||
return tuple(
|
||||
_unpack(runner, da_type, arg_bytes)
|
||||
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes)
|
||||
)
|
||||
|
||||
def _load_static_array_from_address(attrs: tuple[runners.RunnerBase, int], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> Any:
|
||||
runner, adr = attrs
|
||||
sub_typ, len_typ = sa_args
|
||||
|
||||
sys.stderr.write(f'Reading 0x{adr:08x} {sub_typ:s} {len_typ:s}\n')
|
||||
|
||||
sa_len = len_typ.value
|
||||
|
||||
arg_size_1 = calculate_alloc_size(sub_typ, is_member=True)
|
||||
arg_sizes = [arg_size_1 for _ in range(sa_len)] # _split_read_bytes requires one arg per value
|
||||
|
||||
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
|
||||
|
||||
return tuple(
|
||||
_unpack(runner, sub_typ, arg_bytes)
|
||||
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes)
|
||||
)
|
||||
|
||||
def _load_struct_from_address(attrs: tuple[runners.RunnerBase, int], st_args: tuple[tuple[str, type3types.Type3], ...]) -> dict[str, Any]:
|
||||
runner, adr = attrs
|
||||
|
||||
sys.stderr.write(f'Reading 0x{adr:08x} struct {list(st_args)}\n')
|
||||
|
||||
arg_sizes = [
|
||||
calculate_alloc_size(x, is_member=True)
|
||||
for _, x in st_args
|
||||
]
|
||||
|
||||
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
|
||||
|
||||
return {
|
||||
arg_name: _unpack(runner, arg_typ, arg_bytes)
|
||||
for (arg_name, arg_typ, ), arg_bytes in zip(st_args, _split_read_bytes(read_bytes, arg_sizes), strict=True)
|
||||
}
|
||||
|
||||
def _load_tuple_from_address(attrs: tuple[runners.RunnerBase, int], tp_args: tuple[type3types.Type3, ...]) -> Any:
|
||||
runner, adr = attrs
|
||||
|
||||
sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(tp_args)}\n')
|
||||
|
||||
arg_sizes = [
|
||||
calculate_alloc_size(x, is_member=True)
|
||||
for x in tp_args
|
||||
]
|
||||
|
||||
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
|
||||
|
||||
return tuple(
|
||||
_unpack(runner, arg_typ, arg_bytes)
|
||||
for arg_typ, arg_bytes in zip(tp_args, _split_read_bytes(read_bytes, arg_sizes), strict=True)
|
||||
)
|
||||
|
||||
LOAD_FROM_ADDRESS_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, int], Any]()
|
||||
LOAD_FROM_ADDRESS_ROUTER.add_n(prelude.bytes_, _load_bytes_from_address)
|
||||
LOAD_FROM_ADDRESS_ROUTER.add(prelude.dynamic_array, _load_dynamic_array_from_address)
|
||||
LOAD_FROM_ADDRESS_ROUTER.add(prelude.static_array, _load_static_array_from_address)
|
||||
LOAD_FROM_ADDRESS_ROUTER.add(prelude.struct, _load_struct_from_address)
|
||||
LOAD_FROM_ADDRESS_ROUTER.add(prelude.tuple_, _load_tuple_from_address)
|
||||
|
||||
551
tests/integration/memory.py
Normal file
551
tests/integration/memory.py
Normal file
@ -0,0 +1,551 @@
|
||||
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
|
||||
@ -8,8 +8,10 @@ import wasmtime
|
||||
|
||||
from phasm import ourlang, wasm
|
||||
from phasm.compiler import phasm_compile
|
||||
from phasm.optimise.removeunusedfuncs import removeunusedfuncs
|
||||
from phasm.parser import phasm_parse
|
||||
from phasm.type3.entry import phasm_type3
|
||||
from phasm.type5.solver import phasm_type5
|
||||
from phasm.wasmgenerator import Generator as WasmGenerator
|
||||
|
||||
Imports = Optional[Dict[str, Callable[[Any], Any]]]
|
||||
|
||||
@ -18,7 +20,7 @@ class RunnerBase:
|
||||
Base class
|
||||
"""
|
||||
phasm_code: str
|
||||
phasm_ast: ourlang.Module
|
||||
phasm_ast: ourlang.Module[WasmGenerator]
|
||||
wasm_ast: wasm.Module
|
||||
wasm_asm: str
|
||||
wasm_bin: bytes
|
||||
@ -37,7 +39,7 @@ class RunnerBase:
|
||||
Parses the Phasm code into an AST
|
||||
"""
|
||||
self.phasm_ast = phasm_parse(self.phasm_code)
|
||||
phasm_type3(self.phasm_ast, verbose=verbose)
|
||||
phasm_type5(self.phasm_ast, verbose=verbose)
|
||||
|
||||
def compile_ast(self) -> None:
|
||||
"""
|
||||
@ -45,6 +47,12 @@ class RunnerBase:
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
Compiles the WebAssembly AST into WebAssembly Assembly code
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
|
||||
@pytest.mark.slow_integration_test
|
||||
def test_index():
|
||||
with open('examples/buffer.py', 'r', encoding='ASCII') as fil:
|
||||
code_py = "\n" + fil.read()
|
||||
|
||||
result = Suite(code_py).run_code(b'Hello, world!', 5, func_name='index')
|
||||
assert 44 == result.returned_value
|
||||
|
||||
@pytest.mark.slow_integration_test
|
||||
def test_length():
|
||||
with open('examples/buffer.py', 'r', encoding='ASCII') as fil:
|
||||
code_py = "\n" + fil.read()
|
||||
|
||||
result = Suite(code_py).run_code(b'Hello, world!', func_name='length')
|
||||
assert 13 == result.returned_value
|
||||
@ -8,6 +8,6 @@ def test_fib():
|
||||
with open('./examples/fib.py', 'r', encoding='UTF-8') as fil:
|
||||
code_py = "\n" + fil.read()
|
||||
|
||||
result = Suite(code_py).run_code()
|
||||
result = Suite(code_py).run_code(40, func_name='fib')
|
||||
|
||||
assert 102334155 == result.returned_value
|
||||
|
||||
@ -61,15 +61,15 @@ 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',
|
||||
'The given literal must fit the expected type',
|
||||
)
|
||||
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(
|
||||
'Must be tuple',
|
||||
'The given literal must fit the expected type',
|
||||
)
|
||||
expect_type_error('Not the same type')
|
||||
```
|
||||
|
||||
# function_result_is_literal_ok
|
||||
@ -114,20 +114,17 @@ def testEntry() -> i32:
|
||||
|
||||
```py
|
||||
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'):
|
||||
expect_type_error(
|
||||
'Mismatch between applied types argument count',
|
||||
'The type of a tuple is a combination of its members',
|
||||
)
|
||||
expect_type_error('Tuple element count mismatch')
|
||||
elif TYPE_NAME.startswith('struct_'):
|
||||
expect_type_error(
|
||||
TYPE + ' must be (u32, ) instead',
|
||||
'The type of the value returned from function constant should match its return type',
|
||||
)
|
||||
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(
|
||||
'Must be tuple',
|
||||
'The given literal must fit the expected type',
|
||||
)
|
||||
expect_type_error('Not the same type')
|
||||
```
|
||||
|
||||
# function_result_is_module_constant_ok
|
||||
@ -175,16 +172,7 @@ def testEntry() -> i32:
|
||||
```
|
||||
|
||||
```py
|
||||
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_') or TYPE_NAME.startswith('struct_'):
|
||||
expect_type_error(
|
||||
TYPE + ' must be (u32, ) instead',
|
||||
'The type of the value returned from function constant should match its return type',
|
||||
)
|
||||
else:
|
||||
expect_type_error(
|
||||
TYPE_NAME + ' must be (u32, ) instead',
|
||||
'The type of the value returned from function constant should match its return type',
|
||||
)
|
||||
expect_type_error('Not the same type')
|
||||
```
|
||||
|
||||
# function_result_is_arg_ok
|
||||
@ -226,16 +214,7 @@ def select(x: $TYPE) -> (u32, ):
|
||||
```
|
||||
|
||||
```py
|
||||
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_') or TYPE_NAME.startswith('struct_'):
|
||||
expect_type_error(
|
||||
TYPE + ' must be (u32, ) instead',
|
||||
'The type of the value returned from function select should match its return type',
|
||||
)
|
||||
else:
|
||||
expect_type_error(
|
||||
TYPE_NAME + ' must be (u32, ) instead',
|
||||
'The type of the value returned from function select should match its return type',
|
||||
)
|
||||
expect_type_error('Not the same type')
|
||||
```
|
||||
|
||||
# function_arg_literal_ok
|
||||
@ -274,21 +253,17 @@ def testEntry() -> i32:
|
||||
|
||||
```py
|
||||
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'):
|
||||
expect_type_error(
|
||||
'Mismatch between applied types argument count',
|
||||
# FIXME: Shouldn't this be the same as for the else statement?
|
||||
'The type of a tuple is a combination of its members',
|
||||
)
|
||||
expect_type_error('Tuple element count mismatch')
|
||||
elif TYPE_NAME.startswith('struct_'):
|
||||
expect_type_error(
|
||||
TYPE + ' must be (u32, ) instead',
|
||||
'The type of the value passed to argument 0 of function helper should match the type of that argument',
|
||||
)
|
||||
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(
|
||||
'Must be tuple',
|
||||
'The given literal must fit the expected type',
|
||||
)
|
||||
expect_type_error('Not the same type')
|
||||
```
|
||||
|
||||
# function_arg_module_constant_def_ok
|
||||
@ -330,14 +305,5 @@ def testEntry() -> i32:
|
||||
```
|
||||
|
||||
```py
|
||||
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_') or TYPE_NAME.startswith('struct_'):
|
||||
expect_type_error(
|
||||
TYPE + ' must be (u32, ) instead',
|
||||
'The type of the value passed to argument 0 of function helper should match the type of that argument',
|
||||
)
|
||||
else:
|
||||
expect_type_error(
|
||||
TYPE_NAME + ' must be (u32, ) instead',
|
||||
'The type of the value passed to argument 0 of function helper should match the type of that argument',
|
||||
)
|
||||
expect_type_error('Not the same type')
|
||||
```
|
||||
|
||||
@ -41,11 +41,9 @@ def generate_assertion_expect(result, arg, given=None):
|
||||
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, error_comment = None):
|
||||
result.append('with pytest.raises(Type3Exception) as exc_info:')
|
||||
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()')
|
||||
result.append(f'assert {repr(error_msg)} == exc_info.value.args[0][0].msg')
|
||||
result.append(f'assert {repr(error_comment)} == exc_info.value.args[0][0].comment')
|
||||
|
||||
def json_does_not_support_byte_or_tuple_values_fix(inp: Any):
|
||||
if isinstance(inp, (int, float, )):
|
||||
@ -98,7 +96,7 @@ def generate_code(markdown, template, settings):
|
||||
print('"""')
|
||||
print('import pytest')
|
||||
print()
|
||||
print('from phasm.type3.entry import Type3Exception')
|
||||
print('from phasm.type5.solver import Type5SolverException')
|
||||
print()
|
||||
print('from ..helpers import Suite')
|
||||
print()
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
{
|
||||
"TYPE_NAME": "struct_all_primitives",
|
||||
"TYPE": "StructallPrimitives",
|
||||
"VAL0": "StructallPrimitives(1, 4, 8, 1, -1, 4, -4, 8, -8, 125.125, -125.125, 5000.5, -5000.5, b'Hello, world!')",
|
||||
"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",
|
||||
@ -22,10 +25,13 @@
|
||||
"PYTHON": {
|
||||
"VAL0": {
|
||||
"val00": 1,
|
||||
"val03": 2,
|
||||
"val01": 4,
|
||||
"val02": 8,
|
||||
"val10": 1,
|
||||
"val11": -1,
|
||||
"val16": 2,
|
||||
"val17": -2,
|
||||
"val12": 4,
|
||||
"val13": -4,
|
||||
"val14": 8,
|
||||
|
||||
@ -15,7 +15,8 @@ def testEntry() -> bytes:
|
||||
|
||||
result = Suite(code_py).run_code()
|
||||
|
||||
assert b"Hello" == result.returned_value
|
||||
assert b"Hello" == result.returned_value[0:5]
|
||||
assert 5 == len(result.returned_value)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_bytes_export_instantiation():
|
||||
|
||||
@ -1,8 +1,25 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_call_nullary():
|
||||
code_py = """
|
||||
def helper() -> i32:
|
||||
return 3
|
||||
|
||||
@exported
|
||||
def testEntry() -> i32:
|
||||
return helper()
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code()
|
||||
|
||||
assert 3 == result.returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_call_pre_defined():
|
||||
code_py = """
|
||||
@ -23,12 +40,22 @@ def test_call_post_defined():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry() -> i32:
|
||||
return helper(10, 3)
|
||||
return helper(13)
|
||||
|
||||
def helper(left: i32, right: i32) -> i32:
|
||||
return left - right
|
||||
def helper(left: i32) -> i32:
|
||||
return left
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code()
|
||||
|
||||
assert 7 == result.returned_value
|
||||
assert 13 == result.returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_call_invalid_type():
|
||||
code_py = """
|
||||
def helper(left: i32) -> i32:
|
||||
return left()
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match=r'i32 ~ Callable\[i32\]'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
|
||||
@ -70,3 +72,30 @@ def testEntry(a: i32, b: i32) -> i32:
|
||||
assert 2 == suite.run_code(20, 10).returned_value
|
||||
assert 1 == suite.run_code(10, 20).returned_value
|
||||
assert 0 == suite.run_code(10, 10).returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_if_type_invalid():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(a: i32) -> i32:
|
||||
if a:
|
||||
return 1
|
||||
|
||||
return 0
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match='i32 ~ bool'):
|
||||
Suite(code_py).run_code(1)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_if_type_ok():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(a: bool) -> i32:
|
||||
if a:
|
||||
return 1
|
||||
|
||||
return 0
|
||||
"""
|
||||
|
||||
assert 1 == Suite(code_py).run_code(1).returned_value
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type3.entry import Type3Exception
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
@ -69,7 +69,7 @@ def testEntry(x: u32) -> u8:
|
||||
def helper(mul: int) -> int:
|
||||
return 4238 * mul
|
||||
|
||||
with pytest.raises(Type3Exception, match=r'u32 must be u8 instead'):
|
||||
with pytest.raises(Type5SolverException, match='Not the same type'):
|
||||
Suite(code_py).run_code(
|
||||
imports={
|
||||
'helper': helper,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type3.entry import Type3Exception
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
@ -15,7 +15,7 @@ def testEntry() -> u8:
|
||||
return CONSTANT
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
|
||||
with pytest.raises(Type5SolverException, match=r'Must fit in 1 byte\(s\)'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -26,5 +26,5 @@ def testEntry() -> u8:
|
||||
return 1000
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
|
||||
with pytest.raises(Type5SolverException, match=r'Must fit in 1 byte\(s\)'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type3.entry import Type3Exception
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
@ -78,11 +78,28 @@ def testEntry() -> i32:
|
||||
assert 42 == result.returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_sof_wrong_argument_type():
|
||||
def test_sof_function_with_wrong_argument_type_use():
|
||||
code_py = """
|
||||
def double(left: f32) -> f32:
|
||||
def double(left: i32) -> i32:
|
||||
return left * 2
|
||||
|
||||
def action(applicable: Callable[i32, i32], left: f32) -> i32:
|
||||
return applicable(left)
|
||||
|
||||
@exported
|
||||
def testEntry() -> i32:
|
||||
return action(double, 13.0)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match='i32 ~ f32'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_sof_function_with_wrong_argument_type_pass():
|
||||
code_py = """
|
||||
def double(left: f32) -> i32:
|
||||
return truncate(left) * 2
|
||||
|
||||
def action(applicable: Callable[i32, i32], left: i32) -> i32:
|
||||
return applicable(left)
|
||||
|
||||
@ -91,11 +108,11 @@ def testEntry() -> i32:
|
||||
return action(double, 13)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match=r'Callable\[f32, f32\] must be Callable\[i32, i32\] instead'):
|
||||
with pytest.raises(Type5SolverException, match='i32 ~ f32'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_sof_wrong_return():
|
||||
def test_sof_function_with_wrong_return_type_use():
|
||||
code_py = """
|
||||
def double(left: i32) -> i32:
|
||||
return left * 2
|
||||
@ -103,17 +120,33 @@ def double(left: i32) -> i32:
|
||||
def action(applicable: Callable[i32, i32], left: i32) -> f32:
|
||||
return applicable(left)
|
||||
|
||||
@exported
|
||||
def testEntry() -> f32:
|
||||
return action(double, 13)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match='i32 ~ f32'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_sof_function_with_wrong_return_type_pass():
|
||||
code_py = """
|
||||
def double(left: i32) -> f32:
|
||||
return convert(left) * 2.0
|
||||
|
||||
def action(applicable: Callable[i32, i32], left: i32) -> i32:
|
||||
return applicable(left)
|
||||
|
||||
@exported
|
||||
def testEntry() -> i32:
|
||||
return action(double, 13)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match=r'f32 must be i32 instead'):
|
||||
with pytest.raises(Type5SolverException, match='i32 ~ f32'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.skip('FIXME: Probably have the remainder be the a function type')
|
||||
def test_sof_wrong_not_enough_args_call():
|
||||
def test_sof_not_enough_args_use():
|
||||
code_py = """
|
||||
def add(left: i32, right: i32) -> i32:
|
||||
return left + right
|
||||
@ -126,11 +159,11 @@ def testEntry() -> i32:
|
||||
return action(add, 13)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match=r'f32 must be i32 instead'):
|
||||
with pytest.raises(Type5SolverException, match='Not the same type'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_sof_wrong_not_enough_args_refere():
|
||||
def test_sof_not_enough_args_pass():
|
||||
code_py = """
|
||||
def double(left: i32) -> i32:
|
||||
return left * 2
|
||||
@ -143,12 +176,12 @@ def testEntry() -> i32:
|
||||
return action(double, 13, 14)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match=r'Callable\[i32, i32\] must be Callable\[i32, i32, i32\] instead'):
|
||||
match = r'Callable\[i32, i32\] ~ i32'
|
||||
with pytest.raises(Type5SolverException, match=match):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.skip('FIXME: Probably have the remainder be the a function type')
|
||||
def test_sof_wrong_too_many_args_call():
|
||||
def test_sof_too_many_args_use_0():
|
||||
code_py = """
|
||||
def thirteen() -> i32:
|
||||
return 13
|
||||
@ -161,22 +194,60 @@ def testEntry() -> i32:
|
||||
return action(thirteen, 13)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match=r'f32 must be i32 instead'):
|
||||
Suite(code_py).run_code()
|
||||
match = r'\(\) ~ i32'
|
||||
with pytest.raises(Type5SolverException, match=match):
|
||||
Suite(code_py).run_code(verbose=True)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_sof_wrong_too_many_args_refere():
|
||||
def test_sof_too_many_args_use_1():
|
||||
code_py = """
|
||||
def thirteen(x: i32) -> i32:
|
||||
return x
|
||||
|
||||
def action(applicable: Callable[i32, i32], left: i32, right: i32) -> i32:
|
||||
return applicable(left, right)
|
||||
|
||||
@exported
|
||||
def testEntry() -> i32:
|
||||
return action(thirteen, 13, 26)
|
||||
"""
|
||||
|
||||
match = r'i32 ~ Callable\[i32, i32\]'
|
||||
with pytest.raises(Type5SolverException, match=match):
|
||||
Suite(code_py).run_code(verbose=True)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_sof_too_many_args_pass_0():
|
||||
code_py = """
|
||||
def double(left: i32) -> i32:
|
||||
return left * 2
|
||||
|
||||
def action(applicable: Callable[i32]) -> i32:
|
||||
def action(applicable: Callable[i32], left: i32, right: i32) -> i32:
|
||||
return applicable()
|
||||
|
||||
@exported
|
||||
def testEntry() -> i32:
|
||||
return action(double)
|
||||
return action(double, 13, 14)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match=r'Callable\[i32, i32\] must be Callable\[i32\] instead'):
|
||||
match = r'\(\) ~ i32'
|
||||
with pytest.raises(Type5SolverException, match=match):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_sof_too_many_args_pass_1():
|
||||
code_py = """
|
||||
def double(left: i32, right: i32) -> i32:
|
||||
return left * right
|
||||
|
||||
def action(applicable: Callable[i32, i32], left: i32, right: i32) -> i32:
|
||||
return applicable(left)
|
||||
|
||||
@exported
|
||||
def testEntry() -> i32:
|
||||
return action(double, 13, 14)
|
||||
"""
|
||||
|
||||
match = r'i32 ~ Callable\[i32, i32\]'
|
||||
with pytest.raises(Type5SolverException, match=match):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type3.entry import Type3Exception
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
@ -15,7 +15,7 @@ def testEntry() -> i32:
|
||||
return 0
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Member count mismatch'):
|
||||
with pytest.raises(Type5SolverException, match='Tuple element count mismatch'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -28,7 +28,7 @@ def testEntry() -> i32:
|
||||
return 0
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Member count mismatch'):
|
||||
with pytest.raises(Type5SolverException, match='Tuple element count mismatch'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from phasm.exceptions import StaticError
|
||||
from phasm.type3.entry import Type3Exception
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
@ -76,7 +76,7 @@ class CheckedValueRed:
|
||||
CONST: CheckedValueBlue = CheckedValueRed(1)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='CheckedValueBlue must be CheckedValueRed instead'):
|
||||
with pytest.raises(Type5SolverException, match='Not the same type'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -91,7 +91,20 @@ class CheckedValueRed:
|
||||
CONST: (CheckedValueBlue, u32, ) = (CheckedValueRed(1), 16, )
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='CheckedValueBlue must be CheckedValueRed instead'):
|
||||
with pytest.raises(Type5SolverException, match='Not the same type'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_type_mismatch_struct_call_arg_count():
|
||||
code_py = """
|
||||
class CheckedValue:
|
||||
value1: i32
|
||||
value2: i32
|
||||
|
||||
CONST: CheckedValue = CheckedValue(1)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match='Not the same type'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -105,7 +118,7 @@ def testEntry(arg: Struct) -> (i32, i32, ):
|
||||
return arg.param
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match=type_ + r' must be \(i32, i32, \) instead'):
|
||||
with pytest.raises(Type5SolverException, match='Not the same type'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -132,7 +145,6 @@ class f32:
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.skip(reason='FIXME: See constraintgenerator.py for AccessStructMember')
|
||||
def test_struct_not_accessible():
|
||||
code_py = """
|
||||
@exported
|
||||
@ -140,7 +152,67 @@ def testEntry(x: u8) -> u8:
|
||||
return x.y
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='u8 is not struct'):
|
||||
with pytest.raises(Type5SolverException, match='Must be a struct'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_struct_does_not_have_field():
|
||||
code_py = """
|
||||
class CheckedValue:
|
||||
value: i32
|
||||
|
||||
@exported
|
||||
def testEntry(x: CheckedValue) -> u8:
|
||||
return x.y
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match='Must have a field with this name'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_struct_literal_does_not_fit():
|
||||
code_py = """
|
||||
class CheckedValue:
|
||||
value: i32
|
||||
|
||||
@exported
|
||||
def testEntry() -> CheckedValue:
|
||||
return 14
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match='Cannot convert from literal integer'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_struct_wrong_struct():
|
||||
code_py = """
|
||||
class CheckedValue:
|
||||
value: i32
|
||||
|
||||
class MessedValue:
|
||||
value: i32
|
||||
|
||||
@exported
|
||||
def testEntry() -> CheckedValue:
|
||||
return MessedValue(14)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match='Not the same type'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_struct_wrong_arg_count():
|
||||
code_py = """
|
||||
class CheckedValue:
|
||||
value1: i32
|
||||
value2: i32
|
||||
|
||||
@exported
|
||||
def testEntry() -> CheckedValue:
|
||||
return CheckedValue(14)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match='Not the same type'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
|
||||
@ -1,21 +1,22 @@
|
||||
import pytest
|
||||
import wasmtime
|
||||
|
||||
from phasm.type3.entry import Type3Exception
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_, in_put, exp_result', [
|
||||
('(u8, u8, )', (45, 46), 45, ),
|
||||
('u8[2]', (45, 46), 45, ),
|
||||
('bytes', b'This is a test', 84)
|
||||
@pytest.mark.parametrize('in_typ, in_put, out_typ, exp_result', [
|
||||
('(u8, u8, )', (45, 46), 'u8', 45, ),
|
||||
('u16[2]', (45, 46), 'u16', 45, ),
|
||||
('u32[...]', (45, 46), 'u32', 45, ),
|
||||
('bytes', b'This is a test', 'u8', 84),
|
||||
])
|
||||
def test_subscript_0(type_, in_put, exp_result):
|
||||
def test_subscript_0(in_typ, in_put, out_typ, exp_result):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry(f: {type_}) -> u8:
|
||||
def testEntry(f: {in_typ}) -> {out_typ}:
|
||||
return f[0]
|
||||
"""
|
||||
|
||||
@ -70,7 +71,7 @@ def testEntry(f: {type_}) -> u32:
|
||||
return f[0]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='u32 must be u8 instead'):
|
||||
with pytest.raises(Type5SolverException, match='Not the same type'):
|
||||
Suite(code_py).run_code(in_put)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -81,7 +82,7 @@ def testEntry(x: (u8, u32, u64), y: u8) -> u64:
|
||||
return x[y]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Must index with integer literal'):
|
||||
with pytest.raises(Type5SolverException, match='Must index with integer literal'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -92,7 +93,7 @@ def testEntry(x: (u8, u32, u64)) -> u64:
|
||||
return x[0.0]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Must index with integer literal'):
|
||||
with pytest.raises(Type5SolverException, match='Must index with integer literal'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -108,7 +109,7 @@ def testEntry(x: {type_}) -> u8:
|
||||
return x[-1]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Tuple index out of range'):
|
||||
with pytest.raises(Type5SolverException, match='May not be negative'):
|
||||
Suite(code_py).run_code(in_put)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -119,7 +120,7 @@ def testEntry(x: (u8, u32, u64)) -> u64:
|
||||
return x[4]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Tuple index out of range'):
|
||||
with pytest.raises(Type5SolverException, match='Tuple index out of range'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -146,5 +147,5 @@ def testEntry(x: u8) -> u8:
|
||||
return x[0]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='u8 cannot be subscripted'):
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type3.entry import Type3Exception
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
@ -41,7 +41,7 @@ def test_assign_to_tuple_with_tuple():
|
||||
CONSTANT: (u32, ) = 0
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Must be tuple'):
|
||||
with pytest.raises(Type5SolverException, match='Cannot convert from literal integer'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -50,7 +50,7 @@ def test_tuple_constant_too_few_values():
|
||||
CONSTANT: (u32, u8, u8, ) = (24, 57, )
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Tuple element count mismatch'):
|
||||
with pytest.raises(Type5SolverException, match='Tuple element count mismatch'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -59,7 +59,7 @@ def test_tuple_constant_too_many_values():
|
||||
CONSTANT: (u32, u8, u8, ) = (24, 57, 1, 1, )
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Tuple element count mismatch'):
|
||||
with pytest.raises(Type5SolverException, match='Tuple element count mismatch'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -68,7 +68,7 @@ def test_tuple_constant_type_mismatch():
|
||||
CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
|
||||
with pytest.raises(Type5SolverException, match=r'Must fit in 1 byte\(s\)'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
|
||||
@ -18,7 +18,7 @@ class ExpResult:
|
||||
) + ')'
|
||||
|
||||
TYPE_LIST = [
|
||||
'u8', 'u32', 'u64',
|
||||
'u8', 'u16', 'u32', 'u64',
|
||||
]
|
||||
|
||||
@pytest.mark.integration_test
|
||||
|
||||
101
tests/integration/test_typeclasses/test_convertable.py
Normal file
101
tests/integration/test_typeclasses/test_convertable.py
Normal file
@ -0,0 +1,101 @@
|
||||
import pytest
|
||||
import wasmtime
|
||||
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_convert_not_implemented():
|
||||
code_py = """
|
||||
class Foo:
|
||||
val: i32
|
||||
|
||||
class Baz:
|
||||
val: i32
|
||||
|
||||
@exported
|
||||
def testEntry(x: Foo) -> Baz:
|
||||
return convert(x)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('in_typ, in_val, out_typ, exp_val', [
|
||||
('u32', 1000, 'f32', 1000.0, ),
|
||||
('u32', 1000, 'f64', 1000.0, ),
|
||||
('u64', 1000, 'f32', 1000.0, ),
|
||||
('u64', 1000, 'f64', 1000.0, ),
|
||||
('i32', 1000, 'f32', 1000.0, ),
|
||||
('i32', 1000, 'f64', 1000.0, ),
|
||||
('i64', 1000, 'f32', 1000.0, ),
|
||||
('i64', 1000, 'f64', 1000.0, ),
|
||||
])
|
||||
def test_convert_ok(in_typ, in_val, out_typ, exp_val):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry(x: {in_typ}) -> {out_typ}:
|
||||
return convert(x)
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code(in_val)
|
||||
assert exp_val == result.returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_truncate_not_implemented():
|
||||
code_py = """
|
||||
class Foo:
|
||||
val: i32
|
||||
|
||||
class Baz:
|
||||
val: i32
|
||||
|
||||
@exported
|
||||
def testEntry(x: Foo) -> Baz:
|
||||
return truncate(x)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('in_typ, in_val, out_typ, exp_val', [
|
||||
('f32', 1000.0, 'u32', 1000, ),
|
||||
('f64', 1000.0, 'u32', 1000, ),
|
||||
('f32', 1000.0, 'u64', 1000, ),
|
||||
('f64', 1000.0, 'u64', 1000, ),
|
||||
('f32', 1000.0, 'i32', 1000, ),
|
||||
('f64', 1000.0, 'i32', 1000, ),
|
||||
('f32', 1000.0, 'i64', 1000, ),
|
||||
('f64', 1000.0, 'i64', 1000, ),
|
||||
|
||||
('f32', 3e9, 'u32', 3e9, ),
|
||||
('f32', 1e19, 'u64', 9999999980506447872, ),
|
||||
])
|
||||
def test_truncate_ok(in_typ, in_val, out_typ, exp_val):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry(x: {in_typ}) -> {out_typ}:
|
||||
return truncate(x)
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code(in_val)
|
||||
assert exp_val == result.returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('in_typ, in_val, out_typ', [
|
||||
('f32', 3e9, 'i32', ),
|
||||
('f64', 1e19, 'i64', ),
|
||||
])
|
||||
def test_truncate_not_representible(in_typ, in_val, out_typ):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry(x: {in_typ}) -> {out_typ}:
|
||||
return truncate(x)
|
||||
"""
|
||||
|
||||
with pytest.raises(wasmtime.Trap, match='integer overflow'):
|
||||
Suite(code_py).run_code(in_val)
|
||||
@ -1,10 +1,10 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type3.entry import Type3Exception
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
INT_TYPES = ['u8', 'u32', 'u64', 'i8', 'i32', 'i64']
|
||||
INT_TYPES = ['u8', 'u16', 'u32', 'u64', 'i8', 'i16', 'i32', 'i64']
|
||||
FLOAT_TYPES = ['f32', 'f64']
|
||||
|
||||
TYPE_MAP = {
|
||||
@ -25,11 +25,11 @@ class Foo:
|
||||
val: i32
|
||||
|
||||
@exported
|
||||
def testEntry(x: Foo, y: Foo) -> Foo:
|
||||
def testEntry(x: Foo, y: Foo) -> bool:
|
||||
return x == y
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Missing type class instantation: Eq Foo'):
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -107,11 +107,11 @@ class Foo:
|
||||
val: i32
|
||||
|
||||
@exported
|
||||
def testEntry(x: Foo, y: Foo) -> Foo:
|
||||
def testEntry(x: Foo, y: Foo) -> bool:
|
||||
return x != y
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Missing type class instantation: Eq Foo'):
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
|
||||
@ -1,16 +1,22 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type3.entry import Type3Exception
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
EXTENTABLE = [
|
||||
('u8', 'u16', ),
|
||||
('u8', 'u32', ),
|
||||
('u8', 'u64', ),
|
||||
('u16', 'u32', ),
|
||||
('u16', 'u64', ),
|
||||
('u32', 'u64', ),
|
||||
|
||||
('i8', 'i16', ),
|
||||
('i8', 'i32', ),
|
||||
('i8', 'i64', ),
|
||||
('i16', 'i32', ),
|
||||
('i16', 'i64', ),
|
||||
('i32', 'i64', ),
|
||||
]
|
||||
|
||||
@ -28,7 +34,7 @@ def testEntry(x: Foo) -> Baz:
|
||||
return extend(x)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Missing type class instantation: Extendable Foo Baz'):
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -48,17 +54,26 @@ def testEntry() -> {ext_to}:
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('ext_from,in_put,ext_to,exp_out', [
|
||||
('u8', 241, 'u16', 241),
|
||||
('u8', 241, 'u32', 241),
|
||||
('u32', 4059165169, 'u64', 4059165169),
|
||||
('u8', 241, 'u64', 241),
|
||||
('u16', 7681, 'u32', 7681),
|
||||
('u16', 7681, 'u64', 7681),
|
||||
('u32', 4059165169, 'u64', 4059165169),
|
||||
|
||||
('i8', 113, 'i16', 113),
|
||||
('i8', 113, 'i32', 113),
|
||||
('i32', 1911681521, 'i64', 1911681521),
|
||||
('i8', 113, 'i64', 113),
|
||||
('i8', 3585, 'i32', 3585),
|
||||
('i8', 3585, 'i64', 3585),
|
||||
('i32', 1911681521, 'i64', 1911681521),
|
||||
|
||||
('i8', -15, 'i16', -15),
|
||||
('i8', -15, 'i32', -15),
|
||||
('i32', -15, 'i64', -15),
|
||||
('i8', -15, 'i64', -15),
|
||||
('i16', -15, 'i32', -15),
|
||||
('i16', -15, 'i64', -15),
|
||||
('i32', -15, 'i64', -15),
|
||||
|
||||
])
|
||||
def test_extend_results(ext_from, ext_to, in_put, exp_out):
|
||||
@ -89,14 +104,23 @@ def testEntry() -> {ext_from}:
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('ext_to,in_put,ext_from,exp_out', [
|
||||
('u16', 0xF1F1, 'u8', 0xF1),
|
||||
('u32', 0xF1F1F1F1, 'u16', 0xF1F1),
|
||||
('u32', 0xF1F1F1F1, 'u8', 0xF1),
|
||||
('u64', 0xF1F1F1F1F1F1F1F1, 'u32', 0xF1F1F1F1),
|
||||
('u64', 0xF1F1F1F1F1F1F1F1, 'u16', 0xF1F1),
|
||||
('u64', 0xF1F1F1F1F1F1F1F1, 'u8', 0xF1),
|
||||
|
||||
('i16', 0xF171, 'i8', 113),
|
||||
('i16', 0xF1F1, 'i8', -15),
|
||||
('i32', 0xF1F171F1, 'i16', 29169),
|
||||
('i32', 0xF1F1F1F1, 'i16', -3599),
|
||||
('i32', 0xF1F1F171, 'i8', 113),
|
||||
('i32', 0xF1F1F1F1, 'i8', -15),
|
||||
('i64', 0x71F1F1F171F1F1F1, 'i32', 1911681521),
|
||||
('i64', 0x71F1F1F1F1F1F1F1, 'i32', -235802127),
|
||||
('i64', 0xF1F1F1F171F1F1F1, 'i32', 1911681521),
|
||||
('i64', 0xF1F1F1F1F1F1F1F1, 'i32', -235802127),
|
||||
('i64', 0xF1F1F1F1F1F171F1, 'i16', 29169),
|
||||
('i64', 0xF1F1F1F1F1F1F1F1, 'i16', -3599),
|
||||
('i64', 0xF1F1F1F1F1F1F171, 'i8', 113),
|
||||
('i64', 0xF1F1F1F1F1F1F1F1, 'i8', -15),
|
||||
])
|
||||
|
||||
@ -1,11 +1,27 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_sqrt_not_implemented():
|
||||
code_py = """
|
||||
class Foo:
|
||||
val: i32
|
||||
|
||||
@exported
|
||||
def testEntry(x: Foo) -> Foo:
|
||||
return sqrt(x)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_', ['f32', 'f64'])
|
||||
def test_builtins_sqrt(type_):
|
||||
def test_floating_sqrt(type_):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry() -> {type_}:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type3.entry import Type3Exception
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
from .test_natnum import FLOAT_TYPES, INT_TYPES
|
||||
@ -36,7 +36,7 @@ def testEntry(x: Foo[4]) -> Foo:
|
||||
return sum(x)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Missing type class instantation: NatNum Foo'):
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -129,7 +129,7 @@ def testEntry(b: i32[{typ_arg}]) -> i32:
|
||||
"""
|
||||
suite = Suite(code_py)
|
||||
|
||||
result = suite.run_code(tuple(in_put), with_traces=True, do_format_check=False)
|
||||
result = suite.run_code(tuple(in_put))
|
||||
assert exp_result == result.returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -168,9 +168,9 @@ def testEntry(x: {in_typ}, y: i32, z: i64[3]) -> i32:
|
||||
return foldl(x, y, z)
|
||||
"""
|
||||
|
||||
r_in_typ = in_typ.replace('[', '\\[').replace(']', '\\]')
|
||||
match = 'Type shape mismatch'
|
||||
|
||||
with pytest.raises(Type3Exception, match=f'{r_in_typ} must be a function instead'):
|
||||
with pytest.raises(Type5SolverException, match=match):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -184,7 +184,7 @@ def testEntry(i: i64, l: i64[3]) -> i64:
|
||||
return foldr(foo, i, l)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match=r'Callable\[i64, i64, i64\] must be Callable\[i32, i64, i64\] instead'):
|
||||
with pytest.raises(Type5SolverException, match='Not the same type'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -195,7 +195,7 @@ def testEntry(x: i32[5]) -> f64:
|
||||
return sum(x)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='f64 must be i32 instead'):
|
||||
with pytest.raises(Type5SolverException, match='Not the same type'):
|
||||
Suite(code_py).run_code((4, 5, 6, 7, 8, ))
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -206,16 +206,16 @@ def testEntry(x: i32) -> i32:
|
||||
return sum(x)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Missing type class instantation: Foldable i32.*i32 must be a constructed type instead'):
|
||||
with pytest.raises(Type5SolverException, match='Type shape mismatch'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_foldable_not_foldable():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(x: (i32, u32, )) -> i32:
|
||||
def testEntry(x: (u32, i32, )) -> i32:
|
||||
return sum(x)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Missing type class instantation: Foldable tuple'):
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
TYPE_LIST = ['f32', 'f64']
|
||||
@ -57,6 +59,26 @@ TEST_LIST = [
|
||||
('nearest(-5.5)', -6.0, ),
|
||||
]
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('method', [
|
||||
'ceil',
|
||||
'floor',
|
||||
'trunc',
|
||||
'nearest',
|
||||
])
|
||||
def test_fractional_not_implemented(method):
|
||||
code_py = f"""
|
||||
class Foo:
|
||||
val: i32
|
||||
|
||||
@exported
|
||||
def testEntry(x: Foo) -> Foo:
|
||||
return {method}(x)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_', TYPE_LIST)
|
||||
@pytest.mark.parametrize('test_in,test_out', TEST_LIST)
|
||||
|
||||
@ -1,9 +1,29 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
TYPE_LIST = ['u32', 'u64', 'i32', 'i64']
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('operator', [
|
||||
'//',
|
||||
'%',
|
||||
])
|
||||
def test_integral_not_implemented(operator):
|
||||
code_py = f"""
|
||||
class Foo:
|
||||
val: i32
|
||||
|
||||
@exported
|
||||
def testEntry(x: Foo, y: Foo) -> Foo:
|
||||
return x {operator} y
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_', TYPE_LIST)
|
||||
def test_integral_div_ok(type_):
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type3.entry import Type3Exception
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
@ -27,7 +27,7 @@ def testEntry(x: Foo, y: Foo) -> Foo:
|
||||
return x + y
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Missing type class instantation: NatNum Foo'):
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
|
||||
@ -5,16 +5,18 @@ import pytest
|
||||
from ..helpers import Suite
|
||||
|
||||
TYPE_LIST = [
|
||||
'u8', 'u32', 'u64',
|
||||
'i8', 'i32', 'i64',
|
||||
'u8', 'u16', 'u32', 'u64',
|
||||
'i8', 'i16', 'i32', 'i64',
|
||||
'f32', 'f64',
|
||||
]
|
||||
|
||||
BOUND_MAP = {
|
||||
'u8': struct.unpack('<BB', bytes.fromhex('00FF')),
|
||||
'u16': struct.unpack('<HH', bytes.fromhex('0000FFFF')),
|
||||
'u32': struct.unpack('<II', bytes.fromhex('00000000FFFFFFFF')),
|
||||
'u64': struct.unpack('<QQ', bytes.fromhex('0000000000000000FFFFFFFFFFFFFFFF')),
|
||||
'i8': struct.unpack('<bb', bytes.fromhex('807F')),
|
||||
'i16': struct.unpack('<hh', bytes.fromhex('0080FF7F')),
|
||||
'i32': struct.unpack('<ii', bytes.fromhex('00000080FFFFFF7F')),
|
||||
'i64': struct.unpack('<qq', bytes.fromhex('0000000000000080FFFFFFFFFFFFFF7F')),
|
||||
'f32': struct.unpack('<ff', bytes.fromhex('01000000FFFF7F7F')),
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from phasm.type3.entry import Type3Exception
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
@ -19,33 +21,55 @@ def testEntry(x: Foo) -> Baz:
|
||||
return promote(x)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Missing type class instantation: Promotable Foo Baz'):
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_promote_ok():
|
||||
@pytest.mark.parametrize('in_val, exp_val', [
|
||||
(10.5, 10.5, ),
|
||||
(9.999999616903162e+35, 9.999999616903162e+35, ),
|
||||
])
|
||||
def test_promote_ok(in_val, exp_val):
|
||||
code_py = """
|
||||
CONSTANT: f32 = 10.5
|
||||
|
||||
@exported
|
||||
def testEntry() -> f64:
|
||||
return promote(CONSTANT)
|
||||
def testEntry(x: f32) -> f64:
|
||||
return promote(x)
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code()
|
||||
result = Suite(code_py).run_code(in_val)
|
||||
|
||||
assert 10.5 == result.returned_value
|
||||
assert exp_val == result.returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_demote_ok():
|
||||
def test_demote_not_implemented():
|
||||
code_py = """
|
||||
CONSTANT: f64 = 10.5
|
||||
class Foo:
|
||||
val: i32
|
||||
|
||||
class Baz:
|
||||
val: i32
|
||||
|
||||
@exported
|
||||
def testEntry() -> f32:
|
||||
return demote(CONSTANT)
|
||||
def testEntry(x: Foo) -> Baz:
|
||||
return demote(x)
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code()
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
assert 10.5 == result.returned_value
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('in_val, exp_val', [
|
||||
(10.5, 10.5, ),
|
||||
(9.999999616903162e+35, 9.999999616903162e+35, ),
|
||||
(1e39, math.inf, ),
|
||||
])
|
||||
def test_demote_ok(in_val, exp_val):
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(x: f64) -> f32:
|
||||
return demote(x)
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code(in_val)
|
||||
|
||||
assert exp_val == result.returned_value
|
||||
|
||||
44
tests/integration/test_typeclasses/test_reinterpretable.py
Normal file
44
tests/integration/test_typeclasses/test_reinterpretable.py
Normal file
@ -0,0 +1,44 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_reinterpret_not_implemented():
|
||||
code_py = """
|
||||
class Foo:
|
||||
val: i32
|
||||
|
||||
class Baz:
|
||||
val: i32
|
||||
|
||||
@exported
|
||||
def testEntry(x: Foo) -> Baz:
|
||||
return reinterpret(x)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('in_typ, in_val, out_typ, exp_val', [
|
||||
('u32', 3225944128, 'f32', -3.1250152587890625, ),
|
||||
('u64', 13837591364432297984, 'f64', -3.1250152587890625, ),
|
||||
('i32', -1069023168, 'f32', -3.1250152587890625, ),
|
||||
('i64', -4609152709277253632, 'f64', -3.1250152587890625, ),
|
||||
('f32', -3.1250152587890625, 'u32', 3225944128, ),
|
||||
('f64', -3.1250152587890625, 'u64', 13837591364432297984, ),
|
||||
('f32', -3.1250152587890625, 'i32', -1069023168, ),
|
||||
('f64', -3.1250152587890625, 'i64', -4609152709277253632, ),
|
||||
])
|
||||
def test_reinterpret_ok(in_typ, in_val, out_typ, exp_val):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry(x: {in_typ}) -> {out_typ}:
|
||||
return reinterpret(x)
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code(in_val)
|
||||
assert exp_val == result.returned_value
|
||||
@ -1,11 +1,25 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type5.solver import Type5SolverException
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_sized_not_implemented():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(x: (i32, )) -> u32:
|
||||
return len(x)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type5SolverException, match='Missing type class instance'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_, in_put, exp_result', [
|
||||
('bytes', b'Hello, world!', 13),
|
||||
('u16[...]', (1, 2, 8), 3),
|
||||
('u8[4]', (1, 2, 3, 4), 4),
|
||||
])
|
||||
def test_len(type_, in_put, exp_result):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user