diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..bc1e652e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,34 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+## Bug description
+
+**Describe the bug**: a clear and concise description of what the bug is.
+
+## Steps to reproduce it
+
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. iOS]
+ - Browser [e.g. chrome, safari]
+ - Version [e.g. 22]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature-implementation.md b/.github/ISSUE_TEMPLATE/feature-implementation.md
new file mode 100644
index 00000000..c17bc64c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature-implementation.md
@@ -0,0 +1,15 @@
+---
+name: Feature Implementation
+about: Use this template for issues related to the documentation TODOs page.
+title: "[IMPL] "
+labels: implementation
+assignees: ''
+
+---
+
+## Implementation description
+
+## Expected behavior
+
+
+## Tests
diff --git a/.github/ISSUE_TEMPLATE/roadmap-item.md b/.github/ISSUE_TEMPLATE/roadmap-item.md
new file mode 100644
index 00000000..f48f7057
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/roadmap-item.md
@@ -0,0 +1,14 @@
+---
+name: Roadmap item
+about: Describe this issue template's purpose here.
+title: "[ROADMAP]"
+labels: roadmap
+assignees: ''
+
+---
+
+## Item Goals
+
+## Dependency
+
+## Description
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index decdb5d2..0f2c97c5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,7 +2,7 @@ name: ci_gh-pages
on:
push:
branches:
- - dev/python_impl/minimal_lang
+ - main
permissions:
contents: write
jobs:
@@ -25,4 +25,4 @@ jobs:
restore-keys: |
mkdocs-material-
- run: pip install mkdocs-material mkdocstrings markdown-exec
- - run: mkdocs gh-deploy --force
\ No newline at end of file
+ - run: mkdocs gh-deploy --force
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
new file mode 100644
index 00000000..90c483b1
--- /dev/null
+++ b/.github/workflows/pre-commit.yml
@@ -0,0 +1,21 @@
+name: pre-commit
+
+on:
+ pull_request:
+ branches: [main, dev/python]
+ push:
+ branches: [main, dev/python]
+
+jobs:
+ pre-commit:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v3
+ with:
+ python-version: '3.12'
+ - name: Run pre-commit
+ working-directory: python
+ run: |
+ pip install pre-commit
+ pre-commit run --all-files
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
new file mode 100644
index 00000000..d65f6ae6
--- /dev/null
+++ b/.github/workflows/pytest.yml
@@ -0,0 +1,30 @@
+name: pytest
+
+on:
+ push:
+ branches: [main, dev/python]
+ pull_request:
+ branches: [main, dev/python]
+
+jobs:
+ build:
+ name: Run pytest
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ["3.12"]
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies
+ working-directory: python
+ run: |
+ pip install ".[all,dev]"
+ - name: Run tests
+ working-directory: python
+ run: |
+ pytest .
diff --git a/README.md b/README.md
index 76d1404d..48382a99 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@ MIT
Please read this documentation before to understand how the repository is organized and how the language structure works.
-You can check the [TODOs.md](TODOs.md) page to see what is listed to be done. There (probably) are issues in the [H-hat issue's page](https://github.com/hhat-lang/hhat_lang/issues) that you may want to check and try to solve/implement as well.
+You can check the [TODOs.md](TODOs.md) page to see what is listed to be done. There (probably) are issues in the [H-hat issue's page](https://github.com/hhat-lang/hhat_lang/issues) that you may want to check and try to solve/implement as well.
At last, reach us out at the [Discord](http://discord.unitary.foundation)'s `#h-hat` channel to
@@ -110,4 +110,4 @@ learn more on how to contribute and chat, if you feel like doing so.
## Code of Conduct
We coexist in the same world. So be nice to others as you expect others to be nice to you :)
- the same world. So be nice to others as you expect others to be nice to you :)
\ No newline at end of file
+ the same world. So be nice to others as you expect others to be nice to you :)
diff --git a/definitions/IR_diagrams.drawio b/definitions/IR_diagrams.drawio
new file mode 100644
index 00000000..423aeb59
--- /dev/null
+++ b/definitions/IR_diagrams.drawio
@@ -0,0 +1,1478 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/CNAME b/docs/CNAME
index c3f06ca2..54b25b80 100644
--- a/docs/CNAME
+++ b/docs/CNAME
@@ -1 +1 @@
-docs.hhat-lang.org
\ No newline at end of file
+docs.hhat-lang.org
diff --git a/docs/TODOs.md b/docs/TODOs.md
index 0497fdff..4dfa9277 100644
--- a/docs/TODOs.md
+++ b/docs/TODOs.md
@@ -1,6 +1,6 @@
# TODOs
-Here sits an updated list of things to implement/write. Feel free to check them out and [place an issue](https://github.com/hhat-lang/hhat_lang/issues) or discuss them at the [Discord](http://discord.unitary.foundation)'s `#h-hat` channel.
+Here sits an updated list of things to implement/write. Feel free to check them out and [place an issue](https://github.com/hhat-lang/hhat_lang/issues) or discuss them at the [Discord `#h-hat` channel](https://discord.gg/J8udsUNRnk).
## H-hat core modules
diff --git a/docs/core/builtin_instr.md b/docs/core/builtin_instr.md
new file mode 100644
index 00000000..46f31392
--- /dev/null
+++ b/docs/core/builtin_instr.md
@@ -0,0 +1,11 @@
+
+!!! info
+
+ Built-in instructions are continuously being added. Check this page from time to time.
+
+
+The core instructions are divided between classical and quantum, as follows:
+
+- [Classical](classical_instr.md)
+
+- [Quantum](quantum_instr.md)
diff --git a/docs/core/classical_instr.md b/docs/core/classical_instr.md
new file mode 100644
index 00000000..ea673081
--- /dev/null
+++ b/docs/core/classical_instr.md
@@ -0,0 +1,86 @@
+# Classical Instructions
+
+Below you can find a list of existing (:white_check_mark:), under implementation (:construction:), or on TODO (:memo:) list functions/instructions.
+
+
+## 1. `null` type
+
+### `print`
+
+- Status: :memo:
+- Syntax: `print(msg:?T)`
+- Description: print `msg` (generic) argument as text on the terminal
+- Returns: nothing
+
+
+## 2. `bool` type
+
+### `not`
+
+- Status: :memo:
+- Syntax: `not(data:bool)`
+- Description: negates `data` (`bool`) argument binary data
+- Returns: the same type as `data` argument
+
+
+### `eq`
+
+- Status: :memo:
+- Syntax: `eq(a:?T b:?T)`
+- Description: compares `a` (generic) argument with `b` (generic) argument; both must be of the same type
+- Returns: a boolean literal (`true`, `false`) from the comparison
+
+
+### `le`
+
+- Status: :memo:
+- Syntax: `le(a:?T b:?T)`
+- Description: checks if `a` (generic) argument is less or equal than `b` (generic) argument; both must be of the same type
+- Returns: a boolean literal from the comparison
+
+
+### `lt`
+
+- Status: :memo:
+- Syntax: `lt(a:?T b:?T)`
+- Description: checks if `a` (generic) argument is less than `b` (generic) argument; both must be of the same type
+- Returns: a boolean literal from the comparison
+
+
+### `ge`
+
+- Status: :memo:
+- Syntax: `ge(a:?T b:?T)`
+- Description: checks if `a` (generic) argument is greater or equal than `b` (generic) argument; both must be of the same type
+- Returns: a boolean literal from the comparison
+
+
+### `gt`
+
+- Status: :memo:
+- Syntax: `gt(a:?T b:?T)`
+- Description: checks if `a` (generic) argument is greater than `b` (generic) argument; both must be of the same type
+- Returns: a boolean literal from the comparison
+
+
+## 3. `u16` type
+
+
+## 4. `u32` type
+
+### `add`
+
+- Status: :memo:
+- Syntax: `add(a:u32 b:u32)`
+- Description: performs an addition operation on `a` (`u32`) and `b` (`u32`) arguments
+- Returns: a `u32` type from the operation
+
+
+## 5. `u64` type
+
+### `add`
+
+- Status: :memo:
+- Syntax: `add(a:u64 b:u64)`
+- Description: performs an addition operation on `a` (`u64`) and `b` (`u64`) arguments
+- Returns: a `u64` type from the operation
diff --git a/docs/core/index.md b/docs/core/index.md
index 75f02441..9f9a0948 100644
--- a/docs/core/index.md
+++ b/docs/core/index.md
@@ -1,3 +1,60 @@
# Core Features
-In progress. This section is part of a TODO list.
+
+## Core implementation
+
+Some core features provide wide range of possibilities for the dialect implementation.
+
+
+### 1. Call with options
+
+It has the structure:
+
+```
+id (
+ option1: body
+ option2: body
+ ...
+)
+```
+
+It can be used to define an identifier `id` to hold some transformation through the options that are functions call (and not identifiers, as usually in function calls with arguments) with values being the body that is executed for that particular option.
+
+Example: [`if` statement](../dialects/heather/current_syntax.md#8-conditional-statements-if).
+
+
+### 2. Call with body
+
+It has the structure:
+
+```
+id (args) { body }
+```
+
+
+### 3. Call with body options
+
+It has the structure:
+
+```
+id (arg) {
+ option1: body
+ option2: body
+ ...
+}
+```
+
+Example: [pattern matching `match`](../dialects/heather/current_syntax.md#9-pattern-matching-match).
+
+
+### 4. Modifier
+
+It has the structure:
+
+```
+id
+id
+id
+```
+
+Example: []
diff --git a/docs/core/quantum_instr.md b/docs/core/quantum_instr.md
new file mode 100644
index 00000000..8c507ee2
--- /dev/null
+++ b/docs/core/quantum_instr.md
@@ -0,0 +1,79 @@
+# Quantum Instructions
+
+Below you can find a list of existing (:white_check_mark:), under implementation (:construction:), or on TODO (:memo:) list functions/instructions.
+
+
+## 1. `@null` type
+
+
+## 2. `@bool` type
+
+
+### `@not`
+
+- Status: :construction:
+- Syntax: `@not(@data:@bool)`
+- Description: negates `@data` (`@bool`) argument quantum binary data
+- Returns: a `@bool` quantum data
+
+
+### `@redim`
+
+- Status: :white_check_mark:
+- Syntax: `@redim(@data:@bool)`
+- Description: performs a [Hadamard gate](https://www.quantum-inspire.com/kbase/hadamard/) on a `@data` (`@bool`) argument
+- Returns: a `@bool` quantum data
+
+
+## 3. `@u2` type
+
+### `@not`
+
+- Status: :construction:
+- Syntax: `@not(@data:@u2)`
+- Description: negates `@data` (`@u2`) argument quantum binary data
+- Returns: a `@u2` quantum data
+
+
+### `@redim`
+
+- Status: :white_check_mark:
+- Syntax: `@redim(@data:@u2)`
+- Description: performs a [Hadamard gate](https://www.quantum-inspire.com/kbase/hadamard/) on a `@data` (`@u2`) argument
+- Returns: a `@u2` quantum data
+
+
+## 4. `@u3` type
+
+### `@not`
+
+- Status: :construction:
+- Syntax: `@not(@data:@u3)`
+- Description: negates `@data` (`@u3`) argument quantum binary data
+- Returns: a `@u3` quantum data
+
+
+### `@redim`
+
+- Status: :white_check_mark:
+- Syntax: `@redim(@data:@u3)`
+- Description: performs a [Hadamard gate](https://www.quantum-inspire.com/kbase/hadamard/) on a `@data` (`@u3`) argument
+- Returns: a `@u3` quantum data
+
+
+## 5. `@u4` type
+
+### `@not`
+
+- Status: :construction:
+- Syntax: `@not(@data:@u4)`
+- Description: negates `@data` (`@u4`) argument quantum binary data
+- Returns: a `@u4` quantum data
+
+
+### `@redim`
+
+- Status: :white_check_mark:
+- Syntax: `@redim(@data:@u4)`
+- Description: performs a [Hadamard gate](https://www.quantum-inspire.com/kbase/hadamard/) on a `@data` (`@u4`) argument
+- Returns: a `@u4` quantum data
diff --git a/docs/dialects/creation.md b/docs/dialects/creation.md
index 080c6a04..36ff350d 100644
--- a/docs/dialects/creation.md
+++ b/docs/dialects/creation.md
@@ -1,2 +1,2 @@
-In progress. This section is part of a TODO list.
\ No newline at end of file
+In progress. This section is part of a TODO list.
diff --git a/docs/dialects/heather/current_syntax.md b/docs/dialects/heather/current_syntax.md
index bc8accee..b56cc5a3 100644
--- a/docs/dialects/heather/current_syntax.md
+++ b/docs/dialects/heather/current_syntax.md
@@ -6,60 +6,193 @@
The H-hat's Heather dialect syntax works as follows:
-1. There is a main file that will be used for program execution. Its name can be anything, but it must contain a `main` keyword with brackets:
+## 1. Main
+There is a main file that will be used for program execution. Its name can be anything, but it must contain a `main` keyword with brackets:
- ```
- main {}
- ```
+```
+main {}
+```
-2. Code to be executed must live inside `main` body, e.g. anything inside the brackets will be executed.
-3. Comments are:
- - `// comment here` for oneliner
- - `/- big comment here... -/` for multiple lines
-4. Variable declaration:
+Code to be executed must live inside `main` body, e.g. anything inside the brackets will be executed.
+
+## 2. Comments
+
+```
+// single line comment here
+
+/- multiple
+lines comment
+here
+-/
+```
+
+## 3. Variables
+
+1. Variable declaration:
```
- var:type // for classical data
+ var:some-type // for classical data
- @var:@type // for quantum data
+ @var:@some-type // for quantum data
```
-5. Variable assignment:
+ All quantum literals, types, functions, variables (and so on) start with `@`. This is the universal identifier for quantum-related things.
+
+2. Variable assignment:
```
// classical
- var1:type = value // declare+assign
+ var1:some-type = value // declare+assign
var2 = value // assign
// quantum
- @var1:@type = @value // declare+assign
+ @var1:@some-type = @value // declare+assign
@var2 = @value // assign
```
-6. Call:
- ```
- do_smt() // empty call
- print("hoi") // one-argument call
- add(1 2) // multiple-anonymous argument call
- range(start:0 end:10) // multiple-named argument call
- ```
- - Multiple-argument call arguments can be separated by [any Heather-defined whitespaces](index.md#features)
- - Calls with named argument will have the `argument-name` followed by colon `:` and its value, e.g. `arg:val`
-7. Classical variable assignment:
+
+## 4. Calls
+
+```
+do_smt() // empty call
+print("hoi") // one-argument call
+add(1 2) // multiple-anonymous argument call
+range(start:0 end:10) // multiple-named argument call
+```
+
+- Multiple-argument call arguments can be separated by [any Heather-defined whitespaces](index.md#features)
+
+- Calls with named argument will have the `argument-name` followed by colon `:` and its value, e.g. `arg:val`
+
+1. Classical variable assignment:
```
- var:type = data // assign value
+ var:some-type = data // assign value
var = other-data // assign a new data
```
- Assigning data more than once to a classical variable may be possible if it is mutable. More on that at the [language core system page](../../core/index.md). If the variable is immutable, an error will happen.
-8. Quantum variable assignment:
+
+2. Quantum variable assignment:
```
- @var:@type = @first_value // assign the first value
+ @var:@some-type = @first_value // assign the first value
@fn(@var) // @fn will be appended to @var data
@other-fn(@var params) // @other-fn will be appended next
```
- A quantum data is an _appendable data container_, that is a data container that appends instructions applied to it in order. In the case above, the content of `@var` will be an array of elements: `[first_value, @fn(%self), @other-fn(%self params)]` that will be transformed and executed in order. More on what appendable data container is at the [language core system page](../../core/index.md).
-9. Casting:
- ```
- // classical data to classical data casting
- u32*16 // casts 16 to u32 type
-
- // quantum data to classical data casting
- u32*@2 // casts @2 to u32 type
- ```
- - Casting is a special property in the H-hat logic system. There is the usual classical to classical data casting, but also the quantum to classical data casting. The quantum to classical is special due to the nature of quantum data/variables. More on that in the [rule system page](../../rule_system.md). The syntax is `type*literal` or `type*variable`. In a similar fashion when declaring a variable one uses `variable:type`, it can be thought as the "other way around" process, that is why it was chosen to define the type first on casting (with a different syntax sugar, `*`, to connect the type with the data).
+
+## 5. Casting
+
+```
+// classical data to classical data casting
+16*u32 // casts 16 to u32 type
+
+// quantum data to classical data casting
+@2*u32 // casts @2 to u32 type
+```
+ - Casting is a special property in the H-hat logic system. There is the usual classical to classical data casting, but also the quantum to classical data casting. The quantum to classical is special due to the nature of quantum data/variables. More on that in the [rule system page](../../rule_system.md). The syntax is `literal*type` or `variable*type`. In a similar fashion when declaring a variable one uses `variable:type`, with a different syntax sugar, `*`, to connect the data with the type.
+
+## 6. Types
+
+There are 4 types of type definitions: `single`, `struct`, `enum` and `union`. Below you can find how to define them:
+
+```
+type lines:u32
+/- a single type called 'lines' that is
+defined by u32. -/
+
+type point { x:u32 y:u32 }
+/- a struct type called 'point' with
+members 'x ' of type u32 and 'y' of type u32. -/
+
+
+type result {
+ ok{ msg:str }
+ err
+}
+/- a enum type called 'result' with
+members 'ok', a struct data with member 'msg',
+and 'err', a simple identifier. -/
+
+
+type code union {
+ number:u64
+ text:str
+}
+/- a union type called 'code' with
+members 'number' of type u64 and 'text' of type str. -/
+```
+
+Some key aspects of each type:
+
+ - Single types can be thought as a "label" for a given type, but it has its own properties and checks
+
+ - Structs are always defined by members with a name and type
+
+ - Enums can be either identifiers or structs
+
+ - Unions have the `union` keyword before the body and can hold members with name and type, structs or enums; they behave like usual unions in C, for instance
+
+## 7. Functions
+
+
+```
+fn sum (a:u32 b:u32) u32 { =add(a b) }
+```
+
+- The `fn` keyword followed by the function name, `sum`, followed by the arguments between parenthesis, `a` and `b` of type `u32`, followed by the function type, `u32`, followed by the function body between brackets, `=add(a b)`. If the function has no return value, the `null` type can be left empty.
+
+- The last expression to be return must contain a `=` syntax sugar to indicate it is the "return" expression.
+
+## 8. Conditional statements (`if`)
+
+
+```
+if(
+ eq(a b): some-result
+ lt(a b): { some-bracket-body-result }
+ true: else-result
+)
+```
+
+An `if` statement is a call with options: each option is one condition with its result. If the result contains multiple expressions, it must be inside a bracket body. The last condition can be a `true` option, representing the `else` clause in other programming languages, or a fallback `default`.
+
+## 9. Pattern matching (`match`)
+
+
+```
+match (something) {
+ option1: some-result
+ option2: { some-bracket-body-result }
+ default: fallback-result
+}
+```
+
+In a similar fashion to `if`, pattern matching `match` will have options containing their respective instructions. However, `match` requires a variable, function call, literal, etc. to be matched against. This something is placed inside parenthesis after `match` and a bracket body is defined with the options.
+
+
+### 10. Modifiers
+
+```
+id
+
+id
+
+id
+```
+
+Modifiers provide an extensive way to complement, define or modify the data it is attached to, a literal, variable, type, function call, etc.
+
+
+### 11. Generics
+
+
+### 12. Type trait
+
+
+### 13. `typespace`
+
+
+### 14. Function traits and `fnspace`
+
+#### 14.1 call with options
+
+
+#### 14.2 call with body
+
+
+#### 14.3 call with body options
diff --git a/docs/dialects/heather/heather_syntax.md b/docs/dialects/heather/heather_syntax.md
index 72750051..0ad2365e 100644
--- a/docs/dialects/heather/heather_syntax.md
+++ b/docs/dialects/heather/heather_syntax.md
@@ -81,4 +81,4 @@ where `rvc` is the label for `stats.rv-continuous` and `rvd` is the label for `s
---
-**Note**: Incorporating external functions from downloaded sources or local project is still on design phase.
\ No newline at end of file
+**Note**: Incorporating external functions from downloaded sources or local project is still on design phase.
diff --git a/docs/dialects/heather/index.md b/docs/dialects/heather/index.md
index 5b9ce4c9..ae99586f 100644
--- a/docs/dialects/heather/index.md
+++ b/docs/dialects/heather/index.md
@@ -10,7 +10,7 @@ The name is twofold: a [plant](https://en.wikipedia.org/wiki/Calluna)[^1] and a
## Introduction
-This dialect was developed to enable programmers to experience H-hat rule system and explore ideas for a new quantum computer science theory that intends to focus more on the computer science of the thing, namely manipulate quantum data rather than quantum states.
+This dialect was developed to enable programmers to experience H-hat rule system and explore ideas for a new quantum computer science theory that intends to focus more on the computer science of the thing, namely manipulate quantum data rather than quantum states.
### Features
diff --git a/docs/hhat_logo.ico b/docs/hhat_logo.ico
new file mode 100644
index 00000000..5d99cfa5
Binary files /dev/null and b/docs/hhat_logo.ico differ
diff --git a/docs/hhat_logo.svg b/docs/hhat_logo.svg
new file mode 100644
index 00000000..db3c13a6
--- /dev/null
+++ b/docs/hhat_logo.svg
@@ -0,0 +1,12 @@
+
+
+
diff --git a/docs/how_contribute.md b/docs/how_contribute.md
new file mode 100644
index 00000000..3abfbf7e
--- /dev/null
+++ b/docs/how_contribute.md
@@ -0,0 +1,26 @@
+
+## Before writing code
+
+!!! info "Important"
+
+ Please read this documentation before to understand how the repository is organized and how the language structure works.
+
+
+## Setting up your environment
+
+Check the [Getting Started](getting_started.md) page on how to prepare your environment to contributing.
+
+You can check both [H-hat issues page](https://github.com/hhat-lang/hhat_lang/issues) and [TODOs.md](TODOs.md) page to see what is listed to be done. When picking something from the TODOs page, make sure to create a new issue through the issues page and selecting the "Feature Implementation" template. Fill it up accordingly (you can check the current open issues for guidance).
+
+Once the code is ready, you can make a pull request (PR) to the H-hat repository. Write a comprehensive description, referencing all the issues it addresses. You can assign [Doomsk](https://github.com/Doomsk) as reviewer, for instance.
+
+
+### Python code
+
+The code must follow the [pre-commit](https://pre-commit.com/) settings from pre-commit yaml ([.pre-commit-config.yaml](https://github.com/hhat-lang/hhat_lang/blob/main/python/.pre-commit-config,yaml)) file at H-hat's python implementation. Check more on the pre-commit page on how to set up and run it.
+
+
+### Community
+
+At last, you can reach us out at the [UnitaryFundation Discord](http://discord.unitary.foundation)'s `#h-hat` channel to
+learn more on how to contribute, address some questions, or chat, if you feel like doing so.
diff --git a/docs/index.md b/docs/index.md
index 524e40dd..1c9e0402 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -98,15 +98,8 @@ MIT
## How to Contribute
-!!! info "Important"
+Check the [How to Contribute](how_contribute.md) page.
- Please read this documentation before to understand how the repository is organized and how the language structure works.
-
-You can check the [TODOs.md](TODOs.md) page to see what is listed to be done. There (probably) are issues in the [H-hat issue's page](https://github.com/hhat-lang/hhat_lang/issues) that you may want to check and try to solve/implement as well.
-
-
-At last, reach us out at the [Discord](http://discord.unitary.foundation)'s `#h-hat` channel to
-learn more on how to contribute and chat, if you feel like doing so.
## Code of Conduct
diff --git a/docs/python/python_guide.md b/docs/python/python_guide.md
index 1b51c946..2c98490c 100644
--- a/docs/python/python_guide.md
+++ b/docs/python/python_guide.md
@@ -1,7 +1,9 @@
-To properly and safely install H-hat on your computer, you need to configure a [Python virtual environment](https://docs.python.org/3/tutorial/venv.html "Python official virtual environment tutorial"). You can choose between various packages, including [venv](https://docs.python.org/3/library/venv.html#creating-virtual-environments "Create with Python's venv"), [hatch](https://hatch.pypa.io/1.12/ "Hatch: package and project manager"), [uv](https://docs.astral.sh/uv/ "uv: fast package and project manager in Rust"), [pdm](https://pdm-project.org/latest/ "PDM: modern package and project manager"), and [poetry](https://python-poetry.org/ "poetry: package manager"). :material-information-outline:{ title="Here we mentioned the most common ones, but there are plenty of other package and project managers. Choose the one that suits your needs." }
+To properly and safely install H-hat on your computer, you need to configure a [Python virtual environment](https://docs.python.org/3/tutorial/venv.html "Python official virtual environment tutorial"). You can choose between various packages, including [venv](https://docs.python.org/3/library/venv.html#creating-virtual-environments "Create with Python's venv"), [hatch](https://hatch.pypa.io/1.12/ "Hatch: package and project manager"), [uv](https://docs.astral.sh/uv/ "uv: fast package and project manager in Rust"), [pdm](https://pdm-project.org/latest/ "PDM: modern package and project manager"), and [poetry](https://python-poetry.org/ "poetry: package manager"). (1)
+{ .annotate }
+1. Here we mentioned the most common ones, but there are plenty of other package and project managers. Choose the one that suits your needs.
-After configuring it, activate it (_each package has their own way to do it, please check it out before proceeding_), and choose one of the methods below to install `H-hat`:
+After configuring it, activate it (_each package has their own way to do it, please check it out before proceeding_), and choose **one** of the methods below to install `H-hat`, [Pypi](#via-pypi) or [Source code (currently recommended)](#via-source-code):
## Via Pypi
@@ -11,7 +13,7 @@ After configuring it, activate it (_each package has their own way to do it, ple
This is the easiest, most straightforward and simplest way to install `H-hat`. On the terminal, with the virtual environment enabled, type:[^1]
-[^1]: How to check [which shell I am using](https://askubuntu.com/questions/590899/how-do-i-check-which-shell-i-am-using#590902)?
+[^1]: How to check [which shell I am using](https://askubuntu.com/questions/590899/how-do-i-check-which-shell-i-am-using#590902)?
### If you are using `bash` shell:
@@ -28,7 +30,7 @@ pip install hhat-lang ".[all]"
Either the options above will install all the [tools](../toolchain.md), features and a [H-hat dialect](../dialects/index.md) called [Heather](../dialects/heather/index.md) so you can start learning and writing your own code.
-## Via source code recommended
+## Via source code recommended {#via-source-code}
Use the clone HTTPS link in [the H-hat repository page](https://github.com/hhat-lang/hhat_lang) and git clone it, using the terminal:
@@ -53,5 +55,3 @@ pip install -e ".[all]"
!!! note
This approach is meant to be used for those who want to modify the code.
-
-
diff --git a/docs/running_hhat.md b/docs/running_hhat.md
index 07848055..0a78849f 100644
--- a/docs/running_hhat.md
+++ b/docs/running_hhat.md
@@ -18,7 +18,7 @@ For more information on commands available.
## With Python currently available :gear:
!!! info "In progress"
- This step is in progress, so you may experience some breaking or incomplete parts.
+ This step is in progress, so you may experience some breaking or incomplete parts.
The project file organization is created as follows:
@@ -73,4 +73,3 @@ It will create a `file_type.hat` at `hat_types/`, as well as its documentation c
!!! failure "Unavailable"
This step is currently unavailable. May be implemented in the future.
-
diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css
index bdc3ba25..55764880 100644
--- a/docs/stylesheets/extra.css
+++ b/docs/stylesheets/extra.css
@@ -1,4 +1,8 @@
:root > * {
+.md-typeset .admonition,
+.md-typeset details {
+ font-size: 100%
+}
}
diff --git a/docs/toolchain.md b/docs/toolchain.md
index d3b47171..c2c6fb98 100644
--- a/docs/toolchain.md
+++ b/docs/toolchain.md
@@ -3,4 +3,3 @@ The tools available for H-hat are the following:
- [Dialect creation](dialects/creation.md): the most important features that identifies a programming language as part of H-hat family are present there, and can be incorporated and extended within your own dialect.
- [Commandline interface (CLI)](cli.md): the commands you can use to create a new project, update it, include code documentation, execute a project code, access notebooks for interactive code, etc.
- [Notebooks](notebooks.md): write your code in an interactive way through [Jupyter](https://jupyter.org/) notebooks.
-
diff --git a/mkdocs.yml b/mkdocs.yml
index 309d5d8a..b39c21e3 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -6,13 +6,19 @@ repo_url: "https://github.com/hhat-lang/hhat_lang/"
repo_name: hhat-lang/hhat_lang
nav:
- - Home: index.md
+ - Home:
+ - H-hat language: index.md
+ - How to Contribute: how_contribute.md
- Getting Started:
- Getting Started: getting_started.md
- Guides:
- Python: python/python_guide.md
- Rust: rust/rust_guide.md
- Understanding H-hat:
+ - Built-in Instructions:
+ - core/builtin_instr.md
+ - Classical: core/classical_instr.md
+ - Quantum: core/quantum_instr.md
- Rule System: rule_system.md
- Core Features: core/index.md
- Tools:
@@ -87,6 +93,8 @@ theme:
font:
text: Lato
code: JetBrains Mono
+ logo: hhat_logo.svg
+ favicon: hhat_logo.ico
features:
- navigation.sections
- navigation.tabs
diff --git a/python/.pre-commit-config.yaml b/python/.pre-commit-config.yaml
new file mode 100644
index 00000000..4c261734
--- /dev/null
+++ b/python/.pre-commit-config.yaml
@@ -0,0 +1,30 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v5.0.0
+ hooks:
+ - id: end-of-file-fixer
+ - id: check-added-large-files
+ - repo: https://github.com/psf/black
+ rev: 24.10.0
+ hooks:
+ - id: black
+ - repo: https://github.com/pre-commit/mirrors-mypy
+ rev: v1.18.1
+ hooks:
+ - id: mypy
+ args: [
+ --ignore-missing-imports,
+ --disable-error-code, attr-defined,
+ --disable-error-code, override,
+ --disable-error-code, misc,
+ ]
+ exclude: tests
+ additional_dependencies: [tokenize-rt==3.2.0]
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: "v0.13.2"
+ hooks:
+ - id: ruff-check
+ args: [--fix, --show-fixes, --show-files]
+
+default_language_version:
+ python: python3.12
diff --git a/python/README.md b/python/README.md
index 774df346..ef3ce831 100644
--- a/python/README.md
+++ b/python/README.md
@@ -49,6 +49,10 @@ After installing it, you should be able to use
H-hat [cli](https://en.wikipedia.org/wiki/Command-line_interface) to prepare the environment for a
new H-hat project.
+## Documentation
+
+Check more on [the documentation](https://docs.hhat-lang.org).
+
> [!NOTE]
>
> Work in progress.
diff --git a/python/pyproject.toml b/python/pyproject.toml
index 4c8304ed..f67a5f3a 100644
--- a/python/pyproject.toml
+++ b/python/pyproject.toml
@@ -7,7 +7,8 @@ name = "hhat-lang"
description = "H-hat quantum programming language core and rule system implemented in Python"
dynamic = ["version"]
authors = [
- { name = "Eduardo Maschio", email = "eduardo.maschio@hhat-lang.org" }
+ { name = "Eduardo Maschio", email = "eduardo.maschio@hhat-lang.org" },
+ { name = "Inho Choi", email = "inho.quantum@gmail.com" }
]
readme = "README"
requires-python = ">=3.12"
@@ -25,6 +26,8 @@ classifiers = [
]
dependencies = [
+ "rich>=14.0.0",
+ "typer>=0.16.0",
]
[project.optional-dependencies]
@@ -35,6 +38,7 @@ heather = [
qiskit = [
"qiskit",
+ "qiskit-aer",
"qiskit-ibm-runtime",
]
@@ -52,7 +56,7 @@ openqasm2 = [
]
all = [
- "hhat-lang[heather,qiskit,squidasm,netqasm,openqasm2]"
+ "hhat-lang[heather,qiskit,openqasm2]"
]
dev = [
@@ -60,8 +64,12 @@ dev = [
"pytest",
"pytest-xdist",
"pytest-mypy",
+ "pytest-mock",
"ipython",
+ "isort",
+ "ruff",
"black",
+ "flaky",
"isort",
"pre-commit",
"mkdocs",
@@ -71,6 +79,9 @@ dev = [
"mike",
]
+[project.scripts]
+hat = "hhat_lang.toolchain.cli.cli:main"
+
[tool.setuptools_scm]
root = ".."
version_scheme = "no-guess-dev"
@@ -99,6 +110,6 @@ required-imports = ["from __future__ import annotations"]
[tool.mypy]
python_version = "3.12"
-disable_error_code = ["import-untyped"]
+disable_error_code = ["import-untyped", "override", "misc", "attr-defined"]
pretty = true
-exclude = "build"
\ No newline at end of file
+exclude = ["build", "tests"]
diff --git a/python/src/hhat_lang/core/code/abstract.py b/python/src/hhat_lang/core/code/abstract.py
new file mode 100644
index 00000000..e6c18a18
--- /dev/null
+++ b/python/src/hhat_lang/core/code/abstract.py
@@ -0,0 +1,271 @@
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from pathlib import Path
+from typing import Any, Iterable
+
+from hhat_lang.core.code.base import BaseFnCheck, BaseIRBlock
+from hhat_lang.core.code.symbol_table import SymbolTable
+from hhat_lang.core.data.core import CompositeSymbol, Symbol
+
+
+class IRHash:
+ """
+ IR key class to handle the nodes for the IRGraph.
+
+ Use ``key`` attribute when comparing between ``IRModule``. It is also hashable.
+
+ Use ``uid`` attribute when comparing between type or function name
+ and an ``IRHash``, ``IRNode`` or ``IRModule``. This is the default when applying
+ ``hash`` function to this class instance.
+ """
+
+ _key: Path
+ _uid: int
+ __slots__ = ("_key", "_uid")
+
+ def __init__(self, ir_path: Path):
+ if isinstance(ir_path, Path):
+ self._key = ir_path
+ self._uid = hash(ir_path)
+
+ else:
+ raise ValueError("ir_path must be of type Path")
+
+ @property
+ def key(self) -> Path:
+ return self._key
+
+ @property
+ def uid(self) -> int:
+ return self._uid
+
+ def __hash__(self) -> int:
+ return self._uid
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, IRHash):
+ return hash(self) == hash(other)
+
+ if isinstance(other, BaseIRModule):
+ return hash(self) == hash(other.path)
+
+ return False
+
+ def __repr__(self) -> str:
+ txt = Path()
+
+ for p in reversed(self._key.parts):
+ if p == "src":
+ break
+
+ txt = Path(p) / txt
+
+ return f"[{txt}#{str(self._uid)[-8:]}]"
+
+
+###################
+# IR BASE CLASSES #
+###################
+
+
+class BaseIRModule(ABC):
+ """Base abstract class for IR module definitions."""
+
+ _path: Path
+ _symbol_table: SymbolTable
+ _main: BaseIRBlock
+
+ @property
+ def path(self) -> Path:
+ return self._path
+
+ @property
+ def uid(self) -> int:
+ return hash(self._path)
+
+ @property
+ def symbol_table(self) -> SymbolTable:
+ return self._symbol_table
+
+ @property
+ def main(self) -> BaseIRBlock:
+ return self._main
+
+ def __hash__(self) -> int:
+ return hash((hash(self._path), hash(self._symbol_table), hash(self._main)))
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, self.__class__):
+ return hash(self) == hash(other)
+
+ return False
+
+ def __contains__(
+ self, item: Symbol | CompositeSymbol | BaseFnCheck | Path | Any
+ ) -> bool:
+ return item in self._symbol_table.type or item in self._symbol_table.fn
+
+ @abstractmethod
+ def __str__(self) -> str:
+ raise NotImplementedError()
+
+
+class BaseIR(ABC):
+ """
+ Base class for the IR.
+
+ IR holds information about the main code execution (as an IR block), or a symbol
+ table containing type definitions or function definitions, and a reference table
+ to point the definitions of types or functions from other IRs.
+ """
+
+ _ref_table: RefTable
+ _module: BaseIRModule
+
+ @property
+ def module(self) -> BaseIRModule:
+ return self._module
+
+ @property
+ def ref_table(self) -> RefTable:
+ return self._ref_table
+
+ @abstractmethod
+ def __repr__(self) -> str:
+ raise NotImplementedError()
+
+
+###########################
+# REFERENCE TABLE CLASSES #
+###########################
+
+
+class RefTypeTable:
+ """Reference to types from another IR"""
+
+ _table: dict[Symbol | CompositeSymbol, IRHash]
+ __slots__ = ("_table",)
+
+ def __init__(self):
+ self._table = dict()
+
+ def add_ref(self, type_name: Symbol | CompositeSymbol, ir_path: Path) -> None:
+ if isinstance(type_name, Symbol | CompositeSymbol) and isinstance(
+ ir_path, Path
+ ):
+ self._table[type_name] = IRHash(ir_path)
+
+ else:
+ raise ValueError(f"wrong reference type table input ({type_name})")
+
+ def get_irpath(self, type_name: Symbol | CompositeSymbol) -> Path:
+ return self.get_irhash(type_name).key
+
+ def get_irhash(self, type_name: Symbol | CompositeSymbol) -> IRHash:
+ return self._table[type_name]
+
+ def __hash__(self) -> int:
+ return hash(self._table)
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, RefTypeTable):
+ return hash(self) == hash(other)
+
+ return False
+
+ def __contains__(self, item: Symbol | CompositeSymbol | Any) -> bool:
+ return item in self._table
+
+ def __len__(self) -> int:
+ return len(self._table)
+
+ def __iter__(self) -> Iterable[tuple[Symbol | CompositeSymbol, IRHash]]:
+ return iter(self._table.items())
+
+
+class RefFnTable:
+ """Reference to functions from another IR"""
+
+ _table: dict[BaseFnCheck, IRHash]
+ __slots__ = ("_table",)
+
+ def __init__(self):
+ self._table = dict()
+
+ def add_ref(self, fn_name: BaseFnCheck, ir_path: Path) -> None:
+ if isinstance(fn_name, BaseFnCheck) and isinstance(ir_path, Path):
+ self._table[fn_name] = IRHash(ir_path)
+
+ else:
+ raise ValueError(f"wrong reference type table input ({fn_name})")
+
+ def get_irpath(self, fn_name: BaseFnCheck) -> Path:
+ return self.get_irhash(fn_name).key
+
+ def get_irhash(self, fn_name: BaseFnCheck) -> IRHash:
+ return self._table[fn_name]
+
+ def __hash__(self) -> int:
+ return hash(self._table)
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, RefFnTable):
+ return hash(self) == hash(other)
+
+ return False
+
+ def __contains__(self, item: Symbol | CompositeSymbol | BaseFnCheck) -> bool:
+ match item:
+ case BaseFnCheck():
+ return item in self._table
+
+ case Symbol() | CompositeSymbol():
+ for k in self._table:
+ if item == k.name:
+ return True
+
+ return False
+
+ case _:
+ return False
+
+ def __len__(self) -> int:
+ return len(self._table)
+
+ def __iter__(self) -> Iterable[tuple[BaseFnCheck, IRHash]]:
+ return iter(self._table.items())
+
+
+class RefTable:
+ """To store reference for types and functions from another IR"""
+
+ _types: RefTypeTable
+ _fns: RefFnTable
+ __slots__ = ("_types", "_fns")
+
+ def __init__(
+ self, *, type_ref: RefTypeTable | None = None, fn_ref: RefFnTable | None = None
+ ):
+ self._types = type_ref or RefTypeTable()
+ self._fns = fn_ref or RefFnTable()
+
+ @property
+ def types(self) -> RefTypeTable:
+ return self._types
+
+ @property
+ def fns(self) -> RefFnTable:
+ return self._fns
+
+ def __hash__(self) -> int:
+ return hash(hash(self._types) + hash(self._fns))
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, RefTable):
+ return hash(self) == hash(other)
+
+ return False
+
+ def __contains__(self, item: Symbol | CompositeSymbol | BaseFnCheck) -> bool:
+ return item in self._types or item in self._fns
diff --git a/python/src/hhat_lang/core/code/ast.py b/python/src/hhat_lang/core/code/ast.py
deleted file mode 100644
index d84b356a..00000000
--- a/python/src/hhat_lang/core/code/ast.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from __future__ import annotations
-
-from abc import ABC
-from typing import Iterable
-
-
-class AST(ABC):
- """
- Abstract syntax tree for the Heather parser. Consuming the
- lexer and generating the AST to structure the code so the IR
- can be generated for the Evaluator to execute the code.
-
- All the AST code should inherit from this class, including Node
- and Terminal child classes.
- """
-
- _name: str
- _value: tuple[str | AST | tuple[AST, ...], ...] | tuple[str]
-
- @property
- def name(self) -> str:
- return self._name
-
- @property
- def value(self) -> tuple[str | AST | tuple[AST, ...], ...] | tuple[str]:
- return self._value
-
- def __iter__(self) -> Iterable:
- yield from self._value
-
-
-class Node(AST):
- def __repr__(self) -> str:
- res = " ".join(str(k) for k in self.value)
- return f"{self.name}({res})"
-
-
-class Terminal(AST):
- def __repr__(self) -> str:
- res = f"[{self.name}]" if self.name != self.value[0] else ""
- return f"{self.__class__.__name__}{res}{self.value[0]}"
diff --git a/python/src/hhat_lang/core/code/base.py b/python/src/hhat_lang/core/code/base.py
new file mode 100644
index 00000000..0c25ee98
--- /dev/null
+++ b/python/src/hhat_lang/core/code/base.py
@@ -0,0 +1,253 @@
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from enum import Enum
+from typing import Any, Iterable, Iterator
+
+from hhat_lang.core.data.core import (
+ CompositeSymbol,
+ CompositeWorkingData,
+ Symbol,
+ WorkingData,
+)
+
+
+class BaseFnKey:
+ """
+ Base class for functions definition on memory's SymbolTable.
+ Provide functions a signature.
+
+ Given a function::
+
+ fn sum (a:u64 b:u64) u64 { ::add(a b) }
+
+ The function key object is as follows::
+
+ BaseFnKey(
+ name=Symbol("sum"),
+ type=Symbol("u64"),
+ args_names=(Symbol("a"), Symbol("b"),),
+ args_types=(Symbol("u64"), Symbol("u64"),)
+ )
+
+ When trying to retrieve the function data, use ``BaseFnCheck``
+ parent instance instead:
+
+ """
+
+ _name: Symbol | CompositeSymbol
+ _type: Symbol | CompositeSymbol
+ _args_types: tuple | tuple[Symbol | CompositeSymbol, ...]
+ _args_names: tuple | tuple[Symbol, ...]
+ _hash_value: int
+
+ # TODO: implement code for comparison of out of order args_names
+
+ def __init__(
+ self,
+ fn_name: Symbol | CompositeSymbol,
+ fn_type: Symbol | CompositeSymbol,
+ args_names: tuple | tuple[Symbol, ...],
+ args_types: tuple | tuple[Symbol | CompositeSymbol, ...],
+ ):
+ # check correct types for each argument before proceeding
+ assert (
+ isinstance(fn_name, Symbol | CompositeSymbol)
+ and isinstance(fn_type, Symbol | CompositeSymbol)
+ and all(isinstance(k, Symbol) for k in args_names)
+ and all(isinstance(p, Symbol | CompositeSymbol) for p in args_types)
+ ), (
+ f"Wrong types provided for function definition on SymbolTable:\n"
+ f" name: {fn_name}\n type: {fn_type}\n args types: {args_types}\n"
+ f" args names: {args_names}\n",
+ )
+
+ self._name = fn_name
+ self._type = fn_type
+ self._args_names = args_names
+ self._args_types = args_types
+ self._hash_value = hash((hash(self._name), hash(self._args_types)))
+
+ @property
+ def name(self) -> Symbol | CompositeSymbol:
+ return self._name
+
+ @property
+ def type(self) -> Symbol | CompositeSymbol:
+ return self._type
+
+ @property
+ def args_types(self) -> tuple | tuple[Symbol | CompositeSymbol, ...]:
+ return self._args_types
+
+ @property
+ def args_names(self) -> tuple | tuple[Symbol, ...]:
+ return self._args_names
+
+ def __hash__(self) -> int:
+ return self._hash_value
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, BaseFnKey | BaseFnCheck):
+ return hash(self) == hash(other)
+
+ return False
+
+ def has_args(self, args: tuple[Symbol, ...]) -> bool:
+ return set(self._args_names) == set(args)
+
+ def __iter__(self) -> Iterator[tuple[Symbol, Symbol | CompositeSymbol]]:
+ return iter(zip(self.args_names, self.args_types))
+
+ def __repr__(self) -> str:
+ return (
+ f"{self.name}:{self.type}("
+ f"{' '.join(f'{k}:{v}' for k, v in zip(self.args_names, self.args_types))})"
+ )
+
+
+class BaseFnCheck:
+ """
+ Base function class to check and retrieve a given function from the SymbolTable.
+ """
+
+ _name: Symbol | CompositeSymbol
+ _args_types: tuple | tuple[Symbol | CompositeSymbol, ...]
+ _hash_value: int
+ __slots__ = ("_name", "_args_types", "_hash_value")
+
+ def __init__(
+ self,
+ fn_name: Symbol | CompositeSymbol,
+ args_types: tuple | tuple[Symbol | CompositeSymbol, ...],
+ ):
+ # checks types correctness
+ assert isinstance(fn_name, Symbol | CompositeSymbol) and all(
+ isinstance(p, Symbol | CompositeSymbol) for p in args_types
+ ), (
+ f"Wrong types provided for function retrieval on SymbolTable:\n"
+ f" name: {fn_name}\n args types: {args_types}\n",
+ )
+
+ self._name = fn_name
+ self._args_types = args_types
+ self._hash_value = hash((hash(self._name), hash(self._args_types)))
+
+ @property
+ def name(self) -> Symbol | CompositeSymbol:
+ return self._name
+
+ def transform(
+ self, fn_type: Symbol | CompositeSymbol, args_names: tuple[Symbol, ...]
+ ) -> BaseFnKey:
+ if all(
+ isinstance(p, Symbol | CompositeSymbol) for p in args_names
+ ) and isinstance(fn_type, Symbol | CompositeSymbol):
+ return BaseFnKey(
+ fn_name=self.name,
+ fn_type=fn_type,
+ args_types=self._args_types,
+ args_names=args_names,
+ )
+ raise ValueError(
+ f"cannot transform FnKey with fn type {fn_type} and args {args_names}"
+ )
+
+ def check_args_types(self, *values: Symbol | CompositeSymbol) -> bool:
+ """Check whether ``*values`` have the same values as in function args types"""
+
+ return len(values) == len(self._args_types) and all(
+ k == v for k, v in zip(values, self._args_types)
+ )
+
+ def __hash__(self) -> int:
+ return self._hash_value
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, BaseFnCheck):
+ return hash(self) == hash(other)
+
+ return False
+
+ def __repr__(self) -> str:
+ args = ", ".join(f"{t}" for t in self._args_types)
+ return f"fn(name={self.name}, args=({args}))"
+
+
+class BaseIRBlock(ABC):
+ """
+ Base for IR block classes.
+ """
+
+ _name: BaseIRBlockFlag
+ args: tuple[WorkingData | CompositeWorkingData | BaseIRBlock | BaseIRInstr, ...]
+
+ @property
+ def name(self) -> BaseIRBlockFlag:
+ return self._name
+
+ def __hash__(self) -> int:
+ return hash((hash(self._name), hash(self.args)))
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, BaseIRBlock):
+ return hash(self) == hash(other)
+
+ return False
+
+ def __iter__(self) -> Iterator:
+ return iter(self.args)
+
+ @abstractmethod
+ def __repr__(self) -> str:
+ raise NotImplementedError()
+
+
+class BaseIRBlockFlag(Enum):
+ """
+ Base for IR block flag classes. Should be used to define types of IR blocks.
+ """
+
+
+class BaseIRFlag(Enum):
+ """
+ Base for IR flag classes. It should be used to create enums for instructions,
+ such as ``CALL``, ``DECLARE``, ``ASSIGN``, ``RETURN``, etc.
+ """
+
+
+class BaseIRInstr(ABC):
+ """
+ Base IR instruction classes.
+ """
+
+ _name: BaseIRFlag
+ args: tuple[BaseIRBlock | WorkingData | CompositeWorkingData, ...] | tuple
+ _hash_value: int
+
+ def __init__(self):
+ self._hash_value = hash((hash(self.name), hash(self.args)))
+
+ @property
+ def name(self) -> Any:
+ return self._name
+
+ @abstractmethod
+ def resolve(self, *args: Any, **kwargs: Any) -> Any:
+ raise NotImplementedError()
+
+ def __hash__(self) -> int:
+ return self._hash_value
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, BaseIRInstr):
+ return hash(self) == hash(other)
+
+ return False
+
+ def __iter__(self) -> Iterable:
+ return iter(self.args)
+
+ @abstractmethod
+ def __repr__(self) -> str:
+ raise NotImplementedError()
diff --git a/python/src/hhat_lang/core/code/instructions.py b/python/src/hhat_lang/core/code/instructions.py
index 1210b26b..52e207e6 100644
--- a/python/src/hhat_lang/core/code/instructions.py
+++ b/python/src/hhat_lang/core/code/instructions.py
@@ -1,14 +1,23 @@
from __future__ import annotations
-from typing import Any
from abc import ABC, abstractmethod
+from enum import Enum, auto
+from typing import Any
from hhat_lang.core import DataParadigm
from hhat_lang.core.code.utils import InstrStatus
+class QInstrFlag(Enum):
+ """Flags describing special quantum instruction behavior."""
+
+ NONE = auto()
+ SKIP_GEN_ARGS = auto()
+
+
class BaseInstr(ABC):
"""Base instruction class"""
+
name: str
_instr_status: InstrStatus
@@ -18,25 +27,30 @@ def status(self) -> InstrStatus:
@property
@abstractmethod
- def is_quantum(self) -> bool:
- ...
+ def is_quantum(self) -> bool: ...
@property
@abstractmethod
- def paradigm(self) -> DataParadigm:
- ...
+ def paradigm(self) -> DataParadigm: ...
@abstractmethod
- def __call__(self, *args: Any, **kwargs: Any) -> Any:
- ...
+ def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
class QInstr(BaseInstr, ABC):
"""Quantum instruction base class"""
+ flag: QInstrFlag = QInstrFlag.NONE
+
def __init__(self):
self._instr_status = InstrStatus.NOT_STARTED
+ @property
+ def skip_gen_args(self) -> bool:
+ """Whether argument generation should be skipped for this instruction."""
+
+ return self.flag == QInstrFlag.SKIP_GEN_ARGS
+
@property
def is_quantum(self) -> bool:
return True
diff --git a/python/src/hhat_lang/core/code/ir.py b/python/src/hhat_lang/core/code/ir.py
deleted file mode 100644
index fe3b1939..00000000
--- a/python/src/hhat_lang/core/code/ir.py
+++ /dev/null
@@ -1,262 +0,0 @@
-from __future__ import annotations
-
-from typing import Any, Iterable, Callable
-from abc import ABC, abstractmethod
-from enum import Enum, auto
-
-from hhat_lang.core.data.core import Symbol, CompositeSymbol
-from hhat_lang.core.types.abstract_base import BaseTypeDataStructure
-
-
-class BlockIRFlag(Enum):
- INSTR_BLOCK = auto()
- CONTROLFLOW_BLOCK = auto()
- CLOSURE_BLOCK = auto()
- CALL_BLOCK = auto()
-
-
-class InstrIRFlag(Enum):
- ASSIGN = auto()
- DECLARE = auto()
- DECLARE_ASSIGN = auto()
- CALL = auto()
- CONTROLFLOW = auto()
- TEST_COND = auto()
- LOOP = auto()
- LOOP_COND = auto()
- RETURN = auto()
-
-
-class InstrIR(ABC):
- """
- To hold individual instructions and their arguments (if any).
- """
-
- _name: Symbol | CompositeSymbol
- _args: ArgsIR
- _flag: InstrIRFlag
-
- @property
- def name(self) -> Symbol | CompositeSymbol:
- return self._name
-
- @property
- def args(self) -> ArgsIR:
- return self._args
-
- @property
- def flag(self) -> InstrIRFlag:
- return self._flag
-
-
-class BlockIR(ABC):
- """
- To hold tuple of instructions (`InstrIR`) and blocks (`BlockIR`).
- """
-
- _instrs: tuple[InstrIR | BlockIR, ...]
-
- def __getitem__(self, item: int) -> InstrIR | BlockIR:
- return self._instrs[item]
-
- def __iter__(self) -> Iterable:
- yield from self._instrs
-
-
-class ArgsIR(ABC):
- """
- To hold instructions arguments.
- """
-
- _args: tuple[Any, ...]
-
- def __contains__(self, arg: Any) -> bool:
- return arg in self._args
-
- def __iter__(self) -> Iterable:
- yield from self._args
-
-
-class BodyIR:
- """
- Body IR for functions body and `main`.
- """
-
- _data: list[InstrIR | BlockIR] | list
-
- def __init__(self):
- self._data = []
-
- def push(self, new_item: Any, to_instr_fn: Callable | None = None) -> None:
- if not isinstance(new_item, InstrIR | BlockIR):
-
- if to_instr_fn is not None:
- new_item = to_instr_fn(new_item)
-
- else:
- raise ValueError(
- "'to_instr_fn' argument must not be None if the item is not 'InstrIR'."
- )
-
- self._data.append(new_item)
-
- def __iter__(self) -> Iterable:
- yield from self._data
-
-
-####################
-# type annotations #
-####################
-
-TypeTable = dict[Symbol | CompositeSymbol, BaseTypeDataStructure]
-"""
-Type annotation for `TypeTable`. It holds all the types used throughout the program.
-
-A dictionary where the keys are the type names (`Symbol`, `CompositeSymbol`) and the
- values are their data structure (`BaseTypeDataStructure`).
-"""
-
-FnTable = dict[
- tuple[ # key: define the function header
- Symbol | CompositeSymbol, # first element: function name
- Symbol | CompositeSymbol, # second element: function type
- tuple | tuple[Symbol | CompositeSymbol | tuple[
- Symbol, Symbol | CompositeSymbol
- ], ...] # third element: args; empty args, only types args, name and type pairs
- ],
- BodyIR] # value: the function body
-"""
-Type annotation for `FnTable`, a function table that holds all the program functions.
-
-It consists in a dictionary where the key is:
-
-- first element: function name (`Symbol`, `CompositeSymbol`)
-- second element: function type (`Symbol`, `CompositeSymbol`)
-- third element: args, which can be empty, only types or name-type pairs
- (tuple of `Symbol`, `CompositeSymbol` or tuple pairs with `Symbol` and
- `Symbol` or `CompositeSymbol`)
-
-and the dictionary value is body of the function.
-"""
-
-
-#############
-# IR TABLES #
-#############
-
-class TypeIR:
- """To format, store and retrieve all types used in the program."""
-
- _data: TypeTable
-
- def __init__(self):
- self._data = dict()
-
- @property
- def table(self) -> TypeTable:
- return self._data
-
- def push(self, new_type: BaseTypeDataStructure):
- self[new_type.name] = new_type
-
- def get(self, name: Symbol | CompositeSymbol) -> BaseTypeDataStructure:
- return self[name]
-
- def __setitem__(self, key: Symbol | CompositeSymbol, value: BaseTypeDataStructure) -> None:
- if isinstance(key, (Symbol, CompositeSymbol)) and isinstance(value, BaseTypeDataStructure):
- if key not in self._data:
- self._data[key] = value
-
- else:
- print("[[LOG:IR]] ignore adding the same type in the type table.")
-
- else:
- raise ValueError(
- "type table needs symbol/composite symbol as key and data structure as value."
- )
-
- def __getitem__(self, key: Symbol | CompositeSymbol) -> BaseTypeDataStructure:
- return self._data[key]
-
- def __contains__(self, key: Symbol | CompositeSymbol) -> bool:
- return key in self._data
-
-
-class BaseFnIR(ABC):
- """
- Function IR class: handles the function table data. It is an abstract class, because
- it's up to the dialect to implement how it wants to handle function call, e.g.
- whether arguments can be passed in order, a name and value pair in any order, etc.
- """
-
- _data: FnTable
-
- @property
- def table(self) -> FnTable:
- return self._data
-
- @abstractmethod
- def push(self, *ags: Any, **kwargs: Any) -> Any:
- pass
-
- @abstractmethod
- def get(self, item: Any) -> Any:
- pass
-
- @abstractmethod
- def __setitem__(self, key: Any, value: Any) -> None:
- pass
-
- @abstractmethod
- def __getitem__(self, key: Any) -> Any:
- pass
-
- @abstractmethod
- def __contains__(self, item: Any) -> bool:
- pass
-
-
-class BaseIR(ABC):
- """
- Where the IR code lies. It contains `TypeIR` (type table), `FnIR` (function table),
- and the `main` code.
- """
-
- _data: BodyIR
- _type_table: TypeIR
- _fn_table: BaseFnIR
-
- def __init__(self):
- self._data = BodyIR()
- self._type_table = TypeIR()
- self._fn_table = BaseFnIR()
-
- @property
- def types(self) -> TypeIR:
- return self._type_table
-
- @property
- def fns(self) -> BaseFnIR:
- return self._fn_table
-
- @property
- def main(self) -> BodyIR:
- return self._data
-
- def add_type(self, name: Symbol | CompositeSymbol, data: BaseTypeDataStructure) -> None:
- self._type_table[name] = data
-
- @abstractmethod
- def add_fn(
- self,
- *,
- fn_name: Symbol,
- fn_type: Symbol | CompositeSymbol,
- fn_args: Any,
- body: Any,
- ) -> None:
- ...
-
- def add_body(self, body: Any) -> None:
- for k in body:
- self._data.push(k)
diff --git a/python/src/hhat_lang/core/code/new_ir.py b/python/src/hhat_lang/core/code/new_ir.py
new file mode 100644
index 00000000..aab03545
--- /dev/null
+++ b/python/src/hhat_lang/core/code/new_ir.py
@@ -0,0 +1,351 @@
+from __future__ import annotations
+
+from pathlib import Path
+from typing import Any, Iterator, Mapping
+
+from hhat_lang.core.code.abstract import BaseIR, IRHash, RefTable
+from hhat_lang.core.code.base import BaseFnCheck
+from hhat_lang.core.code.utils import ResultPHF, gen_phf, get_hash
+from hhat_lang.core.data.core import (
+ CompositeSymbol,
+ Symbol,
+)
+from hhat_lang.core.data.fn_def import FnDef
+from hhat_lang.core.types.abstract_base import BaseTypeDataStructure
+
+##############
+# IR SECTION #
+##############
+
+
+####################
+# IR GRAPH CLASSES #
+####################
+
+
+class IRNode:
+ """
+ Stores node key as ``IRHash`` and value as ``BaseIRModule`` child instance.
+
+ Use ``key`` attribute to retrieve its ``IRHash`` value, when checking a type
+ or function. Use ``uid`` attribute to retrieve the hash value from its internal
+ ``IRModule`` instance, when comparing between ``IRNode``.
+ """
+
+ _uid: int
+ _irhash: IRHash
+ _ir: BaseIR
+ _path: Path
+ __slots__ = ("_irhash", "_ir", "_uid", "_path")
+
+ def __init__(self, node: BaseIR):
+ self._uid = node.module.uid
+ self._path = node.module.path
+ self._irhash = IRHash(self._path)
+ self._ir = node
+
+ @property
+ def irhash(self) -> IRHash:
+ return self._irhash
+
+ @property
+ def ir(self) -> BaseIR:
+ return self._ir
+
+ @property
+ def uid(self) -> int:
+ return self._uid
+
+ @property
+ def path(self) -> Path:
+ return self._path
+
+ def __hash__(self) -> int:
+ return self._uid
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, IRHash | IRNode):
+ return hash(self) == hash(other)
+
+ return False
+
+ def __contains__(
+ self, item: Symbol | CompositeSymbol | BaseFnCheck | Path | Any
+ ) -> bool:
+ return item in self._ir.module or item == self._path
+
+ def __repr__(self) -> str:
+ return f"Node({self.irhash})"
+
+
+class NodeSet:
+ """
+ Efficiently store ``IRNode`` elements together with the perfect hash function (PHF)
+ ``ResultPHF`` instance.
+ """
+
+ _data: tuple[IRNode, ...] | tuple
+ _phf: ResultPHF | None
+
+ def __init__(self, *data: IRNode, phf: ResultPHF | None = None):
+ if (
+ all(isinstance(k, IRNode) for k in data)
+ and isinstance(phf, ResultPHF)
+ or phf is None
+ ):
+ self._data = data
+ self._phf = phf
+
+ else:
+ raise ValueError("node set accepts only IRNode instances")
+
+ @property
+ def phf(self) -> ResultPHF:
+ if self._phf is not None:
+ return self._phf
+
+ raise ValueError("node's phf instance is null")
+
+ @classmethod
+ def new_set(cls, *data: IRNode, phf: ResultPHF) -> NodeSet:
+ return cls(*data, phf=phf)
+
+ def __contains__(
+ self,
+ item: (
+ IRHash | IRNode | Path | tuple[Path, Symbol | CompositeSymbol | BaseFnCheck]
+ ),
+ ) -> bool:
+ match item:
+ case IRNode():
+ return item in self._data
+
+ case IRHash():
+ for node in self._data:
+ if item == node.irhash:
+ return True
+
+ case Path():
+ for node in self._data:
+ if item == node.path:
+ return True
+
+ case tuple():
+ for node in self._data:
+ _path = item[0]
+ _symbol = item[1]
+
+ if _path == node.path and _symbol in node.ir.module:
+ return True
+
+ return False
+
+ def __getitem__(self, item: IRHash | int) -> IRNode | None:
+ if isinstance(item, IRHash):
+ if self._phf is not None:
+ res = get_hash(hash(item), self.phf)
+
+ if res is not None:
+ return self._data[res]
+
+ raise ValueError(
+ "node set must have phf attribute defined and item hash mush not be null"
+ )
+
+ # assuming it is integer
+ return self._data[item]
+
+ def __len__(self) -> int:
+ return len(self._data)
+
+ def __iter__(self) -> Iterator[IRNode]:
+ return iter(self._data)
+
+
+class IRGraph:
+ """
+ Graph to hold IR instances as nodes and their relationship as edges. The relationship
+ (stored in ``RefTable``) happens when a type or function is imported from another IR
+ module.
+ """
+
+ _is_built: bool
+ _nodes: NodeSet
+ _tmp_nodes: tuple[IRNode, ...] | tuple
+ _main_node: IRHash
+
+ def __init__(self):
+ self._is_built = False
+ self._nodes = NodeSet()
+ self._tmp_nodes = ()
+
+ @property
+ def nodes(self) -> NodeSet:
+ """Last node in a program will always be its 'main' file."""
+ return self._nodes
+
+ @property
+ def main_node(self) -> IRHash:
+ return self._main_node
+
+ @property
+ def is_built(self) -> bool:
+ return self._is_built
+
+ def add_node(self, ir: BaseIR) -> IRHash:
+ """Add an IR to the graph node."""
+
+ node = IRNode(ir)
+ self._tmp_nodes += (node,)
+ return node.irhash
+
+ def add_main_node(self, ir: BaseIR) -> IRHash:
+ """Add main IR to the graph node."""
+ self._main_node = self.add_node(ir)
+ return self._main_node
+
+ def _check_refs(self) -> bool:
+ """
+ Check references inside the node set so there are no missing IRs to build the ir graph.
+ """
+
+ for node in self._nodes:
+ for _, irhash in node.ir.ref_table.types:
+ if irhash not in self._nodes:
+ return False
+
+ for _, irhash in node.ir.ref_table.fns:
+ if irhash not in self._nodes:
+ return False
+
+ return True
+
+ def build(self) -> None:
+ """Build IR graph for performance and optimization purposes."""
+
+ if not self._is_built and len(self._tmp_nodes) > 0:
+ node_res, node_phf = gen_phf(self._tmp_nodes)
+ self._nodes = NodeSet.new_set(*node_res, phf=node_phf) # type: ignore [arg-type]
+ self._tmp_nodes = ()
+
+ if self._check_refs():
+ self._is_built = True
+
+ else:
+ raise ValueError("missing nodes to build the ir graph")
+
+ else:
+ raise ValueError(
+ f"ir graph is already built ({self._is_built}) or no nodes were found"
+ f" (total nodes: {len(self._tmp_nodes)})."
+ )
+
+ def update(self, cur_node_key: IRHash, new_node: BaseIR) -> None:
+ """
+ TODO: implement it
+
+ Update to a new node (IR module) from a given current node key (``IRHash``)
+
+ Args:
+ cur_node_key:
+ new_node:
+ """
+
+ raise NotImplementedError()
+
+ def get_fns(self, module_path: Path, item: Symbol) -> tuple[BaseFnCheck, ...]:
+ for tmp_node in self._tmp_nodes:
+ if module_path == tmp_node.path and item in tmp_node.ir.module:
+ fns = tmp_node.ir.module.symbol_table.fn.get(item)
+
+ if isinstance(fns, dict):
+ return tuple(fn for fn in fns)
+
+ raise ValueError(f"item {item} not found in ir node at {module_path}")
+
+ def __contains__(self, item: Any) -> bool:
+ if isinstance(item, Symbol | BaseFnCheck):
+ for tmp_node in self._tmp_nodes:
+ if item in tmp_node:
+ return True
+
+ if isinstance(item, Path):
+ for tmp_node in self._tmp_nodes:
+ if item in tmp_node:
+ return True
+
+ return False
+
+ def __repr__(self) -> str:
+ max_n = str(len(self.nodes))
+ txt = "".join(
+ f"\nN#{'0' * (len(max_n) - len(str(n)))}{n}{k.ir}"
+ for n, k in enumerate(self.nodes)
+ )
+ return f"==============\n=*=IR GRAPH=*=\n==============\n{txt}\n"
+
+
+####################################
+# BUILDING REFERENCE TABLE SECTION #
+####################################
+
+
+def build_reftable(
+ types: Mapping[Symbol | CompositeSymbol, Path] | None = None,
+ fns: Mapping[BaseFnCheck, Path] | None = None,
+) -> RefTable:
+ types = types or dict()
+ fns = fns or dict()
+ ref_table = RefTable()
+
+ for type_name, ir_ref in types.items():
+ ref_table.types.add_ref(type_name, ir_ref)
+
+ for f_name, ir_ref in fns.items():
+ ref_table.fns.add_ref(f_name, ir_ref)
+
+ return ref_table
+
+
+################################
+# RETRIEVING FUNCTIONS SECTION #
+################################
+
+
+def get_type(
+ node_key: IRHash, importing: Symbol | CompositeSymbol, ir_graph: IRGraph
+) -> BaseTypeDataStructure | None:
+ """
+ Import a type ``importing`` from an IR module's hash value ``node_key``. Return
+ the type instance or ``None`` if not found.
+ """
+
+ node: IRNode | None = ir_graph.nodes[node_key]
+
+ if node is not None:
+ return node.ir.module.symbol_table.type.get(importing)
+
+ return None
+
+
+def get_fn(
+ node_key: IRHash, importing: BaseFnCheck, ir_graph: IRGraph
+) -> FnDef | dict[BaseFnCheck, FnDef] | None:
+ """
+ Import a function check instance ``importing`` from an IR module's hash value ``node_key``.
+
+ Args:
+ node_key: the ``IRHash`` instance
+ importing: the function ``BaseFnCheck`` instance
+ ir_graph: the program's ``IRGraph``
+
+ Returns:
+ A ``FnDef`` instance or ``None``, if no function is found.
+ """
+
+ node: IRNode | None = ir_graph.nodes[node_key]
+
+ if node is not None:
+ return node.ir.module.symbol_table.fn.get(importing)
+
+ return None
diff --git a/python/src/hhat_lang/core/code/symbol_table.py b/python/src/hhat_lang/core/code/symbol_table.py
new file mode 100644
index 00000000..9b2f03bf
--- /dev/null
+++ b/python/src/hhat_lang/core/code/symbol_table.py
@@ -0,0 +1,186 @@
+from __future__ import annotations
+
+from collections import OrderedDict
+from typing import Any, Iterable
+
+from hhat_lang.core.code.base import BaseFnCheck, BaseFnKey
+from hhat_lang.core.data.core import CompositeSymbol, Symbol
+from hhat_lang.core.data.fn_def import FnDef
+from hhat_lang.core.types.abstract_base import BaseTypeDataStructure
+
+
+class TypeTable:
+ _table: OrderedDict[Symbol | CompositeSymbol, BaseTypeDataStructure]
+ __slots__ = ("_table",)
+
+ def __init__(self):
+ self._table = OrderedDict()
+
+ @property
+ def table(self) -> OrderedDict[Symbol | CompositeSymbol, BaseTypeDataStructure]:
+ return self._table
+
+ def add(self, name: Symbol | CompositeSymbol, data: BaseTypeDataStructure) -> None:
+ if isinstance(name, Symbol | CompositeSymbol) and isinstance(
+ data, BaseTypeDataStructure
+ ):
+ if name not in self.table:
+ self.table[name] = data
+
+ else:
+ raise ValueError(
+ f"type {name} must be symbol/composite symbol and its data must be "
+ f"known type structure"
+ )
+
+ def get(
+ self, name: Symbol | CompositeSymbol, default: Any | None = None
+ ) -> BaseTypeDataStructure | None:
+ return self.table.get(name, default)
+
+ def __hash__(self) -> int:
+ return hash(self.table)
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, TypeTable):
+ return hash(self) == hash(other)
+
+ return False
+
+ def __getitem__(
+ self, item: Symbol | CompositeSymbol
+ ) -> BaseTypeDataStructure | None:
+ return self.get(item)
+
+ def __contains__(self, item: Any) -> bool:
+ return item in self.table
+
+ def __len__(self) -> int:
+ return len(self.table)
+
+ def __iter__(self) -> Iterable:
+ return iter(self.table.items())
+
+ def __repr__(self) -> str:
+ content = "\n ".join(f"{v}" for v in self.table.values())
+ return f"\n - types:\n {content}\n"
+
+
+class FnTable:
+ """
+ This class holds functions definitions as ``BaseFnCheck`` for function
+ entry (function name, type and argument types) and its body (content).
+
+ Together with ``TypeTable``, ``SymbolTable`` and ``IRModule`` it provides
+ the base for an IR object picturing the full code.
+ """
+
+ _table: OrderedDict[Symbol | CompositeSymbol, dict[BaseFnCheck, FnDef]]
+ __slots__ = ("_table",)
+
+ def __init__(self):
+ self._table = OrderedDict()
+
+ @property
+ def table(
+ self,
+ ) -> OrderedDict[Symbol | CompositeSymbol, dict[BaseFnCheck, FnDef]]:
+ return self._table
+
+ def add(self, fn_entry: BaseFnCheck, data: FnDef) -> None:
+ if isinstance(data, FnDef):
+ if isinstance(fn_entry, BaseFnCheck):
+ if fn_entry.name in self.table:
+ self.table[fn_entry.name].update({fn_entry: data})
+
+ else:
+ self.table[fn_entry.name] = {fn_entry: data}
+
+ elif isinstance(fn_entry, BaseFnKey):
+ new_fn_entry = BaseFnCheck(
+ fn_name=fn_entry.name, args_types=fn_entry.args_types
+ )
+ if fn_entry.name in self.table:
+ self.table[fn_entry.name].update({new_fn_entry: data})
+
+ else:
+ self.table[fn_entry.name] = {new_fn_entry: data}
+
+ else:
+ raise ValueError(f"fn_entry is of wrong type ({type(fn_entry)})")
+
+ def get(
+ self,
+ fn_entry: Symbol | CompositeSymbol | BaseFnCheck,
+ default: Any | None = None,
+ ) -> FnDef | dict[BaseFnCheck, FnDef] | None:
+ match fn_entry:
+ case Symbol() | CompositeSymbol():
+ return self.table.get(fn_entry, default)
+
+ case BaseFnCheck():
+ if fn_entry.name in self.table:
+ return self.table[fn_entry.name].get(fn_entry, default)
+
+ raise ValueError(f"cannot retrieve fn {fn_entry}")
+
+ def __hash__(self) -> int:
+ return hash(self.table)
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, FnTable):
+ return hash(self) == hash(other)
+
+ return False
+
+ def __contains__(self, item: Any) -> bool:
+ match item:
+ case Symbol() | CompositeSymbol():
+ return item in self._table
+
+ case BaseFnCheck():
+ return item in self._table[item.name]
+
+ case _:
+ return False
+
+ def __len__(self) -> int:
+ return sum(len(k) for k in self.table.values())
+
+ def __iter__(self) -> Iterable:
+ return iter((p, q) for v in self.table.values() for p, q in v.items())
+
+ def __repr__(self) -> str:
+ content = "\n ".join(
+ f"{k}:\n {v}" for h in self.table.values() for k, v in h.items()
+ )
+ return f"\n - fns:\n {content}"
+
+
+class SymbolTable:
+ """To store types and functions"""
+
+ _types: TypeTable
+ _fns: FnTable
+ __slots__ = ("_types", "_fns")
+
+ def __init__(self):
+ self._types = TypeTable()
+ self._fns = FnTable()
+
+ @property
+ def type(self) -> TypeTable:
+ return self._types
+
+ @property
+ def fn(self) -> FnTable:
+ return self._fns
+
+ def __hash__(self) -> int:
+ return hash((self._types, self._fns))
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, SymbolTable):
+ return hash(self) == hash(other)
+
+ return False
diff --git a/python/src/hhat_lang/core/code/utils.py b/python/src/hhat_lang/core/code/utils.py
index 49785596..8749abf3 100644
--- a/python/src/hhat_lang/core/code/utils.py
+++ b/python/src/hhat_lang/core/code/utils.py
@@ -1,6 +1,8 @@
from __future__ import annotations
+import sys
from enum import IntEnum, auto
+from typing import Hashable
class InstrStatus(IntEnum):
@@ -22,12 +24,177 @@ def check_quantum_type_correctness(names: tuple[str, ...]) -> None:
prev_quantum = False
cur_quantum = False
for n, name in enumerate(names):
-
if n != 0 and cur_quantum and not prev_quantum:
raise ValueError(
f"{name} is an attribute from a non-quantum symbol. "
f"Cannot have a quantum attribute from a classical symbol."
)
- prev_quantum = True if cur_quantum else False
- cur_quantum = True if name.startswith("@") else False
+ prev_quantum = cur_quantum
+ cur_quantum = name.startswith("@")
+
+
+#######################################
+# PERFECT HASH FUNCTION (PHF) SECTION #
+#######################################
+
+
+def get_phf_prime(tuple_len: int) -> int:
+ """
+ Retrieve a prime for the perfect hash function (PHF) algorithm. Use the tuple length
+ to check which primer number to use, which must be bigger than ``tuple_len``.
+
+ Probably a relatively good size project may have a few hundreds items (types and
+ functions combined). By that time, python will not be useful to interpret the code
+ anyway, but we never know what things will come out of it.
+ """
+
+ if tuple_len <= 2**5:
+ return 37
+
+ if tuple_len <= 2**6:
+ return 67
+
+ if tuple_len <= 2**8:
+ return 257
+
+ if tuple_len <= 2**12:
+ return 4_099
+
+ if tuple_len <= 2**14:
+ return 16_411
+
+ # I don't think this number below will ever be needed, but for future references
+ return 1_048_583
+
+
+PHF_A_LIMIT = 1_000_000
+"""perfect hash function (PHF) parameter ``a`` limit"""
+
+# only compatible with 64- or 128-bit systems
+PHF_R_LIMIT = 127 if sys.maxsize > 2**64 else 61
+"""perfect hash function (PHF) parameter ``r`` limit"""
+
+
+class ResultPHF:
+ """Hold PHF result values"""
+
+ _a: int
+ _r: int
+ _n: int
+ _prime: int
+ __slots__ = ("_a", "_r", "_n", "_prime")
+
+ def __init__(self, *, a: int, r: int, prime: int, n: int):
+ self._a = a
+ self._r = r
+ self._prime = prime
+ self._n = n
+
+ @property
+ def a(self) -> int:
+ return self._a
+
+ @property
+ def r(self) -> int:
+ return self._r
+
+ @property
+ def n(self) -> int:
+ return self._n
+
+ @property
+ def prime(self) -> int:
+ return self._prime
+
+ def __repr__(self) -> str:
+ return f"PHF(a={self.a}, r={self.r}, prime={self.prime}, n={self.n})"
+
+
+def get_hash_with_args(value: int, a: int, r: int, n: int, prime: int) -> int:
+ """
+ Calculate the hash without a ``ResultPHF`` instance, but with the args. Use it when
+ finding the correct args to produce the PHF values (``a`` and ``r``) combination.
+ """
+
+ p = value * a
+ return ((p ^ (p >> r)) % prime) % n
+
+
+def _gen_res_a_r_phf(
+ group_tuple: tuple[Hashable, ...],
+ tuple_len: int,
+ a: int,
+ r: int,
+ prime: int,
+) -> tuple[Hashable, ...]:
+ """
+ Generate a perfect hash function (PHF) tuple.
+
+ Args:
+ group_tuple: the tuple of IR hashes, or IR hashes and symbol/function check tuple-pairs
+ tuple_len:
+ a: an integer parameter to define the index for each element in the ``group_tuple``
+ r: another integer parameter to define the index for each element in the ``group_tuple``
+ prime: the prime number used to define the index for each element in the ``group_tuple``
+
+ Returns:
+ A tuple with the ``group_tuple`` ordered by their PHF index. Empty tuple if the PHF
+ could not be found.
+ """
+
+ collision: bool = False
+ res_list: list = [None for _ in range(tuple_len)]
+
+ for obj in group_tuple:
+ h = get_hash_with_args(hash(obj), a, r, tuple_len, prime)
+
+ if obj not in res_list and res_list[h] is None:
+ res_list[h] = obj
+
+ else:
+ collision = True
+ break
+
+ if not collision and None not in res_list:
+ return tuple(res_list)
+
+ return ()
+
+
+def gen_phf(
+ group_tuple: tuple[Hashable, ...],
+) -> tuple[tuple[Hashable, ...], ResultPHF]:
+ """
+ Generate the perfect hash function (PHF). Each ``group_tuple`` element will be ordered
+ in a new tuple according to its newly calculated hash value. Each element has exactly
+ one unique index number that wil define its position in the new tuple.
+
+ Args:
+ group_tuple: a tuple with IR hash elements, or IR hash and symbol/function
+ check tuple-pairs
+
+ Returns:
+ A resulting tuple with the elements positioned in their respective index number
+ inside the tuple, and a ``ResultPHF`` instance with the ``a`` and ``r`` parameters
+ to retrieve the hash values of each element.
+ """
+
+ tuple_len: int = len(group_tuple)
+ prime = get_phf_prime(tuple_len)
+
+ for a in range(1, PHF_A_LIMIT):
+ for r in range(PHF_R_LIMIT):
+ res_list = _gen_res_a_r_phf(group_tuple, tuple_len, a, r, prime)
+
+ if res_list:
+ return tuple(res_list), ResultPHF(a=a, r=r, prime=prime, n=tuple_len)
+
+ raise ValueError("could not find satisfactory parameter values to generate the PHF")
+
+
+def get_hash(value: int, phf: ResultPHF) -> int:
+ """Retrieve a number given a value hash (``value``) and a phf instance (``ResultPHF``)"""
+
+ p = value * phf.a
+ return ((p ^ (p >> phf.r)) % phf.prime) % phf.n
diff --git a/python/src/hhat_lang/dialects/heather/code/mlir_builder/__init__.py b/python/src/hhat_lang/core/compiler/__init__.py
similarity index 100%
rename from python/src/hhat_lang/dialects/heather/code/mlir_builder/__init__.py
rename to python/src/hhat_lang/core/compiler/__init__.py
diff --git a/python/src/hhat_lang/core/compiler/core.py b/python/src/hhat_lang/core/compiler/core.py
new file mode 100644
index 00000000..78c72240
--- /dev/null
+++ b/python/src/hhat_lang/core/compiler/core.py
@@ -0,0 +1,22 @@
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from typing import Any
+
+
+class BaseCompiler(ABC):
+ """
+ Abstract class for the compiler that holds all compilation information, such
+ as: the list of other compilers used (classical, for other dialects, and
+ quantum), the list of executors (that evaluate the IR code for classical
+ and quantum) the compiler can use, quantum specs for available devices,
+ backends, quantum languages
+ """
+
+ @abstractmethod
+ def parse(self) -> Any:
+ raise NotImplementedError()
+
+ @abstractmethod
+ def evaluate(self) -> Any:
+ raise NotImplementedError()
diff --git a/python/src/hhat_lang/core/data/core.py b/python/src/hhat_lang/core/data/core.py
index b7f42db8..fa23edf4 100644
--- a/python/src/hhat_lang/core/data/core.py
+++ b/python/src/hhat_lang/core/data/core.py
@@ -1,6 +1,8 @@
from __future__ import annotations
+import struct
from enum import Enum, auto
+from functools import lru_cache
from typing import Any, Iterable
ACCEPTABLE_VALUES: dict = {
@@ -18,7 +20,7 @@
class InvalidType:
- """It just exists to be used as 'default' instance for the `ACCEPTABLE_VALUES` above."""
+ """It just exists to be used as 'default' instance for the ``ACCEPTABLE_VALUES`` above."""
pass
@@ -35,48 +37,35 @@ class WorkingData:
"""
_value: str
- _type: str
+ _type: str | tuple[str, ...]
_is_quantum: bool
_suppress_type: bool
+ _hash_value: int
+ __slots__ = ("_value", "_type", "_is_quantum", "_suppress_type", "_hash_value")
+
+ def __init__(self):
+ self._hash_value = hash((self.value, self.type))
@property
def value(self) -> str:
return self._value
@property
- def type(self) -> str:
+ def type(self) -> str | tuple[str, ...]:
return self._type
@property
def is_quantum(self) -> bool:
return self._is_quantum
- def _op_bitwise(self, op: str, other: Any) -> bool:
- if isinstance(other, self.__class__):
- return getattr(self.value, op)(other.value)
-
- if isinstance(other, ACCEPTABLE_VALUES.get(self._type, InvalidType)):
- return getattr(self.value, op)(other)
-
- return False
-
def __hash__(self) -> int:
- return hash((self.value, self.type))
+ return self._hash_value
def __eq__(self, other: Any) -> bool:
- return self._op_bitwise("__eq__", other)
-
- def __le__(self, other) -> bool:
- return self._op_bitwise("__le__", other)
-
- def __ge__(self, other) -> bool:
- return self._op_bitwise("__ge__", other)
-
- def __lt__(self, other) -> bool:
- return self._op_bitwise("__lt__", other)
+ if isinstance(other, self.__class__):
+ return self.value == other.value and self.type == other.type
- def __ne__(self, other) -> bool:
- return self._op_bitwise("__ne__", other)
+ return False
def __repr__(self) -> str:
type_txt = "" if self.type is None or self._suppress_type else f":{self.type}"
@@ -95,6 +84,25 @@ class CompositeWorkingData:
_group_type: CompositeGroup
_is_quantum: bool
_suppress_type: bool
+ _hash_value: int
+ __slots__ = (
+ "_group",
+ "_type",
+ "_group_type",
+ "_is_quantum",
+ "_suppress_type",
+ "_hash_value",
+ )
+
+ def __init__(self):
+ self._hash_value = hash(
+ (
+ hash(self._group),
+ hash(self._type),
+ hash(self._group_type),
+ hash(self._is_quantum),
+ )
+ )
@property
def value(self) -> tuple[str, ...]:
@@ -123,14 +131,14 @@ def __eq__(self, other: Any) -> bool:
return False
def __hash__(self) -> int:
- return hash((self._group, self._type, self._group_type, self._is_quantum))
+ return self._hash_value
def __iter__(self) -> Iterable:
- yield from self._group
+ return iter(self._group)
def __repr__(self) -> str:
txt = "" if self.type is None or self._suppress_type else f":{self.type}"
- return " ".join(str(k) for k in self._group) + f"{txt}"
+ return ".".join(str(k) for k in self._group) + f"{txt}"
class Symbol(WorkingData):
@@ -140,9 +148,10 @@ class Symbol(WorkingData):
def __init__(self, value: str, symbol_type: str | None = None):
self._value = value
- self._type = symbol_type or "str"
- self._is_quantum = True if value.startswith("@") else False
+ self._type = symbol_type or "`symbol"
+ self._is_quantum = value.startswith("@")
self._suppress_type = True
+ super().__init__()
class CompositeSymbol(CompositeWorkingData):
@@ -154,8 +163,9 @@ def __init__(self, value: tuple[str, ...]):
self._group = value
self._type = "str"
self._group_type = CompositeGroup.SymbolAttrs
- self._is_quantum = True if all(k.startswith("@") for k in value) else False
+ self._is_quantum = value[-1].startswith("@")
self._suppress_type = True
+ super().__init__()
class Atomic(Symbol):
@@ -171,27 +181,79 @@ class CoreLiteral(WorkingData):
Any defined literal by the dialect.
"""
- def __init__(self, value: str, lit_type: str):
- if (value.startswith("@") and not lit_type.startswith("@")) or (
- not value.startswith("@") and lit_type.startswith("@")
- ):
- raise ValueError(
- f"Literal got incompatible {value} value and type {lit_type}."
- )
+ def __init__(self, value: str, lit_type: str | tuple[str, ...]):
+ match lit_type:
+ case str():
+ if (value.startswith("@") and not lit_type.startswith("@")) or (
+ not value.startswith("@") and lit_type.startswith("@")
+ ):
+ raise ValueError(
+ f"Literal got incompatible {value} value and type {lit_type}."
+ )
+ self._is_quantum = lit_type.startswith("@")
+
+ case tuple():
+ if (value.startswith("@") and not lit_type[-1].startswith("@")) or (
+ not value.startswith("@") and lit_type[-1].startswith("@")
+ ):
+ raise ValueError(
+ f"Literal got incompatible {value} value and type {lit_type}."
+ )
+ self._is_quantum = lit_type[-1].startswith("@")
self._value = value
self._type = lit_type
- self._is_quantum = True if lit_type.startswith("@") else False
self._suppress_type = False
- self._bin_form = bin(int(value.strip("@")))[2:]
+ super().__init__()
- @property
- def value(self) -> str:
- return self._value
+ @lru_cache
+ def transform_bin(self) -> str:
+ value: str
+
+ try:
+ # works if integer
+ value = bin(int(self.value.strip("@")))[2:]
+
+ except ValueError:
+ try:
+ # works if float
+ value = "".join(
+ f"{k:08b}" for k in struct.pack(">d", float(self.value.strip("@")))
+ )
+
+ except ValueError:
+ # works if string
+ value = "".join(f"{ord(s):08b}" for s in self.value)
+
+ return value
+
+ def _op_bitwise(self, op: str, other: Any) -> bool:
+ if isinstance(other, self.__class__):
+ return getattr(self.value, op)(other.value)
+
+ if isinstance(other, ACCEPTABLE_VALUES.get(self._type, InvalidType)):
+ return getattr(self.value, op)(other)
+
+ return False
+
+ # def __eq__(self, other: Any) -> bool:
+ # return self._op_bitwise("__eq__", other)
+
+ def __le__(self, other) -> bool:
+ return self._op_bitwise("__le__", other)
+
+ def __ge__(self, other) -> bool:
+ return self._op_bitwise("__ge__", other)
+
+ def __lt__(self, other) -> bool:
+ return self._op_bitwise("__lt__", other)
+
+ def __ne__(self, other) -> bool:
+ return self._op_bitwise("__ne__", other)
@property
def bin(self) -> str:
- return self._bin_form
+ return self.transform_bin()
class CompositeLiteral(CompositeWorkingData):
diff --git a/python/src/hhat_lang/core/data/fn_def.py b/python/src/hhat_lang/core/data/fn_def.py
new file mode 100644
index 00000000..59e607be
--- /dev/null
+++ b/python/src/hhat_lang/core/data/fn_def.py
@@ -0,0 +1,105 @@
+from __future__ import annotations
+
+from functools import lru_cache
+from typing import Sized, cast
+
+from hhat_lang.core.code.base import BaseFnCheck, BaseIRBlock
+from hhat_lang.core.data.core import (
+ CompositeSymbol,
+ CompositeWorkingData,
+ Symbol,
+ WorkingData,
+)
+
+
+class FnDef:
+ """
+ Function definition class
+ """
+
+ _name: Symbol | CompositeSymbol
+ _type: Symbol | CompositeSymbol
+ _body: BaseIRBlock
+ _fn_check: BaseFnCheck
+ _args: BaseIRBlock
+ """
+ function definition arguments must be a special kind of IRBlock
+ that has ``arg`` and ``value`` attributes and is iterable through
+ them.
+ """
+
+ def __init__(
+ self,
+ fn_name: Symbol | CompositeSymbol,
+ fn_args: BaseIRBlock,
+ fn_body: BaseIRBlock,
+ fn_type: Symbol | CompositeSymbol | None = None,
+ ):
+ if (
+ isinstance(fn_name, Symbol | CompositeSymbol)
+ and isinstance(fn_args, BaseIRBlock)
+ and isinstance(fn_body, BaseIRBlock)
+ and isinstance(fn_type, Symbol | CompositeSymbol)
+ or fn_type is None
+ ):
+ self._name = fn_name
+ self._args = self._unwrap_args(cast(Sized, fn_args))
+ self._body = fn_body
+ self._type = fn_type or Symbol("null")
+ self._fn_check = BaseFnCheck(fn_name=self.name, args_types=self.arg_values)
+
+ else:
+ raise ValueError(
+ f"some fn definition type is wrong: "
+ f"{type(fn_name)} {type(fn_args)} {type(fn_body)} {type(fn_body)}"
+ )
+
+ @property
+ def name(self) -> Symbol | CompositeSymbol:
+ return self._name
+
+ @property
+ def type(self) -> Symbol | CompositeSymbol:
+ return self._type
+
+ @property
+ def args(self) -> BaseIRBlock:
+ return self._args
+
+ @property
+ def body(self) -> BaseIRBlock:
+ return self._body
+
+ @property
+ @lru_cache
+ def arg_names(self) -> tuple[WorkingData | CompositeWorkingData, ...]:
+ if hasattr(self._args, "args"):
+ return self._args.args # type: ignore [return-value]
+
+ raise ValueError(f"wrong arg names from function definition {self._name}")
+
+ @property
+ @lru_cache
+ def arg_values(self) -> tuple[Symbol | CompositeSymbol | BaseIRBlock, ...]:
+ if hasattr(self._args, "values"):
+ return self._args.values
+
+ raise ValueError(f"wrong arg values from function definition {self._name}")
+
+ @property
+ def fn_check(self) -> BaseFnCheck:
+ return self._fn_check
+
+ def _unwrap_args(self, args: Sized) -> BaseIRBlock:
+ if len(args) == 1:
+ if hasattr(args, "args"):
+ if hasattr(args.args[0], "values"):
+ return args.args[0]
+
+ raise ValueError("function definition must contain arg-value pairs")
+
+ def __repr__(self) -> str:
+ args = " ".join(str(k) for k in self.args)
+ fn_header = f"FN-DEF NAME[{self.name}] ARGS[{args}] TYPE[{self.type or 'null'}]"
+ body = "\n ".join(str(k) for k in self.body)
+ return f"{fn_header}" + "\n " + f"{body}" + "\n"
diff --git a/python/src/hhat_lang/core/data/utils.py b/python/src/hhat_lang/core/data/utils.py
index b8a08aa1..2158808c 100644
--- a/python/src/hhat_lang/core/data/utils.py
+++ b/python/src/hhat_lang/core/data/utils.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+from abc import ABC
from enum import Enum, auto
from typing import Any
@@ -13,7 +14,7 @@ class VariableKind(Enum):
def isquantum(data: Any) -> bool:
if isinstance(data, str):
- return True if data.startswith("@") else False
+ return data.startswith("@")
if hasattr(data, "is_quantum"):
return data.is_quantum
@@ -29,3 +30,7 @@ def has_same_paradigm(data1: Any, data2: Any) -> bool:
return True
return False
+
+
+class AbstractDataContainer(ABC):
+ """Abstract data container. To prevent circular imports"""
diff --git a/python/src/hhat_lang/core/data/variable.py b/python/src/hhat_lang/core/data/variable.py
index 973615e5..a7e83207 100644
--- a/python/src/hhat_lang/core/data/variable.py
+++ b/python/src/hhat_lang/core/data/variable.py
@@ -3,8 +3,8 @@
from abc import ABC, abstractmethod
from typing import Any, Iterable
-from hhat_lang.core.data.core import WorkingData, Symbol
-from hhat_lang.core.data.utils import isquantum, VariableKind
+from hhat_lang.core.data.core import CompositeSymbol, CoreLiteral, Symbol, WorkingData
+from hhat_lang.core.data.utils import AbstractDataContainer, VariableKind, isquantum
from hhat_lang.core.error_handlers.errors import (
ContainerVarError,
ContainerVarIsImmutableError,
@@ -13,17 +13,21 @@
VariableFreeingBorrowedError,
VariableWrongMemberError,
)
+from hhat_lang.core.types.utils import AbstractDataTypeStructure, BaseTypeEnum
from hhat_lang.core.utils import SymbolOrdered
-class BaseDataContainer(ABC):
+class BaseDataContainer(AbstractDataContainer):
"""Data container for constant and variables definitions."""
- _name: Symbol
- _type: Symbol
+ _name: Symbol | CompositeSymbol
+ _type: Symbol | CompositeSymbol
_ds: SymbolOrdered
"""_ds: data from data structure, e.g. member types and names"""
+ _ds_type: BaseTypeEnum
+ """_ds_type: type enum from the data structure"""
+
_data: SymbolOrdered
"""_data: where data will actually be stored"""
@@ -35,8 +39,8 @@ class BaseDataContainer(ABC):
_instr_counter: int
"""the counter for instructions so quantum instructions can be
- processed in ordered fashion; especially useful for quantum or
- appendable variables types. For instance, can be used on remote
+ processed in ordered fashion; especially useful for quantum or
+ appendable variables types. For instance, can be used on remote
instructions for teleportation"""
_transferred: bool
@@ -49,12 +53,12 @@ class BaseDataContainer(ABC):
"""if the data is borrowed somewhere else"""
@property
- def name(self) -> Symbol:
+ def name(self) -> Symbol | CompositeSymbol:
"""name of the variable"""
return self._name
@property
- def type(self) -> Symbol:
+ def type(self) -> Symbol | CompositeSymbol:
"""type of the variable"""
return self._type
@@ -108,11 +112,84 @@ def _check_array_prop(cls, data: Any):
return False
- def _check_assign_ds_vals(
+ @staticmethod
+ def _get_single_ds_member(
+ attr: Symbol | CompositeSymbol,
+ ) -> Symbol | CompositeSymbol:
+ return attr
+
+ def _get_struct_ds_member(
+ self, attr: Symbol | CompositeSymbol
+ ) -> Symbol | CompositeSymbol:
+ return self._ds[attr]
+
+ def _get_correct_ds_member(
+ self, attr: Symbol | CompositeSymbol
+ ) -> Symbol | CompositeSymbol:
+ if self._ds_type is BaseTypeEnum.SINGLE:
+ return self._get_single_ds_member(attr)
+
+ if self._ds_type is BaseTypeEnum.STRUCT:
+ return self._get_struct_ds_member(attr)
+
+ raise NotImplementedError()
+
+ def _get_data_type(self, data: WorkingData) -> Symbol | CompositeSymbol:
+ match data:
+ case Symbol():
+ return data
+
+ case CoreLiteral():
+ return (
+ Symbol(data.type)
+ if isinstance(data.type, str)
+ else CompositeSymbol(data.type)
+ )
+
+ case _:
+ raise NotImplementedError()
+
+ def _check_and_assign_enum_val(
+ self, data: Symbol | CompositeSymbol | BaseDataContainer
+ ) -> bool:
+ """
+ Check data from enum type and assign to variable container.
+
+ Args:
+ - data: enum member as a ``Symbol``, ``CompositeSymbol`` or a struct data
+
+ Returns:
+ ``True`` if successfully checked and assigned enum data to variable container.
+ """
+
+ match data:
+ case Symbol():
+ if data in self._ds:
+ self._data[0] = data
+ return True
+
+ case CompositeSymbol():
+ if Symbol(data.value[-1]) in self._ds:
+ self._data[0] = Symbol(data.value[-1])
+ return True
+
+ case AbstractDataTypeStructure() | BaseDataContainer():
+ if data.name in self._ds:
+ self._data[0] = data
+ return True
+
+ raise NotImplementedError()
+
+ case _:
+ print(f"{type(data)}")
+ raise NotImplementedError()
+
+ return False
+
+ def _check_and_assign_ds_vals(
self,
data: Any,
- attr_type: Symbol,
- # tmp_container: SymbolOrdered
+ attr_type: Symbol | CompositeSymbol,
) -> bool:
"""
Check data structure when passing values only (equivalent to `fn(*args)`) and
@@ -127,13 +204,12 @@ def _check_assign_ds_vals(
False if there is no attribute type. Otherwise, true.
"""
- if data.type == attr_type:
+ data_type = self._get_data_type(data)
+ if data_type == self._get_correct_ds_member(attr_type):
# is quantum or array data structure
if data.is_quantum or self._check_array_prop(data):
-
if attr_type in self._ds:
-
if attr_type in self._data:
self._data[attr_type].append(data)
@@ -151,11 +227,10 @@ def _check_assign_ds_vals(
return False
- def _check_assign_ds_args_vals(
+ def _check_and_assign_ds_args_vals(
self,
key: Symbol,
value: Any,
- # tmp_container: SymbolOrdered
) -> bool:
"""
Check data structure when passing args and values, (equivalent to `fn(**args)`).
@@ -168,10 +243,8 @@ def _check_assign_ds_args_vals(
"""
if key in self._ds:
-
# is quantum or array data structure
if key.is_quantum or self._check_array_prop(value):
-
if key in self._data:
self._data[key].append(value)
@@ -190,30 +263,37 @@ def _check_assign_ds_args_vals(
return False
@abstractmethod
- def assign(self, *args: Any, **kwargs: Any) -> None | ErrorHandler:
- ...
+ def assign(self, *args: Any, **kwargs: Any) -> BaseDataContainer | ErrorHandler:
+ """Assign a data to the variable container"""
+
+ raise NotImplementedError()
@abstractmethod
def get(self, *args: Any, **kwargs: Any) -> Any | ErrorHandler:
- ...
+ """Retrieve variable content"""
+
+ raise NotImplementedError()
def __call__(
self,
*args: Any,
- **kwargs: SymbolOrdered[WorkingData, WorkingData | BaseDataContainer],
- ) -> None | ErrorHandler:
+ **kwargs: SymbolOrdered,
+ ) -> BaseDataContainer | ErrorHandler:
return self.assign(*args, **kwargs)
def __iter__(self) -> Iterable:
- yield from self._data.items()
+ return iter(self._data.items())
+
+ def __repr__(self) -> str:
+ return f"{self.name}"
@abstractmethod
def borrow(self, *args: Any, **kwargs: Any) -> None | ErrorHandler:
- ...
+ raise NotImplementedError()
@abstractmethod
def transfer(self, *args: Any, **kwargs: Any) -> None | ErrorHandler:
- ...
+ raise NotImplementedError()
def free(self) -> None | ErrorHandler:
"""Freeing the container (program going out of container's scope)."""
@@ -234,45 +314,52 @@ class VariableTemplate:
def __new__(
cls,
- var_name: Symbol,
- type_name: Symbol,
- type_ds: SymbolOrdered,
+ var_name: Symbol | CompositeSymbol,
+ type_name: Symbol | CompositeSymbol,
+ ds_data: SymbolOrdered,
+ ds_type: BaseTypeEnum,
flag: VariableKind = VariableKind.IMMUTABLE,
) -> BaseDataContainer | ErrorHandler:
-
# quantum variables are, at least for now, always appendable and thus mutable
if isquantum(var_name) and isquantum(type_name):
- return AppendableVariable(var_name, type_name, type_ds, True)
+ return AppendableVariable(var_name, type_name, ds_data, ds_type, True)
if not isquantum(var_name) and not isquantum(type_name):
-
match flag:
-
# constant, at least for now, cannot be quantum
case VariableKind.CONSTANT:
- return ConstantData(var_name, type_name, type_ds)
+ return ConstantData(var_name, type_name, ds_data, ds_type)
case VariableKind.APPENDABLE:
- return AppendableVariable(var_name, type_name, type_ds, False)
+ return AppendableVariable(
+ var_name, type_name, ds_data, ds_type, False
+ )
case VariableKind.MUTABLE:
- return MutableVariable(var_name, type_name, type_ds)
+ return MutableVariable(var_name, type_name, ds_data, ds_type)
case VariableKind.IMMUTABLE:
- return ImmutableVariable(var_name, type_name, type_ds)
+ return ImmutableVariable(var_name, type_name, ds_data, ds_type)
# default for now is immutable
case _:
- return ImmutableVariable(var_name, type_name, type_ds)
+ return ImmutableVariable(var_name, type_name, ds_data, ds_type)
return VariableCreationError(var_name, type_name)
class ConstantData(BaseDataContainer):
- def __init__(self, var_name: Symbol, type_name: Symbol, type_ds: SymbolOrdered):
+ def __init__(
+ self,
+ var_name: Symbol | CompositeSymbol,
+ type_name: Symbol | CompositeSymbol,
+ ds_data: SymbolOrdered,
+ ds_type: BaseTypeEnum,
+ ):
self._name = var_name
self._type = type_name
- self._ds = type_ds
+ self._ds = ds_data
+ self._ds_type = ds_type
self._data = SymbolOrdered()
self._assigned = False
self._is_constant = True
@@ -289,7 +376,7 @@ def assign(self, *args: Any, **kwargs: Any) -> None | ErrorHandler:
raise NotImplementedError()
def get(self, member: Symbol | None = None) -> Any | ErrorHandler:
- member = next(iter(self._ds.keys())) if member is None else member
+ member = member or next(iter(self._ds.keys()))
if member in self._data:
return self._data[member]
@@ -304,10 +391,17 @@ def transfer(self, *args: Any, **kwargs: Any) -> None | ErrorHandler:
class ImmutableVariable(BaseDataContainer):
- def __init__(self, var_name: Symbol, type_name: Symbol, type_ds: SymbolOrdered):
+ def __init__(
+ self,
+ var_name: Symbol | CompositeSymbol,
+ type_name: Symbol | CompositeSymbol,
+ ds_data: SymbolOrdered,
+ ds_type: BaseTypeEnum,
+ ):
self._name = var_name
self._type = type_name
- self._ds = type_ds
+ self._ds = ds_data
+ self._ds_type = ds_type
self._data = SymbolOrdered()
self._assigned = False
self._is_constant = False
@@ -322,32 +416,34 @@ def __init__(self, var_name: Symbol, type_name: Symbol, type_ds: SymbolOrdered):
def assign(
self,
*args: Any,
- **kwargs: SymbolOrdered[WorkingData, WorkingData | BaseDataContainer],
- ) -> None | ErrorHandler:
-
+ **kwargs: SymbolOrdered,
+ ) -> ImmutableVariable | ErrorHandler:
if not self._assigned:
+ if self._ds_type is BaseTypeEnum.ENUM:
+ self._check_and_assign_enum_val(args[0])
+ return self
if len(args) == len(self._ds):
-
for k, d in zip(args, self._ds):
-
- if not self._check_assign_ds_vals(k, d):
+ if not self._check_and_assign_ds_vals(k, d):
return ContainerVarError(self.name)
elif len(kwargs) == len(self._ds):
-
for k, v in kwargs.items():
-
- if not self._check_assign_ds_args_vals(Symbol(k), v):
+ if not self._check_and_assign_ds_args_vals(Symbol(k), v):
return ContainerVarError(self.name)
self._assigned = True
- return None
+ return self
return ContainerVarIsImmutableError(self.name)
def get(self, member: Symbol | None = None) -> Any | ErrorHandler:
- member = next(iter(self._ds.keys())) if member is None else member
+ if self._ds_type == BaseTypeEnum.ENUM:
+ # enum type only have a single data stored from its members
+ return self._data[0]
+
+ member = member or next(iter(self._ds.keys()))
if member in self._data:
return self._data[member]
@@ -364,13 +460,15 @@ def transfer(self, *args: Any, **kwargs: Any) -> None | ErrorHandler:
class MutableVariable(BaseDataContainer):
def __init__(
self,
- var_name: Symbol,
- type_name: Symbol,
- type_ds: SymbolOrdered,
+ var_name: Symbol | CompositeSymbol,
+ type_name: Symbol | CompositeSymbol,
+ ds_data: SymbolOrdered,
+ ds_type: BaseTypeEnum,
):
self._name = var_name
self._type = type_name
- self._ds = type_ds
+ self._ds = ds_data
+ self._ds_type = ds_type
self._data = SymbolOrdered()
self._assigned = False
self._is_constant = False
@@ -384,27 +482,35 @@ def __init__(
def assign(
self, *args: Any, **kwargs: dict[WorkingData, WorkingData | BaseDataContainer]
- ) -> None | ErrorHandler:
+ ) -> MutableVariable | ErrorHandler:
+ if self._ds_type == BaseTypeEnum.ENUM:
+ self._check_and_assign_enum_val(args[0])
+ return self
if len(args) == len(self._ds):
-
for k, d in zip(args, self._ds):
-
- if not self._check_assign_ds_vals(k, d):
+ if not self._check_and_assign_ds_vals(k, d):
return ContainerVarError(self.name)
elif len(kwargs) == len(self._ds):
-
for k, v in kwargs.items():
+ k = Symbol("@" + k[3:]) if k.startswith("q__") else Symbol(k)
+
+ if k in self._ds:
+ if not self._check_and_assign_ds_args_vals(k, v):
+ return ContainerVarError(self.name)
- if not self._check_assign_ds_args_vals(Symbol(k), v):
+ else:
return ContainerVarError(self.name)
self._assigned = True
- return None
+ return self
def get(self, member: Symbol | None = None) -> Any | ErrorHandler:
- member = next(iter(self._ds.keys())) if member is None else member
+ if self._ds_type == BaseTypeEnum.ENUM:
+ return self._data[0]
+
+ member = member or next(iter(self._ds.keys()))
if member in self._data:
return self._data[member]
@@ -421,14 +527,16 @@ def transfer(self, *args: Any, **kwargs: Any) -> None | ErrorHandler:
class AppendableVariable(BaseDataContainer):
def __init__(
self,
- var_name: Symbol,
- type_name: Symbol,
- type_ds: SymbolOrdered,
+ var_name: Symbol | CompositeSymbol,
+ type_name: Symbol | CompositeSymbol,
+ ds_data: SymbolOrdered,
+ ds_type: BaseTypeEnum,
is_quantum: bool,
):
self._name = var_name
self._type = type_name
- self._ds = type_ds
+ self._ds = ds_data
+ self._ds_type = ds_type
self._data = SymbolOrdered()
self._assigned = False
self._is_constant = False
@@ -443,28 +551,32 @@ def __init__(
def assign(
self,
*args: Any,
- **kwargs: SymbolOrdered[WorkingData, WorkingData | BaseDataContainer],
- ) -> None | ErrorHandler:
+ **kwargs: SymbolOrdered,
+ ) -> AppendableVariable | ErrorHandler:
+ if self._ds_type == BaseTypeEnum.ENUM:
+ self._check_and_assign_enum_val(args[0])
+ return self
if len(args) == len(self._ds):
-
for k, d in zip(args, self._ds):
-
- if not self._check_assign_ds_vals(k, d):
+ if not self._check_and_assign_ds_vals(k, d):
return ContainerVarError(self.name)
elif len(kwargs) == len(self._ds):
-
for k, v in kwargs.items():
+ k = Symbol("@" + k[3:]) if k.startswith("q__") else Symbol(k)
- if not self._check_assign_ds_args_vals(Symbol(k), v):
+ if not self._check_and_assign_ds_args_vals(k, v):
return ContainerVarError(self.name)
self._assigned = True
- return None
+ return self
def get(self, member: Symbol | None = None) -> Any | ErrorHandler:
- member = next(iter(self._ds.keys())) if member is None else member
+ if self._ds_type == BaseTypeEnum.ENUM:
+ return self._data[0]
+
+ member = member or next(iter(self._ds.keys()))
if member in self._data:
return self._data[member]
diff --git a/python/src/hhat_lang/core/error_handlers/errors.py b/python/src/hhat_lang/core/error_handlers/errors.py
index 50d0798c..c88239bb 100644
--- a/python/src/hhat_lang/core/error_handlers/errors.py
+++ b/python/src/hhat_lang/core/error_handlers/errors.py
@@ -2,8 +2,7 @@
from abc import ABC, abstractmethod
from enum import Enum, auto
-
-from hhat_lang.core.data.core import Symbol, WorkingData
+from typing import Any
class ErrorCodes(Enum):
@@ -31,19 +30,25 @@ class ErrorCodes(Enum):
CAST_INT_OVERFLOW_ERROR = auto()
CAST_ERROR = auto()
+ FUNCTION_WRONG_ARGS_TYPES_ERROR = auto()
+
+ STACK_FRAME_GET_ERROR = auto()
+ STACK_FRAME_NOT_FN_ERROR = auto()
STACK_EMPTY_ERROR = auto()
STACK_OVERFLOW_ERROR = auto()
HEAP_INVALID_KEY_ERROR = auto()
HEAP_EMPTY_ERROR = auto()
+ SYMBOLTABLE_INVALID_KEY_ERROR = auto()
+
INVALID_QUANTUM_COMPUTED_RESULT = auto()
INSTR_NOTFOUND_ERROR = auto()
INSTR_STATUS_ERROR = auto()
-class ErrorHandler(ABC):
+class ErrorHandler(BaseException, ABC):
def __init__(self, error_code: ErrorCodes):
self.err_code = error_code
@@ -80,7 +85,7 @@ def __call__(self) -> str:
class IndexVarHasIndexesError(ErrorHandler):
- def __init__(self, var_name: WorkingData | str):
+ def __init__(self, var_name: Any):
self._var = var_name
super().__init__(ErrorCodes.INDEX_VAR_HAS_INDEXES_ERROR)
@@ -89,7 +94,7 @@ def __call__(self) -> str:
class IndexInvalidVarError(ErrorHandler):
- def __init__(self, var_name: WorkingData | str):
+ def __init__(self, var_name: Any):
self._var = var_name
super().__init__(ErrorCodes.INDEX_INVALID_VAR_ERROR)
@@ -100,7 +105,7 @@ def __call__(self) -> str:
class TypeQuantumOnClassicalError(ErrorHandler):
"""Cannot have quantum data inside classical data type. The opposite is valid."""
- def __init__(self, q: WorkingData, c: WorkingData):
+ def __init__(self, q: Any, c: Any):
super().__init__(ErrorCodes.TYPE_QUANTUM_ON_CLASSICAL_ERROR)
self._q = q
self._c = c
@@ -112,7 +117,7 @@ def __call__(self) -> str:
class TypeAndMemberNoMatchError(ErrorHandler):
- def __init__(self, m_type: WorkingData, m_member: WorkingData):
+ def __init__(self, m_type: Any, m_member: Any):
super().__init__(ErrorCodes.TYPE_AND_MEMBER_NO_MATCH)
self.m_type = m_type
self.m_member = m_member
@@ -125,7 +130,7 @@ def __call__(self) -> str:
class TypeAddMemberError(ErrorHandler):
- def __init__(self, member_name: WorkingData):
+ def __init__(self, member_name: Any):
self._member = member_name
super().__init__(ErrorCodes.TYPE_ADD_MEMBER_ERROR)
@@ -134,7 +139,7 @@ def __call__(self) -> str:
class TypeSingleError(ErrorHandler):
- def __init__(self, type_name: WorkingData):
+ def __init__(self, type_name: Any):
super().__init__(ErrorCodes.TYPE_SINGLE_ASSIGN_ERROR)
self._type_name = type_name
@@ -146,7 +151,7 @@ def __call__(self) -> str:
class TypeStructError(ErrorHandler):
- def __init__(self, type_name: WorkingData):
+ def __init__(self, type_name: Any):
super().__init__(ErrorCodes.TYPE_STRUCT_ASSIGN_ERROR)
self._type_name = type_name
@@ -158,7 +163,7 @@ def __call__(self) -> str:
class TypeUnionError(ErrorHandler):
- def __init__(self, type_name: WorkingData):
+ def __init__(self, type_name: Any):
super().__init__(ErrorCodes.TYPE_UNION_ASSIGN_ERROR)
self._type_name = type_name
@@ -170,7 +175,7 @@ def __call__(self) -> str:
class TypeEnumError(ErrorHandler):
- def __init__(self, type_name: WorkingData):
+ def __init__(self, type_name: Any):
super().__init__(ErrorCodes.TYPE_ENUM_ASSIGN_ERROR)
self._type_name = type_name
@@ -182,19 +187,17 @@ def __call__(self) -> str:
class ContainerVarError(ErrorHandler):
- def __init__(self, var_name: WorkingData):
+ def __init__(self, var_name: Any):
super().__init__(ErrorCodes.CONTAINER_VAR_ASSIGN_ERROR)
self._var_name = var_name
def __call__(self) -> str:
- return (
- f"[[{self.__class__.__name__}]]: Error assigning value "
- f"to variable '{self._var_name}'"
- )
+ name = self.__class__.__name__
+ return f"[[{name}]]: Error assigning value to variable '{self._var_name}'"
class ContainerVarIsImmutableError(ErrorHandler):
- def __init__(self, var_name: WorkingData):
+ def __init__(self, var_name: Any):
super().__init__(ErrorCodes.CONTAINER_VAR_IS_IMMUTABLE_ERROR)
self._var_name = var_name
@@ -205,7 +208,7 @@ def __call__(self) -> str:
class VariableWrongMemberError(ErrorHandler):
- def __init__(self, var_name: WorkingData):
+ def __init__(self, var_name: Any):
super().__init__(ErrorCodes.VARIABLE_WRONG_MEMBER_ERROR)
self._var_name = var_name
@@ -214,7 +217,7 @@ def __call__(self) -> str:
class VariableCreationError(ErrorHandler):
- def __init__(self, var_name: WorkingData, var_type: WorkingData):
+ def __init__(self, var_name: Any, var_type: Any):
super().__init__(ErrorCodes.VARIABLE_CREATION_ERROR)
self._var_name = var_name
self._var_type = var_type
@@ -227,7 +230,7 @@ def __call__(self) -> str:
class VariableFreeingBorrowedError(ErrorHandler):
- def __init__(self, var_name: WorkingData):
+ def __init__(self, var_name: Any):
super().__init__(ErrorCodes.VARIABLE_FREEING_BORROWED_ERROR)
self._var_name = var_name
@@ -239,7 +242,7 @@ def __call__(self) -> str:
class CastNegToUnsignedError(ErrorHandler):
- def __init__(self, neg_value: WorkingData, unsigned_value: WorkingData):
+ def __init__(self, neg_value: Any, unsigned_value: Any):
super().__init__(ErrorCodes.CAST_NEG_TO_UNSIGNED_ERROR)
self._neg_value = neg_value
self._unsigned_value = unsigned_value
@@ -252,7 +255,7 @@ def __call__(self) -> str:
class CastIntOverflowError(ErrorHandler):
- def __init__(self, int_value: WorkingData, limit: Symbol):
+ def __init__(self, int_value: Any, limit: Any):
super().__init__(ErrorCodes.CAST_INT_OVERFLOW_ERROR)
self._int_value = int_value
self._limit = limit
@@ -265,7 +268,7 @@ def __call__(self) -> str:
class CastError(ErrorHandler):
- def __init__(self, type_cast: Symbol, data: WorkingData):
+ def __init__(self, type_cast: Any, data: Any):
super().__init__(ErrorCodes.CAST_ERROR)
self._type_cast = type_cast
self._data = data
@@ -274,24 +277,53 @@ def __call__(self) -> str:
return f"[[{self.__class__.__name__}]]: Cannot cast {self._data} into {self._type_cast}."
-class StackEmptyError(ErrorHandler):
+class FnWrongArgsTypesError(ErrorHandler):
+ def __init__(self, values: Any, expected: Any):
+ self._values = values
+ self._expected = expected
+ super().__init__(ErrorCodes.FUNCTION_WRONG_ARGS_TYPES_ERROR)
+
+ def __call__(self) -> str:
+ return (
+ f"[[{self.__class__.__name__}]]: wrong args types; expected {self._expected},"
+ f" but got {self._values}."
+ )
+
+
+class StackFrameGetError(ErrorHandler):
+ def __init__(self, data: Any):
+ self._data = data
+ super().__init__(ErrorCodes.STACK_FRAME_GET_ERROR)
+
+ def __call__(self) -> str:
+ return f"[[{self.__class__.__name__}]]: Stack frame could not retrieve data {self._data}."
+
+
+class StackFrameNotFnError(ErrorHandler):
def __init__(self):
- super().__init__(ErrorCodes.STACK_EMPTY_ERROR)
+ super().__init__(ErrorCodes.STACK_FRAME_NOT_FN_ERROR)
def __call__(self) -> str:
return (
- f"[[{self.__class__.__name__}]]: Stack is empty."
+ f"[[{self.__class__.__name__}]]: Stack frame is not defined for functions,"
+ f" but tried to used as if."
)
+class StackEmptyError(ErrorHandler):
+ def __init__(self):
+ super().__init__(ErrorCodes.STACK_EMPTY_ERROR)
+
+ def __call__(self) -> str:
+ return f"[[{self.__class__.__name__}]]: Stack is empty."
+
+
class StackOverflowError(ErrorHandler):
def __init__(self):
super().__init__(ErrorCodes.STACK_OVERFLOW_ERROR)
def __call__(self) -> str:
- return (
- f"[[{self.__class__.__name__}]]: Stack overflow."
- )
+ return f"[[{self.__class__.__name__}]]: Stack overflow."
class HeapEmptyError(ErrorHandler):
@@ -299,50 +331,58 @@ def __init__(self):
super().__init__(ErrorCodes.HEAP_EMPTY_ERROR)
def __call__(self) -> str:
- return (
- f"[[{self.__class__.__name__}]]: Heap is empty."
- )
+ return f"[[{self.__class__.__name__}]]: Heap is empty."
class HeapInvalidKeyError(ErrorHandler):
- def __init__(self, key: str | Symbol):
+ def __init__(self, key: Any):
super().__init__(ErrorCodes.HEAP_INVALID_KEY_ERROR)
self._key = key
def __call__(self) -> str:
- return (
- f"[[{self.__class__.__name__}]]: key '{self._key}' is invalid."
- )
+ return f"[[{self.__class__.__name__}]]: key '{self._key}' is invalid."
+
+
+class SymbolTableInvalidKeyError(ErrorHandler):
+ def __init__(self, key: Any, key_type: str):
+ super().__init__(ErrorCodes.SYMBOLTABLE_INVALID_KEY_ERROR)
+ self._key = key
+ self._key_type = key_type
+
+ @classmethod
+ def Type(cls) -> str:
+ return "type"
+
+ @classmethod
+ def Fn(cls) -> str:
+ return "fn"
+
+ def __call__(self) -> str:
+ return f"[[{self.__class__.__name__}]]: key '{self._key}' is invalid for {self._key_type}."
class InvalidQuantumComputedResult(ErrorHandler):
- def __init__(self, qdata: str | Symbol):
+ def __init__(self, qdata: Any):
super().__init__(ErrorCodes.INVALID_QUANTUM_COMPUTED_RESULT)
self._qdata = qdata
def __call__(self) -> str:
- return (
- f"[[{self.__class__.__name__}]]: quantum data {self._qdata} produced invalid result."
- )
+ return f"[[{self.__class__.__name__}]]: quantum data {self._qdata} produced invalid result."
class InstrNotFoundError(ErrorHandler):
- def __init__(self, name: str | Symbol):
+ def __init__(self, name: Any):
super().__init__(ErrorCodes.INSTR_NOTFOUND_ERROR)
self._name = name
def __call__(self) -> str:
- return (
- f"[[{self.__class__.__name__}]]: instr {self._name} not found"
- )
+ return f"[[{self.__class__.__name__}]]: instr {self._name} not found"
class InstrStatusError(ErrorHandler):
- def __init__(self, name: str | Symbol):
+ def __init__(self, name: Any):
super().__init__(ErrorCodes.INSTR_STATUS_ERROR)
self._name = name
def __call__(self) -> str:
- return (
- f"[[{self.__class__.__name__}]]: instr {self._name} has status error"
- )
+ return f"[[{self.__class__.__name__}]]: instr {self._name} has status error"
diff --git a/python/src/hhat_lang/core/execution/abstract_base.py b/python/src/hhat_lang/core/execution/abstract_base.py
index 11035b6d..fe992806 100644
--- a/python/src/hhat_lang/core/execution/abstract_base.py
+++ b/python/src/hhat_lang/core/execution/abstract_base.py
@@ -1,31 +1,126 @@
from __future__ import annotations
-from typing import Any
from abc import ABC, abstractmethod
+from typing import Any
-from hhat_lang.core.code.ir import TypeIR, BaseFnIR
+from hhat_lang.core.code.abstract import BaseIR
+from hhat_lang.core.code.new_ir import IRGraph
+from hhat_lang.core.data.core import CompositeSymbol, Symbol
from hhat_lang.core.memory.core import MemoryManager
-class BaseEvaluator(ABC):
- _mem: MemoryManager
- _type_table: TypeIR
- _fn_table: BaseFnIR
+class BaseIRManager(ABC):
+ """
+ To manage IR code in a graph way, where nodes are IR from files and edges are the
+ connection between them (uni- or bidirectional).
+ """
- @property
- def mem(self) -> MemoryManager:
- return self._mem
+ _graph: IRGraph
@property
- def type_table(self) -> TypeIR:
- return self._type_table
+ def ir_graph(self) -> IRGraph:
+ return self._graph
+
+ @abstractmethod
+ def add_ir(self, *args: Any, **kwargs: Any) -> Any:
+ """To add IR objects to the IR manager"""
+
+ raise NotImplementedError()
+
+ @abstractmethod
+ def link_ir(
+ self,
+ *refs: Symbol | CompositeSymbol,
+ ir_importing: BaseIR,
+ ir_imported: BaseIR,
+ **kwargs: Any,
+ ) -> Any:
+ """
+ To link IR objects. When a file (``A``, importing) imports types or functions from
+ another file (``B``, imported), a directed edge is created from ``A`` to ``B``, that
+ is ``A`` holds a reference to ``B``, but not the other way around.
+ """
+
+ raise NotImplementedError()
+
+ @abstractmethod
+ def update_ir(self, prev_ir: BaseIR, new_ir: BaseIR) -> Any:
+ """
+ Update IR object to a new one.
+ """
+
+ raise NotImplementedError()
+
+
+class BaseInterpreter(ABC):
+ """
+ An abstract execution class. The execution class must hold basic execution
+ attributes and functionalities, such as parsing and evaluating code.
+
+ Each execution object holds information regarding available quantum devices specs,
+ quantum target backend and its quantum language as well as their specs, and the H-hat
+ dialect specs to parse the code and to evaluate it.
+ """
+
+ _depth_counter: int
+ """to count code depth, for memory and scope management; it must be >= 0"""
@property
- def fn_table(self) -> BaseFnIR:
- return self._fn_table
+ def depth_counter(self) -> int:
+ """
+ To count code depth, especially on recursive function calls.
+
+ Returns:
+ The current integer of the depth counter
+ """
+
+ return self._depth_counter
+
+ def inc_depth_counter(self) -> None:
+ self._depth_counter += 1
+
+ def dec_depth_counter(self) -> None:
+ self._depth_counter -= 1
+ if self._depth_counter < 0:
+ raise ValueError("execution depth counter is < 0")
+
+ @abstractmethod
+ def parse(self, *args: Any, code: str, **kwargs: Any) -> Any:
+ """
+ Parsing the source code to some intermediate representation,
+ e.g. AST, IR, etc.
+ """
+
+ raise NotImplementedError()
+
+ @abstractmethod
+ def evaluate(self, *args: Any, **kwargs: Any) -> Any:
+ """
+ Evaluates the code using the evaluator instance defined by the
+ execution specs.
+ """
+
+ raise NotImplementedError()
+
+
+class BaseEvaluator(ABC):
+ """
+ An abstract evaluator class.
+ """
@abstractmethod
- def run(self, code: Any, **kwargs: Any) -> Any:
+ def run(self, *, code: Any, mem: MemoryManager, **kwargs: Any) -> Any:
+ """To run only once, when calling the evaluator to execute the code."""
+
+ raise NotImplementedError()
+
+ @abstractmethod
+ def walk(self, code: Any, mem: MemoryManager, **kwargs: Any) -> Any:
+ """
+ To run recursively and evaluate the code. Should not be called directly be
+ the user, but rather through `run` and internal methods.
+ """
+
raise NotImplementedError()
@abstractmethod
diff --git a/python/src/hhat_lang/core/execution/abstract_program.py b/python/src/hhat_lang/core/execution/abstract_program.py
index afe3ce89..a7d69cb7 100644
--- a/python/src/hhat_lang/core/execution/abstract_program.py
+++ b/python/src/hhat_lang/core/execution/abstract_program.py
@@ -1,22 +1,25 @@
+from __future__ import annotations
-from typing import Any
from abc import ABC, abstractmethod
+from typing import Any
-from hhat_lang.core.code.ir import BlockIR
+from hhat_lang.core.code.abstractr import BaseIRBlock
+from hhat_lang.core.code.symbol_table import SymbolTable
from hhat_lang.core.data.core import WorkingData
from hhat_lang.core.error_handlers.errors import ErrorHandler
from hhat_lang.core.execution.abstract_base import BaseEvaluator
-from hhat_lang.core.memory.core import IndexManager
from hhat_lang.core.lowlevel.abstract_qlang import BaseLowLevelQLang
+from hhat_lang.core.memory.core import IndexManager, Stack
class BaseProgram(ABC):
_qdata: WorkingData
_idx: IndexManager
- _block: BlockIR
+ _block: BaseIRBlock
_executor: BaseEvaluator
_qlang: BaseLowLevelQLang
+ _qstack: Stack
+ _symbol: SymbolTable
@abstractmethod
- def run(self) -> Any | ErrorHandler:
- ...
+ def run(self) -> Any | ErrorHandler: ...
diff --git a/python/src/hhat_lang/core/imports/__init__.py b/python/src/hhat_lang/core/imports/__init__.py
new file mode 100644
index 00000000..08d8b174
--- /dev/null
+++ b/python/src/hhat_lang/core/imports/__init__.py
@@ -0,0 +1,5 @@
+from __future__ import annotations
+
+from .importer import TypeImporter
+
+__all__ = ["TypeImporter"]
diff --git a/python/src/hhat_lang/core/imports/importer.py b/python/src/hhat_lang/core/imports/importer.py
new file mode 100644
index 00000000..c8c7f567
--- /dev/null
+++ b/python/src/hhat_lang/core/imports/importer.py
@@ -0,0 +1,213 @@
+from __future__ import annotations
+
+from abc import ABC
+from pathlib import Path
+from typing import Callable, Iterable, cast
+
+from arpeggio import ParserPython
+from arpeggio.cleanpeg import ParserPEG
+
+from hhat_lang.core.code.abstract import BaseIR
+from hhat_lang.core.code.base import BaseFnCheck
+from hhat_lang.core.code.new_ir import IRGraph
+from hhat_lang.core.data.core import CompositeSymbol, Symbol
+from hhat_lang.toolchain.project import SOURCE_FOLDER_NAME, SOURCE_TYPES_PATH
+
+
+class BaseImporter(ABC):
+ _base: Path
+ _project_root: Path
+ _parser_fn: Callable[
+ [
+ Callable[[Callable], ParserPEG | ParserPython],
+ Callable,
+ str,
+ Path,
+ Path,
+ IRGraph,
+ ],
+ BaseIR,
+ ]
+ _grammar_parser: Callable[[Callable], ParserPEG | ParserPython]
+ _program_rule: Callable
+
+ def __init__(
+ self,
+ project_root: Path,
+ grammar_parser: Callable[[Callable], ParserPEG | ParserPython],
+ program_rule: Callable,
+ parser_fn: Callable[
+ [
+ Callable[[Callable], ParserPEG | ParserPython],
+ Callable,
+ str,
+ Path,
+ Path,
+ IRGraph,
+ ],
+ BaseIR,
+ ],
+ ) -> None:
+ self._project_root = project_root
+ self._grammar_parser = grammar_parser
+ self._parser_fn = parser_fn
+ self._program_rule = program_rule
+
+ @property
+ def project_root(self) -> Path:
+ return self._project_root
+
+ @property
+ def grammar_parser(self) -> Callable[[Callable], ParserPEG | ParserPython]:
+ return self._grammar_parser
+
+ @property
+ def program_rule(self) -> Callable:
+ return self._program_rule
+
+ @property
+ def parser_fn(
+ self,
+ ) -> Callable[
+ [
+ Callable[[Callable], ParserPEG | ParserPython],
+ Callable,
+ str,
+ Path,
+ Path,
+ IRGraph,
+ ],
+ BaseIR,
+ ]:
+ return self._parser_fn
+
+ @classmethod
+ def _path_parts(cls, name: CompositeSymbol) -> tuple[tuple[str, ...], str, Symbol]:
+ parts = tuple(name.value)
+
+ if len(parts) == 1:
+ dirs: tuple[str, ...] = ()
+ file_name = parts[0]
+ importer_name = parts[0]
+
+ else:
+ dirs = parts[:-2]
+ file_name = parts[-2]
+ importer_name = parts[-1]
+
+ return dirs, file_name, Symbol(importer_name)
+
+ def _get_module_path(self, *path: Path | str) -> Path:
+ return Path().joinpath(self._base, *path[:-1], str(path[-1]) + ".hat")
+
+ def _add_module(self, module_path: Path, ir_graph: IRGraph) -> None:
+ """To add a new IR module to the graph based on the ``module_path``"""
+ raw_code: str = module_path.read_text()
+ self._parser_fn(
+ self._grammar_parser,
+ self._program_rule,
+ raw_code,
+ self._project_root,
+ module_path,
+ ir_graph,
+ )
+
+
+class TypeImporter(BaseImporter):
+ """Locate and load types under ``src/hat_types`` relative to a project.
+
+ Each ``.hat`` file is scanned for ``type`` declarations and
+ ``use(type:...)`` statements. Referenced types are resolved recursively.
+ Circular imports are tolerated during discovery, but a missing type raises
+ ``FileNotFoundError`` or ``ValueError``.
+ """
+
+ def __init__(
+ self,
+ project_root: Path,
+ grammar_parser: Callable[[Callable], ParserPEG | ParserPython],
+ program_rule: Callable,
+ parser_fn: Callable,
+ ):
+ self._base = Path(project_root).resolve() / SOURCE_TYPES_PATH
+ super().__init__(project_root, grammar_parser, program_rule, parser_fn)
+
+ def _retrieve_type_reference(
+ self,
+ name: CompositeSymbol,
+ ir_graph: IRGraph,
+ ) -> tuple[Symbol, Path]:
+ """
+ Retrieve type references to be filled at the node's ``RefTypeTable``.
+
+ Args:
+ name: the type path as ``CompositeSymbol``
+ ir_graph: the ``IRGraph`` instance
+
+ Returns:
+ A tuple with the type name and its module file path
+ """
+
+ dir_name, file_name, type_name = self._path_parts(name)
+ module_path: Path = self._get_module_path(*dir_name, file_name)
+
+ if module_path not in ir_graph:
+ self._add_module(module_path, ir_graph)
+
+ return type_name, module_path
+
+ def import_types(
+ self,
+ names: Iterable[CompositeSymbol],
+ ir_graph: IRGraph,
+ ) -> dict[Symbol, Path]:
+ return dict(self._retrieve_type_reference(name, ir_graph) for name in names)
+
+
+class FnImporter(BaseImporter):
+ def __init__(
+ self,
+ project_root: Path,
+ grammar_parser: Callable[[Callable], ParserPEG | ParserPython],
+ program_rule: Callable,
+ parser_fn: Callable,
+ ):
+ self._base = Path(project_root).resolve() / SOURCE_FOLDER_NAME
+ super().__init__(project_root, grammar_parser, program_rule, parser_fn)
+
+ def _retrieve_fn_reference(
+ self,
+ name: CompositeSymbol,
+ ir_graph: IRGraph,
+ ) -> tuple[tuple[BaseFnCheck, Path], ...]:
+ """
+ Retrieve function references to be filled at the node's ``RefFnTable``.
+
+ Args:
+ name: the type path as ``CompositeSymbol``
+ ir_graph: the ``IRGraph`` instance
+
+ Returns:
+ Tuple of tuple-pairs containing the function check object and the module file path
+ """
+
+ dir_name, file_name, fn_name = self._path_parts(name)
+ module_path: Path = self._get_module_path(*dir_name, file_name)
+
+ if module_path not in ir_graph:
+ self._add_module(module_path, ir_graph)
+
+ fns = ir_graph.get_fns(module_path, fn_name)
+ return tuple((fn, module_path) for fn in fns)
+
+ def import_fns(
+ self,
+ names: Iterable[CompositeSymbol],
+ ir_graph: IRGraph,
+ ) -> dict[BaseFnCheck, Path]:
+ res: tuple | tuple[tuple[BaseFnCheck, Path]] = ()
+
+ for name in names:
+ res += self._retrieve_fn_reference(name, ir_graph)
+
+ return dict(res)
diff --git a/python/src/hhat_lang/core/imports/utils.py b/python/src/hhat_lang/core/imports/utils.py
new file mode 100644
index 00000000..6579fce2
--- /dev/null
+++ b/python/src/hhat_lang/core/imports/utils.py
@@ -0,0 +1,12 @@
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from collections.abc import Mapping
+from typing import Any, Iterable
+
+
+class BaseImports(ABC):
+ """Base class for importing types and functions"""
+
+ types: Mapping
+ fns: Mapping
diff --git a/python/src/hhat_lang/core/lowlevel/abstract_qlang.py b/python/src/hhat_lang/core/lowlevel/abstract_qlang.py
index f6cb9708..8df5ef2d 100644
--- a/python/src/hhat_lang/core/lowlevel/abstract_qlang.py
+++ b/python/src/hhat_lang/core/lowlevel/abstract_qlang.py
@@ -3,10 +3,13 @@
from abc import ABC, abstractmethod
from typing import Any
+from hhat_lang.core.code.symbol_table import SymbolTable
from hhat_lang.core.data.core import WorkingData
+from hhat_lang.core.error_handlers.errors import ErrorHandler, IndexInvalidVarError
from hhat_lang.core.execution.abstract_base import BaseEvaluator
-from hhat_lang.core.memory.core import IndexManager
-from hhat_lang.dialects.heather.code.simple_ir_builder.ir import IRBlock
+from hhat_lang.core.memory.core import IndexManager, Stack
+from hhat_lang.core.utils import Result
+from hhat_lang.dialects.heather.code.simple_ir_builder.new_ir import IRBlock
class BaseLowLevelQLang(ABC):
@@ -20,6 +23,8 @@ class BaseLowLevelQLang(ABC):
_code: IRBlock
_idx: IndexManager
_executor: BaseEvaluator
+ _qstack: Stack
+ _symbol: SymbolTable
def __init__(
self,
@@ -27,31 +32,34 @@ def __init__(
code: IRBlock,
idx: IndexManager,
executor: BaseEvaluator,
+ qstack: Stack,
+ symboltable: SymbolTable,
*_args: Any,
- **_kwargs: Any
+ **_kwargs: Any,
):
self._qdata = qvar
self._code = code
self._idx = idx
self._executor = executor
- self._num_idxs = len(self._idx.in_use_by.get(self._qdata, []))
+ self._qstack = qstack
+ self._symbol = symboltable
- @abstractmethod
- def init_qlang(self) -> tuple[str, ...]:
- ...
+ match res := self._idx.in_use_by[self._qdata]:
+ case IndexInvalidVarError():
+ # TODO: handle this error properly
+ raise res
+
+ case _:
+ self._num_idxs = len(res)
@abstractmethod
- def end_qlang(self) -> tuple[str, ...]:
- ...
+ def init_qlang(self) -> tuple[str, ...]: ...
@abstractmethod
- def gen_instrs(self, *args: Any, **kwargs: Any) -> tuple[str, ...]:
- ...
+ def gen_instrs(self, *args: Any, **kwargs: Any) -> Result | ErrorHandler: ...
@abstractmethod
- def gen_program(self, *args: Any, **kwargs: Any) -> str:
- ...
+ def gen_program(self, *args: Any, **kwargs: Any) -> str: ...
@abstractmethod
- def __call__(self, *args: Any, **kwargs: Any) -> Any:
- ...
+ def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
diff --git a/python/src/hhat_lang/core/memory/core.py b/python/src/hhat_lang/core/memory/core.py
index 6a7fcc99..84748936 100644
--- a/python/src/hhat_lang/core/memory/core.py
+++ b/python/src/hhat_lang/core/memory/core.py
@@ -1,22 +1,36 @@
from __future__ import annotations
-from abc import ABC, abstractmethod
-from collections import deque
-from queue import LifoQueue
+import sys
+from abc import ABC
+from collections import OrderedDict, deque
+from copy import deepcopy
+from enum import Enum, auto
+from typing import Any, Hashable, cast
from uuid import UUID
+from hhat_lang.core.code.base import BaseFnCheck, BaseIRBlock
from hhat_lang.core.data.core import (
- Symbol, CoreLiteral, CompositeLiteral, CompositeMixData,
- WorkingData
+ CompositeLiteral,
+ CompositeMixData,
+ CompositeSymbol,
+ CompositeWorkingData,
+ CoreLiteral,
+ Symbol,
+ WorkingData,
)
from hhat_lang.core.data.variable import BaseDataContainer
from hhat_lang.core.error_handlers.errors import (
ErrorHandler,
+ FnWrongArgsTypesError,
+ HeapInvalidKeyError,
IndexAllocationError,
+ IndexInvalidVarError,
IndexUnknownError,
IndexVarHasIndexesError,
- HeapInvalidKeyError, IndexInvalidVarError,
+ StackFrameGetError,
+ StackFrameNotFnError,
)
+from hhat_lang.core.utils import gen_uuid
class PIDManager:
@@ -25,10 +39,10 @@ class PIDManager:
"""
def new(self) -> UUID:
- pass
+ raise NotImplementedError()
def list(self) -> list[UUID]:
- pass
+ raise NotImplementedError()
class IndexManager:
@@ -36,22 +50,34 @@ class IndexManager:
Holds and manages information about the indexes (qubits) availability and allocation.
Properties
- - `max_number`: maximum number of allowed indexes
- - `available`: deque with all the available indexes
- - `allocated`: deque with all the allocated indexes
- - `in_use_by`: dictionary containing the allocator variable as key and deque with allocated indexes as value
+ ``max_number``: maximum number of allowed indexes
+
+ ``available``: deque with all the available indexes
+
+ ``allocated``: deque with all the allocated indexes
+
+ ``resources``: variable members and literals with respective number of indexes
+ requested
+
+ ``in_use_by``: dictionary containing the allocator variable member as key and
+ deque with allocated indexes as value
Methods
- - `request`: given a variable (`Symbol`) and the number of indexes (`int`), allocate the number if it has enough space
- - `free`: given a variable (`Symbol`), free all the allocated indexes
+ ``add``: add a variable member or literal with a requested number of indexes to
+ the resources dictionary
+
+ ``request``: given a variable member (``Symbol``) and the number of indexes
+ (``int``), allocate the number if it has enough space
+
+ ``free``: given a variable member (``Symbol``), free all the allocated indexes
"""
_max_num_index: int
_num_allocated: int
_available: deque
_allocated: deque
- _resources: dict[WorkingData, int]
- _in_use_by: dict[WorkingData, deque]
+ _resources: dict[WorkingData | CompositeWorkingData, int]
+ _in_use_by: dict[WorkingData | CompositeWorkingData, deque]
def __init__(self, max_num_index: int):
self._max_num_index = max_num_index
@@ -77,31 +103,46 @@ def allocated(self) -> deque:
return self._allocated
@property
- def resources(self) -> dict[WorkingData, int]:
+ def resources(self) -> dict[WorkingData | CompositeWorkingData, int]:
"""
- Dictionary containing the variable(s)/literal(s) and
+ Dictionary containing the variable members/literal(s) and
the index amount requested.
"""
return self._resources
@property
- def in_use_by(self) -> dict[WorkingData, deque]:
+ def in_use_by(self) -> dict[WorkingData | CompositeWorkingData, deque]:
"""
- Dictionary containing the variable(s)/literal(s) with
+ Dictionary containing the variable members/literal(s) with
the deque of indexes provided.
"""
return self._in_use_by
+ def __getitem__(
+ self, item: WorkingData | CompositeWorkingData
+ ) -> deque | IndexInvalidVarError:
+ """Return the deque of indexes from a quantum data."""
+
+ if res := self._in_use_by.get(item, False):
+ return res # type: ignore [return-value]
+
+ return IndexInvalidVarError(var_name=item)
+
+ def __contains__(self, item: WorkingData | CompositeWorkingData) -> bool:
+ """Checks whether there is item in the IndexManager."""
+
+ return item in self._in_use_by
+
def _alloc_idxs(self, num_idxs: int) -> deque | IndexAllocationError:
- available = (self._max_num_index - self._num_allocated)
+ available = self._max_num_index - self._num_allocated
if available >= num_idxs:
- _data = tuple()
+ _data: tuple = tuple()
for _ in range(0, num_idxs):
- _data += self._available.popleft(),
+ _data += (self._available.popleft(),)
self._num_allocated += 1
return deque(
@@ -111,57 +152,63 @@ def _alloc_idxs(self, num_idxs: int) -> deque | IndexAllocationError:
return IndexAllocationError(requested_idxs=num_idxs, max_idxs=available)
- def _alloc_var(self, var_name: WorkingData, idxs_deque: deque) -> None:
- self._in_use_by[var_name] = idxs_deque
+ def _alloc_var(
+ self, member_name: WorkingData | CompositeWorkingData, idxs_deque: deque
+ ) -> None:
+ self._in_use_by[member_name] = idxs_deque
self._allocated.extend(idxs_deque)
- def _has_var(self, var_name: WorkingData) -> bool:
- return var_name in self._resources
+ def _has_var(self, member_name: WorkingData | CompositeWorkingData) -> bool:
+ return member_name in self._resources
- def _free_var(self, var_name: WorkingData) -> deque:
+ def _free_var(self, member_name: WorkingData | CompositeWorkingData) -> deque:
"""
- Free variable's indexes and allocated deque with those indexes.
+ Free variable member's indexes and allocated deque with those indexes.
"""
- idxs = self._in_use_by.pop(var_name)
+ idxs = self._in_use_by.pop(member_name)
for k in idxs:
self._allocated.remove(k)
return idxs
- def add(self, var_name: WorkingData, num_idxs: int) -> None | ErrorHandler:
+ def add(
+ self, member_name: WorkingData | CompositeWorkingData, num_idxs: int
+ ) -> None | ErrorHandler:
"""
- Add a variable/literal with a given number of indexes required for it.
+ Add a variable member/literal with a given number of indexes required for it.
The amount will be used upon request through the `request` method.
"""
if (self._num_allocated + num_idxs) <= self._max_num_index:
-
- if var_name not in self._resources:
- self._resources[var_name] = num_idxs
+ if member_name not in self._resources:
+ self._resources[member_name] = num_idxs
return None
- return IndexVarHasIndexesError(var_name)
+ return IndexVarHasIndexesError(member_name)
- return IndexAllocationError(requested_idxs=num_idxs, max_idxs=self._num_allocated)
+ return IndexAllocationError(
+ requested_idxs=num_idxs, max_idxs=self._num_allocated
+ )
- def request(self, var_name: WorkingData) -> deque | ErrorHandler:
+ def request(
+ self, member_name: WorkingData | CompositeWorkingData
+ ) -> deque | ErrorHandler:
"""
Request a number of indexes given by the `resources` property for
- a variable `var_name`.
+ a variable member `var_name`.
"""
- if not (num_idxs := self._resources.get(var_name, False)):
- return IndexInvalidVarError(var_name)
+ if not (num_idxs := self._resources.get(member_name, False)):
+ return IndexInvalidVarError(member_name)
match x := self._alloc_idxs(num_idxs):
-
case deque():
- if not self._has_var(var_name):
- return IndexInvalidVarError(var_name=var_name)
+ if not self._has_var(member_name):
+ return IndexInvalidVarError(var_name=member_name)
- self._alloc_var(var_name, x)
+ self._alloc_var(member_name, x)
return x
case IndexAllocationError():
@@ -169,12 +216,12 @@ def request(self, var_name: WorkingData) -> deque | ErrorHandler:
return IndexUnknownError()
- def free(self, var_name: WorkingData) -> None:
+ def free(self, member_name: WorkingData | CompositeWorkingData) -> None:
"""
- Free indexes from a given variable `var_name`.
+ Free indexes from a given variable member `var_name`.
"""
- idxs = self._free_var(var_name)
+ idxs = self._free_var(member_name)
self._available.extend(idxs)
self._num_allocated -= len(idxs)
@@ -184,56 +231,244 @@ def free(self, var_name: WorkingData) -> None:
#########################
-class BaseStack(ABC):
- _data: LifoQueue
+class StackFrame:
+ """Stack memory frame. To be used inside ``Stack`` instance whenever a new scope is needed"""
- @abstractmethod
- def push(self, data: MemoryDataTypes) -> None:
- pass
+ _data: OrderedDict[
+ WorkingData | CompositeSymbol | BaseFnCheck,
+ BaseDataContainer | CoreLiteral | None,
+ ]
+ _fn_header: BaseFnCheck | None
+ _for_fn_use: bool
- @abstractmethod
- def pop(self) -> MemoryDataTypes:
- pass
+ def __init__(self, for_fn_use: bool = False):
+ self._data = OrderedDict()
+ self._for_fn_use = for_fn_use
- @abstractmethod
- def peek(self) -> MemoryDataTypes:
- pass
+ @property
+ def keys(
+ self,
+ ) -> tuple[WorkingData | CompositeWorkingData | BaseFnCheck, ...] | tuple:
+ return tuple(self._data.keys())
+ @property
+ def for_fn_use(self) -> bool:
+ return self._for_fn_use
+
+ def add_no_assign(self, key: Symbol | CompositeSymbol) -> None:
+ if key not in self._data and isinstance(key, WorkingData):
+ self._data[key] = None
+
+ def add(
+ self,
+ key: Symbol | CompositeSymbol | CoreLiteral,
+ value: BaseDataContainer | CoreLiteral,
+ ) -> None:
+ if (
+ isinstance(key, Symbol | CompositeSymbol | CoreLiteral)
+ and (key not in self._data or self._data[key] is None) # type: ignore [index]
+ and isinstance(value, BaseDataContainer | CoreLiteral)
+ ):
+ self._data[key] = value
+
+ def add_fn_header(self, header: BaseFnCheck) -> None:
+ """First thing to be added on the stack frame instance if it is used for a function."""
+
+ if isinstance(header, BaseFnCheck):
+ self._fn_header = header
+
+ def _check_fn_args_types(
+ self, *values_types: BaseDataContainer | CoreLiteral
+ ) -> bool:
+ if self._for_fn_use:
+ return all(
+ cast(BaseFnCheck, self._fn_header).check_args_types(
+ k.type
+ if isinstance(k, BaseDataContainer)
+ else (
+ Symbol(k.type)
+ if isinstance(k.type, str)
+ else CompositeSymbol(k.type)
+ )
+ )
+ for k in values_types
+ )
+
+ sys.exit(StackFrameNotFnError()())
+
+ def add_ordered(self, *values: BaseDataContainer | CoreLiteral) -> None:
+ """
+ **Note**: to be used only for functions, on its startup parameters declaration.
+
+ Use when no argument name is provided and the ``*values`` are assumed to be in
+ the correct order
+ """
+
+ if self._for_fn_use:
+ if self._check_fn_args_types(*values):
+ for k, v in zip(self._data, values):
+ self._data[k] = v
+
+ return
+
+ sys.exit(
+ FnWrongArgsTypesError(
+ values=values,
+ expected=cast(BaseFnCheck, self._fn_header)._args_types,
+ )()
+ )
+
+ # if no function-use stack frame defined, error is raised
+ sys.exit(StackFrameNotFnError()())
+
+ def get(
+ self, item: WorkingData | CompositeSymbol | BaseFnCheck
+ ) -> BaseDataContainer | CoreLiteral | ErrorHandler:
+ return self._data.get(item) or StackFrameGetError(item)
+
+ def __contains__(self, item: Any) -> bool:
+ return item in self._data
+
+
+class Stack:
+ """
+ Stack memory handling data inside frames according to scopes that appears in Lifo order.
+ """
+
+ class EntryType(Enum):
+ VALUE_ONLY = auto()
+ ARG_VALUE = auto()
+
+ _data: list[StackFrame] | list
+ _entry_stack: (
+ list[
+ BaseDataContainer
+ | CoreLiteral
+ | tuple[Symbol, BaseDataContainer | CoreLiteral]
+ ]
+ | list
+ )
+ _entry_type: Stack.EntryType
+ _return_stack: list[BaseDataContainer | CoreLiteral] | list
-class Stack(BaseStack):
def __init__(self):
- self._data = LifoQueue()
+ self._data = []
+ self._entry_stack = []
+ self._return_stack = []
- def push(self, data: MemoryDataTypes) -> None:
- self._data.put(data)
+ def new(self, for_fn_use: bool = False) -> None:
+ """Push a new ``StackFrame`` instance to the stack"""
- def pop(self) -> MemoryDataTypes:
- return self._data.get()
+ self._data.append(StackFrame(for_fn_use))
- def peek(self) -> MemoryDataTypes:
- """Expensive method to 'peek' the last item from the stack."""
+ def push(self, data: BaseDataContainer | CoreLiteral) -> None:
+ """Push ``data`` into current stack's frame as its new last item"""
- last_item = self._data.get()
- self._data.put(last_item)
- return last_item
+ if isinstance(data, BaseDataContainer):
+ self._data[-1].add(data.name, data) # type: ignore [arg-type]
+ else:
+ self._data[-1].add(data, data)
-class BaseHeap(ABC):
- _data: dict[Symbol, BaseDataContainer]
+ def get(
+ self, item: WorkingData | CompositeSymbol
+ ) -> BaseDataContainer | CoreLiteral:
+ """Retrieves data from the current stack frame"""
- @abstractmethod
- def set(self, key: Symbol, value: BaseDataContainer) -> None:
- pass
+ match res := self._data[-1].get(item):
+ case ErrorHandler():
+ sys.exit(res())
- @abstractmethod
- def get(self, key: Symbol) -> BaseDataContainer:
- pass
+ case _:
+ return res
- def __getitem__(self, item: Symbol) -> BaseDataContainer:
- return self.get(item)
+ def set_fn_entry(
+ self,
+ *values: BaseDataContainer | CoreLiteral,
+ fn_header: BaseFnCheck,
+ **args_values: BaseDataContainer | CoreLiteral,
+ ) -> None:
+ """
+ Set the function entry, i.e. it's arguments. It can be through only
+ arguments (``*values``) or through keyword arguments (``**args_values``).
+ It will be consumed by the function once the stack frame is initialized
+ for it.
+
+ Args:
+ *values: ``BaseDataContainer`` or ``CoreLiteral`` data
+ fn_header: ``BaseFnCheck`` instance
+ **args_values: ``BaseDataContainer`` or ``CoreLiteral`` data
+ """
+
+ assert (values and not args_values) or (
+ not values and args_values
+ ), "stack frame cannot have both values and args values-pair"
+
+ if isinstance(fn_header, BaseFnCheck):
+ self._data[-1].add_fn_header(fn_header)
+
+ if values:
+ self._entry_stack.extend(value for value in values)
+ self._entry_type = Stack.EntryType.VALUE_ONLY
+ return
+
+ self._entry_stack.extend(
+ (Symbol(arg), value) for arg, value in args_values.items()
+ )
+ self._entry_type = Stack.EntryType.ARG_VALUE
+
+ def get_fn_entry(self) -> None:
+ """
+ Retrieve function entry for the function stack frame.
+ """
+
+ if self._data[-1].for_fn_use:
+ match self._entry_type:
+ case Stack.EntryType.ARG_VALUE:
+ for arg, value in self._entry_stack:
+ self._data[-1].add(arg, value)
+
+ case Stack.EntryType.VALUE_ONLY:
+ self._data[-1].add_ordered(*self._entry_stack)
+
+ sys.exit(StackFrameNotFnError()())
+
+ def set_fn_return(self, item: BaseDataContainer | CoreLiteral) -> None:
+ """
+ Add a function return to a special space in the stack; to be
+ retrieved by the newest last stack frame
+ """
+
+ self._return_stack = [item]
+
+ def get_fn_return(self) -> BaseDataContainer | CoreLiteral:
+ """
+ After the function is finished and its return value is properly
+ addressed, this method must be used to clean the queue from
+ function returns. Its output is the value being hold (possibly
+ to be used by another stack frame).
+ """
+
+ return_res = deepcopy(self._return_stack)[0]
+ self._return_stack = []
+ return return_res
+
+ def free(self) -> None:
+ """Free last frame from stack"""
+
+ self._data.pop()
+
+ def __contains__(self, item: Any) -> bool:
+ """Always check in the last stack frame added"""
+ return item in self._data[-1]
+
+
+class Heap:
+ """Heap memory handling data of dynamic size"""
+
+ _data: dict[Symbol, BaseDataContainer]
-class Heap(BaseHeap):
def __init__(self):
self._data = dict()
@@ -245,10 +480,130 @@ def set(self, key: Symbol, value: BaseDataContainer) -> None | HeapInvalidKeyErr
return None
def get(self, key: Symbol) -> BaseDataContainer | HeapInvalidKeyError:
- if not (var_data:= self._data.get(key, False)):
+ """
+ Given a key, returns its data which can be a variable container (variable content),
+ a working data (symbol, literal) or composite working data.
+ """
+
+ if (var_data := self._data.get(key, None)) is None:
return HeapInvalidKeyError(key=key)
- return var_data
+ return var_data # type: ignore [return-value]
+
+ def free(self, key: Symbol) -> HeapInvalidKeyError | None:
+ """
+ To free a given key from the heap. It must be called every time the heap goes out of scope
+ """
+
+ if not self._data.pop(key, False):
+ return HeapInvalidKeyError(key=key)
+
+ return None
+
+ def __contains__(self, item: Symbol) -> bool:
+ return item in self._data
+
+ def __getitem__(self, item: Symbol) -> BaseDataContainer:
+ match res := self.get(item):
+ case BaseDataContainer():
+ return res
+
+ case HeapInvalidKeyError():
+ sys.exit(res())
+
+ case _:
+ raise ValueError("could not get heap value")
+
+
+class ScopeValue:
+ """Holds a value for scopes"""
+
+ _value: int
+ _counter: int
+
+ def __init__(self, obj: Hashable, *, counter: int):
+ """
+ Hold a value for scope.
+
+ Args:
+ obj: object must be hashable
+ counter: from the execution counter, to keep track of scope nesting
+ """
+
+ self._value = gen_uuid(gen_uuid(obj) + counter)
+ self._counter = counter
+
+ @property
+ def value(self) -> int:
+ return self._value
+
+ @property
+ def counter(self) -> int:
+ return self._counter
+
+ def __hash__(self) -> int:
+ return self._value
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, ScopeValue):
+ return self._value == other._value
+
+ if isinstance(other, int):
+ return self._value == other
+
+ return False
+
+ def __repr__(self) -> str:
+ return f"S#{self._value}"
+
+
+class Scope:
+ """Defines a scope for stack and heap memory allocation"""
+
+ _table: OrderedDict[ScopeValue, Heap]
+
+ def __init__(self):
+ self._heap = dict()
+
+ @property
+ def table(self) -> OrderedDict[ScopeValue, Heap]:
+ return self._table
+
+ def new(self, scope: ScopeValue) -> Any:
+ """Define a new scope"""
+ if isinstance(scope, ScopeValue):
+ self._table[scope] = Heap()
+
+ else:
+ # TODO: maybe create a error handler for it?
+ raise ValueError(f"value scope must be ScopeValue, got {type(scope)}")
+
+ def last(self) -> ScopeValue:
+ """
+ Get the last ``ScopeValue``, having an ``OrderedDict`` object, will always
+ return the key-value pairs in insertion order.
+ """
+
+ return next(reversed(self._table))
+
+ def free(self, scope: ScopeValue) -> ScopeValue | None:
+ """
+ Free scope heap memory. Must be called every time the scope is finished.
+
+ Returns:
+ The ``ScopeValue`` object where the return data was placed,
+ if ``to_return`` is set to ``True``. ``False`` by default. Otherwise,
+ ``None`` is returned
+ """
+
+ self._table.pop(scope)
+ return None
+
+ def __len__(self) -> int:
+ return len(self._table)
+
+ def __contains__(self, item: ScopeValue) -> bool:
+ return item in self._table
########################
@@ -257,49 +612,107 @@ def get(self, key: Symbol) -> BaseDataContainer | HeapInvalidKeyError:
class BaseMemoryManager(ABC):
-
- _idx: IndexManager
- _stack: BaseStack
- _heap: BaseHeap
- _pid: PIDManager
+ _heap: Scope
+ _stack: Stack
+ _cur_scope: ScopeValue
@property
- def index(self) -> IndexManager:
- return self._idx
+ def heap(self) -> Scope:
+ return self._heap
@property
- def stack(self) -> BaseStack:
+ def stack(self) -> Stack:
return self._stack
@property
- def heap(self) -> BaseHeap:
- return self._heap
-
- @property
- def pid(self) -> PIDManager:
- return self._pid
+ def cur_scope(self) -> ScopeValue:
+ return self._cur_scope
class MemoryManager(BaseMemoryManager):
- """Manages the stack, heap, pid, and index."""
+ """Manages the stack and heap per scope, pid, and indexes."""
+
+ def __init__(self, *, ir_block: BaseIRBlock, depth_counter: int):
+ if isinstance(ir_block, BaseIRBlock) and isinstance(depth_counter, int):
+ self._stack = Stack()
+ self._heap = Scope()
+ self._cur_scope = ScopeValue(obj=ir_block, counter=depth_counter)
+ self._heap.new(self._cur_scope)
+
+ else:
+ raise ValueError(
+ "memory manager needs IR block object, and execution code depth counter"
+ )
- def __init__(self, max_num_index: int):
- self._stack = Stack()
- self._heap = Heap()
- self._pid = PIDManager()
- self._idx = IndexManager(max_num_index)
+ def new_scope(self, ir_block: BaseIRBlock, depth_counter: int) -> ScopeValue:
+ scope_value = ScopeValue(ir_block, counter=depth_counter)
+ self._heap.new(scope_value)
+ self._cur_scope = scope_value
+ return scope_value
- @property
- def stack(self) -> BaseStack:
- return self._stack
+ def free_scope(self, scope: ScopeValue, to_return: bool = False) -> None:
+ self._heap.free(scope=scope)
+
+ if scope == self._cur_scope:
+ if len(self._heap) > 0:
+ self._cur_scope = self._heap.last()
+
+ else:
+ # no more scope, the execution should have reached the end of the code
+ # TODO: double check later what to do in this case
+ pass
+
+ def free_last_scope(self, to_return: bool = False) -> None:
+ if len(self._heap) > 0:
+ last_scope = self._heap.last()
+ self._heap.free(scope=last_scope)
+
+ if len(self._heap) > 0:
+ self._cur_scope = self._heap.last()
+
+ else:
+ # TODO: what to do next
+ pass
+
+ else:
+ raise ValueError(
+ "trying to free last scope, but no more scope is left; mind is empty"
+ )
- @property
- def heap(self) -> BaseHeap:
- return self._heap
+
+class QuantumMemoryManager(MemoryManager):
+ """
+ A quantum version of memory manager to execute quantum programs containing both classical
+ and quantum instructions. It is a superset of ``MemoryManager`` because it includes
+ ``IndexManager``.
+ """
+
+ _idx: IndexManager
+
+ def __init__(
+ self, *, ir_block: BaseIRBlock, max_num_index: int, depth_counter: int = 0
+ ):
+ if isinstance(max_num_index, int):
+ self._idx = IndexManager(max_num_index)
+ super().__init__(ir_block=ir_block, depth_counter=depth_counter)
+
+ else:
+ raise ValueError(
+ f"max num index must be integer, got {type(max_num_index)}"
+ )
@property
def idx(self) -> IndexManager:
return self._idx
-MemoryDataTypes = BaseDataContainer | CoreLiteral | CompositeLiteral | Symbol | CompositeMixData
+MemoryDataTypes = (
+ BaseDataContainer | CoreLiteral | CompositeLiteral | Symbol | CompositeMixData
+)
+"""
+- BaseDataContainer
+- CoreLiteral
+- CompositeLiteral
+- Symbol
+- CompositeMixData
+"""
diff --git a/python/src/hhat_lang/core/types/__init__.py b/python/src/hhat_lang/core/types/__init__.py
index 08915859..2d7ca32b 100644
--- a/python/src/hhat_lang/core/types/__init__.py
+++ b/python/src/hhat_lang/core/types/__init__.py
@@ -1,5 +1,4 @@
from __future__ import annotations
-
# for now, consider pointer size of 32bits instead of 64
POINTER_SIZE = 32
diff --git a/python/src/hhat_lang/core/types/abstract_base.py b/python/src/hhat_lang/core/types/abstract_base.py
index a11258cf..948b651c 100644
--- a/python/src/hhat_lang/core/types/abstract_base.py
+++ b/python/src/hhat_lang/core/types/abstract_base.py
@@ -3,10 +3,10 @@
from abc import ABC, abstractmethod
from typing import Any, Iterable
-from hhat_lang.core.data.core import WorkingData, Symbol, CompositeSymbol
-from hhat_lang.core.data.utils import VariableKind
-from hhat_lang.core.data.variable import BaseDataContainer, VariableTemplate
+from hhat_lang.core.data.core import CompositeSymbol, Symbol
+from hhat_lang.core.data.utils import AbstractDataContainer, VariableKind
from hhat_lang.core.error_handlers.errors import ErrorHandler
+from hhat_lang.core.types.utils import AbstractDataTypeStructure, BaseTypeEnum
from hhat_lang.core.utils import SymbolOrdered
@@ -22,6 +22,9 @@ def __init__(self, size: int):
def size(self) -> int:
return self._size
+ def __repr__(self) -> str:
+ return f"Size({self.size})"
+
class QSize:
"""
@@ -52,30 +55,49 @@ def add_max(self, max_num: int) -> None:
if isinstance(max_num, int) and self._max is None:
self._max = max_num
+ def __repr__(self) -> str:
+ return f"QSize(min={self.min}{f'|max={self.max}' if self.max else ''})"
+
-class BaseTypeDataStructure(ABC):
+class BaseTypeDataStructure(AbstractDataTypeStructure):
"""Base type class for data structures, such as single, struct, enum and union."""
_name: Symbol | CompositeSymbol
+ _ds_type: BaseTypeEnum
_type_container: SymbolOrdered
+ _tmp_container: tuple[Symbol | CompositeSymbol] | tuple
+ """temporary container for yet-to-be-validated members"""
+
_is_quantum: bool
_is_builtin: bool
- _size: Size | None
- _qsize: QSize | None
+ _size: Size
+ _qsize: QSize
_array_type: bool
def __init__(
- self, name: Symbol | CompositeSymbol, is_builtin: bool = False, array_type: bool = False
+ self,
+ name: Symbol | CompositeSymbol,
+ is_builtin: bool = False,
+ array_type: bool = False,
):
self._name = name
self._is_quantum = name.is_quantum
self._is_builtin = is_builtin
self._array_type = array_type
+ self._tmp_container = ()
@property
- def name(self) -> Symbol:
+ def name(self) -> Symbol | CompositeSymbol:
return self._name
+ @property
+ def type(self) -> BaseTypeEnum:
+ return self._ds_type
+
+ @property
+ def ds(self) -> SymbolOrdered:
+ return self._type_container
+
@property
def is_quantum(self) -> bool:
return self._is_quantum
@@ -85,13 +107,23 @@ def is_builtin(self) -> bool:
return self._is_builtin
@property
- def size(self) -> Size | None:
+ def size(self) -> Size:
return self._size
+ @size.setter
+ def size(self, value: Size) -> None:
+ if isinstance(value, Size):
+ self._size = value
+
@property
- def qsize(self) -> QSize | None:
+ def qsize(self) -> QSize:
return self._qsize
+ @qsize.setter
+ def qsize(self, value: QSize) -> None:
+ if isinstance(value, QSize):
+ self._qsize = value
+
@property
def is_array(self) -> bool:
return self._array_type
@@ -100,20 +132,41 @@ def is_array(self) -> bool:
def members(self) -> tuple:
return tuple(k for k in self)
+ @property
+ def tmp_members(self) -> tuple[Symbol | CompositeSymbol] | tuple:
+ """
+ Temporary place to hold members that need validation, e.g. their types are
+ not yet defined at symbol table's ``TypeTable`` or ref table's ``RefTypeTable``.
+ """
+
+ return self._tmp_container
+
+ @abstractmethod
+ def add_member(self, *args: Any, **kwargs: Any) -> Any | ErrorHandler:
+ raise NotImplementedError()
+
@abstractmethod
- def add_member(self, member_type: Any, member_name: Any) -> Any | ErrorHandler: ...
+ def add_tmp_member(self, *args: Any, **kwargs: Any) -> Any:
+ """
+ Add temporary member. It is used when the member is not validated yet,
+ for instance, when its type is in another file or ahead in the parsed
+ code. It must be added as a member later on with ``add_member`` method.
+ """
+
+ raise NotImplementedError()
@abstractmethod
def __call__(
self,
- *args: Any,
- var_name: str,
+ *,
+ var_name: Symbol | CompositeSymbol,
flag: VariableKind,
- **kwargs: dict[WorkingData, WorkingData | VariableTemplate],
- ) -> BaseDataContainer | ErrorHandler: ...
+ **kwargs: Any,
+ ) -> AbstractDataContainer | ErrorHandler:
+ raise NotImplementedError()
def __contains__(self, item: Any) -> bool:
return item in self._type_container
def __iter__(self) -> Iterable:
- yield from self._type_container.items()
+ return iter(self._type_container.items())
diff --git a/python/src/hhat_lang/core/types/builtin.py b/python/src/hhat_lang/core/types/builtin.py
deleted file mode 100644
index cece3b12..00000000
--- a/python/src/hhat_lang/core/types/builtin.py
+++ /dev/null
@@ -1,151 +0,0 @@
-from __future__ import annotations
-
-from collections import OrderedDict
-from typing import Any, Callable, Iterable
-
-from hhat_lang.core.data.core import CoreLiteral, WorkingData, Symbol
-from hhat_lang.core.data.variable import BaseDataContainer, VariableTemplate
-from hhat_lang.core.error_handlers.errors import (
- ErrorHandler,
- TypeSingleError,
- CastNegToUnsignedError,
- CastIntOverflowError,
- CastError,
-)
-from hhat_lang.core.types import POINTER_SIZE
-from hhat_lang.core.types.abstract_base import BaseTypeDataStructure
-from hhat_lang.core.types.abstract_base import Size, QSize
-
-###############
-# DEFINITIONS #
-###############
-
-# classical symbol
-S_INT = Symbol("int")
-S_BOOL = Symbol("bool")
-S_U16 = Symbol("u16")
-S_U32 = Symbol("u32")
-S_U64 = Symbol("u64")
-
-# quantum symbol
-S_QINT = Symbol("@int")
-S_QBOOL = Symbol("@bool")
-S_QU2 = Symbol("@u2")
-S_QU3 = Symbol("@u3")
-S_QU4 = Symbol("@u4")
-
-# sets
-int_types: set = {S_INT, S_U16, S_U32, S_U64}
-qint_types: set = {S_QINT, S_QU2, S_QU3, S_QU4}
-
-
-######################################
-# BUILT-IN DATA STRUCTURE STRUCTURES #
-######################################
-
-
-class BuiltinSingleDS(BaseTypeDataStructure):
- def __init__(self, name: Symbol, bitsize: Size | None = None, qsize: QSize | None = None):
- super().__init__(name, is_builtin=True)
- self._type_container: list = [name]
- self._bitsize = bitsize
- self._qsize = qsize if qsize is not None else QSize(0, 0)
-
- @property
- def bitsize(self) -> Size | None:
- return self._bitsize
-
- def cast_from(
- self, data: WorkingData, cast_fn: Callable
- ) -> CoreLiteral | BaseDataContainer:
- """Cast data to this type."""
-
- return cast_fn(self, data)
-
- def add_member(self, *args: Any) -> BuiltinSingleDS | ErrorHandler:
- return self
-
- def __call__(
- self,
- *args: Any,
- var_name: Symbol,
- **kwargs: dict[WorkingData, WorkingData | VariableTemplate],
- ) -> BaseDataContainer | ErrorHandler:
- if len(args) == 1:
- x = args[0]
-
- if x.type == self._type_container[0]:
- variable = VariableTemplate(
- var_name=var_name,
- type_name=self.name,
- type_ds=OrderedDict({x.type: self._type_container}),
- is_mutable=True,
- )
- variable(*args)
- return variable
-
- return TypeSingleError(self._name)
-
- def __contains__(self, item: Any) -> bool:
- pass
-
- def __iter__(self) -> Iterable:
- pass
-
-
-##################
-# CAST FUNCTIONS #
-##################
-
-
-def int_to_uN(
- ds: BuiltinSingleDS, data: CoreLiteral | BaseDataContainer
-) -> CoreLiteral | BaseDataContainer | ErrorHandler:
-
- if ds.bitsize is not None:
- max_value = 1 << ds.bitsize.size
-
- if isinstance(data, CoreLiteral):
-
- if data < 0:
- return CastNegToUnsignedError(data, ds.members[0][1])
-
- if data < max_value:
- return CoreLiteral(data.value, ds.name.value)
-
- return CastIntOverflowError(data, ds.name)
-
- if isinstance(data, BaseDataContainer):
- val = data.get()
- if data.type in int_types:
-
- if val < 0:
- return CastNegToUnsignedError(val, ds.members[0][1])
-
- if val < max_value:
- return CoreLiteral(val.name, ds.name.value)
-
- return CastIntOverflowError(val, ds.name)
-
- return CastError(ds.name, val)
-
- # something else?
- raise NotImplementedError()
-
-
-#######################
-# BUILT-IN DATA TYPES #
-#######################
-
-# classical
-Int = BuiltinSingleDS(Symbol("int"))
-Bool = BuiltinSingleDS(Symbol("bool"), Size(8))
-U16 = BuiltinSingleDS(Symbol("u16"), Size(16))
-U32 = BuiltinSingleDS(Symbol("u32"), Size(32))
-U64 = BuiltinSingleDS(Symbol("u64"), Size(64))
-
-# quantum
-QBool = BuiltinSingleDS(Symbol("@bool"), Size(POINTER_SIZE), qsize=QSize(1))
-QU2 = BuiltinSingleDS(Symbol("@u2"), Size(POINTER_SIZE), qsize=QSize(2))
-QU3 = BuiltinSingleDS(Symbol("@u3"), Size(POINTER_SIZE), qsize=QSize(3))
-QU4 = BuiltinSingleDS(Symbol("@u4"), Size(POINTER_SIZE), qsize=QSize(4))
diff --git a/python/src/hhat_lang/core/types/builtin_base.py b/python/src/hhat_lang/core/types/builtin_base.py
new file mode 100644
index 00000000..d12dbbf0
--- /dev/null
+++ b/python/src/hhat_lang/core/types/builtin_base.py
@@ -0,0 +1,90 @@
+from __future__ import annotations
+
+from typing import Any, Callable, Iterable
+
+from hhat_lang.core.data.core import CoreLiteral, Symbol, WorkingData
+from hhat_lang.core.data.utils import VariableKind
+from hhat_lang.core.data.variable import BaseDataContainer, VariableTemplate
+from hhat_lang.core.error_handlers.errors import (
+ ErrorHandler,
+)
+from hhat_lang.core.types import POINTER_SIZE
+from hhat_lang.core.types.abstract_base import BaseTypeDataStructure, QSize, Size
+from hhat_lang.core.types.utils import BaseTypeEnum
+from hhat_lang.core.utils import SymbolOrdered
+
+###############
+# DEFINITIONS #
+###############
+
+# classical symbol
+S_INT = Symbol("int")
+S_BOOL = Symbol("bool")
+S_U16 = Symbol("u16")
+S_U32 = Symbol("u32")
+S_U64 = Symbol("u64")
+
+# quantum symbol
+S_QINT = Symbol("@int")
+S_QBOOL = Symbol("@bool")
+S_QU2 = Symbol("@u2")
+S_QU3 = Symbol("@u3")
+S_QU4 = Symbol("@u4")
+
+# sets
+int_types: set = {S_INT, S_U16, S_U32, S_U64}
+qint_types: set = {S_QINT, S_QU2, S_QU3, S_QU4}
+
+
+######################################
+# BUILT-IN DATA STRUCTURE STRUCTURES #
+######################################
+
+
+class BuiltinSingleDS(BaseTypeDataStructure):
+ def __init__(
+ self, name: Symbol, bitsize: Size | None = None, qsize: QSize | None = None
+ ):
+ super().__init__(name, is_builtin=True)
+ self._type_container: SymbolOrdered = SymbolOrdered({0: name})
+ self._size = bitsize or Size(POINTER_SIZE)
+ self._qsize = qsize or QSize(0)
+ self._ds_type = BaseTypeEnum.SINGLE
+
+ @property
+ def bitsize(self) -> Size | None:
+ return self._size
+
+ def cast_from(
+ self, data: WorkingData, cast_fn: Callable
+ ) -> CoreLiteral | BaseDataContainer:
+ """Cast data to this type."""
+
+ return cast_fn(self, data)
+
+ def add_member(self, *args: Any) -> BuiltinSingleDS | ErrorHandler:
+ return self
+
+ def add_tmp_member(self, *args: Any, **kwargs: Any) -> Any:
+ raise ValueError(
+ "built-in type cannot have unknown type at during IR-parsing time"
+ )
+
+ def __call__(
+ self, *, var_name: Symbol, flag: VariableKind = VariableKind.MUTABLE, **_: Any
+ ) -> BaseDataContainer | VariableTemplate | ErrorHandler:
+ return VariableTemplate(
+ var_name=var_name,
+ type_name=self.name,
+ ds_data=SymbolOrdered(
+ {next(iter(self._type_container.values())): self._type_container}
+ ),
+ ds_type=self._ds_type,
+ flag=flag,
+ )
+
+ def __contains__(self, item: Any) -> bool:
+ raise NotImplementedError()
+
+ def __iter__(self) -> Iterable:
+ raise NotImplementedError()
diff --git a/python/src/hhat_lang/core/types/builtin_conversion.py b/python/src/hhat_lang/core/types/builtin_conversion.py
new file mode 100644
index 00000000..c25b1b53
--- /dev/null
+++ b/python/src/hhat_lang/core/types/builtin_conversion.py
@@ -0,0 +1,76 @@
+from __future__ import annotations
+
+from typing import cast
+
+from hhat_lang.core.data.core import CoreLiteral, Symbol, WorkingData
+from hhat_lang.core.data.variable import BaseDataContainer
+from hhat_lang.core.error_handlers.errors import (
+ CastError,
+ CastIntOverflowError,
+ CastNegToUnsignedError,
+ ErrorHandler,
+)
+from hhat_lang.core.types.builtin_base import BuiltinSingleDS, int_types
+
+###################################
+# COMPATIBLE CONVERTABLE TYPES #
+###################################
+
+compatible_types: dict[Symbol, tuple[Symbol, ...]] = {
+ Symbol("int"): (
+ Symbol("u16"),
+ Symbol("u32"),
+ Symbol("u64"),
+ Symbol("i16"),
+ Symbol("i32"),
+ Symbol("i64"),
+ ),
+ Symbol("float"): (Symbol("f32"), Symbol("f64")),
+ Symbol("@int"): (Symbol("@u2"), Symbol("@u3"), Symbol("@u4")),
+}
+"""dictionary to establish the relation between generic types (``int``, ``float``, ``@int``)
+as their possible convertible types"""
+
+
+##################
+# CAST FUNCTIONS #
+##################
+
+
+def int_to_uN(
+ ds: BuiltinSingleDS, data: CoreLiteral | BaseDataContainer
+) -> CoreLiteral | BaseDataContainer | ErrorHandler:
+ if ds.bitsize is not None:
+ max_value = 1 << ds.bitsize.size
+
+ if isinstance(data, CoreLiteral):
+ if data < 0:
+ return CastNegToUnsignedError(data, ds.members[0][1])
+
+ if data < max_value:
+ lit_type = cast(str, ds.name.value)
+ return CoreLiteral(data.value, lit_type)
+
+ return CastIntOverflowError(data, ds.name)
+
+ if isinstance(data, BaseDataContainer):
+ val = data.get()
+ if data.type in int_types:
+ match val:
+ case ErrorHandler():
+ return val
+
+ case CoreLiteral():
+ if val < 0:
+ return CastNegToUnsignedError(val, ds.members[0][1])
+
+ if val < max_value:
+ lit_type = cast(str, ds.name.value)
+ return CoreLiteral(val.value, lit_type)
+
+ return CastIntOverflowError(val, ds.name)
+
+ return CastError(ds.name, val)
+
+ # something else?
+ raise NotImplementedError()
diff --git a/python/src/hhat_lang/core/types/builtin_types.py b/python/src/hhat_lang/core/types/builtin_types.py
new file mode 100644
index 00000000..bb1eb1c9
--- /dev/null
+++ b/python/src/hhat_lang/core/types/builtin_types.py
@@ -0,0 +1,73 @@
+from __future__ import annotations
+
+from hhat_lang.core.data.core import Symbol
+from hhat_lang.core.types import POINTER_SIZE
+from hhat_lang.core.types.abstract_base import QSize, Size
+from hhat_lang.core.types.builtin_base import BuiltinSingleDS
+
+#######################
+# BUILT-IN DATA TYPES #
+#######################
+
+# ---------- #
+# classical #
+# ---------- #
+
+Int = BuiltinSingleDS(Symbol("int"), Size(64))
+Bool = BuiltinSingleDS(Symbol("bool"), Size(8))
+U16 = BuiltinSingleDS(Symbol("u16"), Size(16))
+U32 = BuiltinSingleDS(Symbol("u32"), Size(32))
+U64 = BuiltinSingleDS(Symbol("u64"), Size(64))
+I16 = BuiltinSingleDS(Symbol("i16"), Size(16))
+I32 = BuiltinSingleDS(Symbol("i32"), Size(32))
+I64 = BuiltinSingleDS(Symbol("i64"), Size(64))
+Float = BuiltinSingleDS(Symbol("float"), Size(64))
+F32 = BuiltinSingleDS(Symbol("f32"), Size(32))
+F64 = BuiltinSingleDS(Symbol("f64"), Size(64))
+
+
+# -------- #
+# quantum #
+# -------- #
+
+QBool = BuiltinSingleDS(Symbol("@bool"), Size(POINTER_SIZE), qsize=QSize(1, 1))
+QU2 = BuiltinSingleDS(Symbol("@u2"), Size(POINTER_SIZE), qsize=QSize(2, 2))
+QU3 = BuiltinSingleDS(Symbol("@u3"), Size(POINTER_SIZE), qsize=QSize(3, 3))
+QU4 = BuiltinSingleDS(Symbol("@u4"), Size(POINTER_SIZE), qsize=QSize(4, 4))
+QInt = BuiltinSingleDS(
+ Symbol("@int"),
+ Size(POINTER_SIZE),
+ qsize=QSize(min_num=QU2.qsize.min, max_num=QU4.qsize.max),
+)
+"""
+``QInt`` (``@int``) represents a generic quantum integer, where the minimum qsize is the
+minimum of quantum integer type (``@u2``) and the maximum is the maximum of the biggest
+quantum integer available.
+"""
+
+
+# ---------------------------------- #
+# list with all built-in data types #
+# ---------------------------------- #
+
+builtins_types = {
+ # classical
+ Symbol("int"): Int,
+ Symbol("float"): Float,
+ Symbol("bool"): Bool,
+ Symbol("u16"): U16,
+ Symbol("u32"): U32,
+ Symbol("u64"): U64,
+ Symbol("i16"): I16,
+ Symbol("i32"): I32,
+ Symbol("i64"): I64,
+ Symbol("f32"): F32,
+ Symbol("f64"): F64,
+ # quantum
+ Symbol("@bool"): QBool,
+ Symbol("@int"): QInt,
+ Symbol("@u2"): QU2,
+ Symbol("@u3"): QU3,
+ Symbol("@u4"): QU4,
+}
+"""a dictionary where keys are the available types as str and the values are their classes"""
diff --git a/python/src/hhat_lang/core/types/core.py b/python/src/hhat_lang/core/types/core.py
index 668bc025..9210b51b 100644
--- a/python/src/hhat_lang/core/types/core.py
+++ b/python/src/hhat_lang/core/types/core.py
@@ -1,25 +1,23 @@
from __future__ import annotations
-from collections import OrderedDict
-from typing import Any
+from typing import Any, cast
-from hhat_lang.core.data.core import Symbol, WorkingData, CompositeSymbol
-from hhat_lang.core.data.utils import has_same_paradigm, isquantum, VariableKind
+from hhat_lang.core.data.core import CompositeSymbol, Symbol, WorkingData
+from hhat_lang.core.data.utils import VariableKind, has_same_paradigm, isquantum
from hhat_lang.core.data.variable import BaseDataContainer, VariableTemplate
from hhat_lang.core.error_handlers.errors import (
ErrorHandler,
TypeAndMemberNoMatchError,
TypeQuantumOnClassicalError,
- TypeSingleError,
- TypeStructError,
)
+from hhat_lang.core.types import POINTER_SIZE
from hhat_lang.core.types.abstract_base import BaseTypeDataStructure, QSize, Size
+from hhat_lang.core.types.utils import BaseTypeEnum
from hhat_lang.core.utils import SymbolOrdered
def is_valid_member(
- datatype: BaseTypeDataStructure,
- member: str | Symbol | CompositeSymbol
+ datatype: BaseTypeDataStructure, member: str | Symbol | CompositeSymbol
) -> bool:
"""
Check if a datatype member is valid for the given datatype, e.g. quantum
@@ -34,61 +32,75 @@ def is_valid_member(
class SingleDS(BaseTypeDataStructure):
+ """Class to define data structure for single types."""
+
def __init__(
- self, name: Symbol | CompositeSymbol, size: Size | None = None, qsize: QSize | None = None
+ self,
+ name: Symbol | CompositeSymbol,
+ size: Size | None = None,
+ qsize: QSize | None = None,
):
super().__init__(name)
- self._size = size
- self._qsize = qsize
- self._type_container: OrderedDict[Symbol | CompositeSymbol, Symbol | CompositeSymbol] = OrderedDict()
-
- def add_member(
- self, member_type: BaseTypeDataStructure, _member_name: None = None
- ) -> SingleDS | ErrorHandler:
+ self._size = size or Size(POINTER_SIZE)
+ self._qsize = qsize or QSize(0)
+ self._type_container: SymbolOrdered = SymbolOrdered()
+ self._ds_type = BaseTypeEnum.SINGLE
+ def add_member(self, member_type: BaseTypeDataStructure) -> SingleDS | ErrorHandler:
if not is_valid_member(self, member_type.name):
return TypeQuantumOnClassicalError(member_type.name, self.name)
+ if member_type.type != self.type:
+ return TypeAndMemberNoMatchError(member_type, self.name)
+
self._type_container[self.name] = member_type.name
return self
+ def add_tmp_member(self, *args: Any, **kwargs: Any) -> Any:
+ raise NotImplementedError()
+
def __call__(
self,
- *args: Any,
- var_name: Symbol,
+ *,
+ var_name: Symbol | CompositeSymbol,
flag: VariableKind = VariableKind.IMMUTABLE,
- **kwargs: dict[WorkingData, WorkingData | BaseDataContainer],
- ) -> BaseDataContainer | ErrorHandler:
- if len(args) == 1:
- x = args[0]
-
- if x.type == self._type_container[self.name]:
- variable = VariableTemplate(
- var_name=var_name,
- type_name=self.name,
- type_ds=SymbolOrdered({Symbol(x.type): self._type_container}),
- flag=flag,
- )
- variable(*args)
- return variable
+ **_: Any,
+ ) -> BaseDataContainer | VariableTemplate | ErrorHandler:
+ return VariableTemplate(
+ var_name=var_name,
+ type_name=self.name,
+ ds_data=SymbolOrdered(
+ {next(iter(self._type_container.values())): self._type_container}
+ ),
+ ds_type=self._ds_type,
+ flag=flag,
+ )
- return TypeSingleError(self._name)
+ def __repr__(self) -> str:
+ member = "".join(str(k) for k in self._type_container.values())
+ return f"{self.name}:{member}"
class ArrayDS(BaseTypeDataStructure):
"""This is an array data structure, to be thought as [u64] to represent an array of u64."""
def __init__(
- self, name: Symbol | CompositeSymbol, size: Size | None = None, qsize: QSize | None = None
+ self,
+ name: Symbol | CompositeSymbol,
+ size: Size | None = None,
+ qsize: QSize | None = None,
):
super().__init__(name, array_type=True)
- self._size = size
- self._qsize = qsize
- self._type_container: OrderedDict[Symbol | CompositeSymbol, Symbol | CompositeSymbol] = OrderedDict()
+ self._size = size or Size(POINTER_SIZE)
+ self._qsize = qsize or QSize(0)
+ self._type_container: SymbolOrdered = SymbolOrdered()
def add_member(self, member_type: Any, member_name: Any) -> Any | ErrorHandler:
raise NotImplementedError()
+ def add_tmp_member(self, *args: Any, **kwargs: Any) -> Any:
+ raise NotImplementedError()
+
def __call__(
self,
*args: Any,
@@ -100,21 +112,25 @@ def __call__(
class StructDS(BaseTypeDataStructure):
+ """Class to define data structure for struct types."""
+
def __init__(
- self, name: Symbol | CompositeSymbol, size: Size | None = None, qsize: QSize | None = None
+ self,
+ name: Symbol | CompositeSymbol,
+ size: Size | None = None,
+ qsize: QSize | None = None,
):
super().__init__(name)
- self._size = size
- self._qsize = qsize
- self._type_container: SymbolOrdered[Symbol | CompositeSymbol, Symbol | CompositeSymbol] = SymbolOrdered()
+ self._size = size or Size(POINTER_SIZE)
+ self._qsize = qsize or QSize(0)
+ self._type_container: SymbolOrdered = SymbolOrdered()
+ self._ds_type = BaseTypeEnum.STRUCT
def add_member(
self, member_type: BaseTypeDataStructure, member_name: Symbol | CompositeSymbol
) -> StructDS | ErrorHandler:
-
# check if type and name are consistent, i.e. both quantum or classical
if has_same_paradigm(member_type, member_name):
-
if is_valid_member(self, member_type.name):
self._type_container[member_name] = member_type.name
return self
@@ -123,82 +139,139 @@ def add_member(
return TypeAndMemberNoMatchError(member_type.name, self.name)
+ def add_tmp_member(
+ self,
+ member_type: Symbol | CompositeSymbol,
+ member_name: Symbol | CompositeSymbol,
+ ) -> StructDS:
+ self._tmp_container += ((member_type, member_name),)
+ return self
+
def __call__(
self,
- *args: Any,
- var_name: Symbol,
+ *,
+ var_name: Symbol | CompositeSymbol,
flag: VariableKind = VariableKind.IMMUTABLE,
- **kwargs: dict[WorkingData, WorkingData | BaseDataContainer],
- ) -> BaseDataContainer | ErrorHandler:
- container: SymbolOrdered = SymbolOrdered()
+ **_: Any,
+ ) -> BaseDataContainer | VariableTemplate | ErrorHandler:
+ return VariableTemplate(
+ var_name=var_name,
+ type_name=self._name,
+ ds_data=self._type_container,
+ ds_type=self._ds_type,
+ flag=flag,
+ )
- if len(args) == len(self._type_container):
- for k, (g, c) in zip(args, self._type_container.items()):
+ def __repr__(self) -> str:
+ members = (
+ "{" + " ".join(f"{k}:{v}" for k, v in self._type_container.items()) + "}"
+ )
+ return f"{self.name}{members}"
- if k.type == c:
- container[g] = k
- else:
- return TypeStructError(self._name)
+class EnumDS(BaseTypeDataStructure):
+ """Class to define data structure for enum types."""
+
+ def __init__(
+ self,
+ name: Symbol | CompositeSymbol,
+ size: Size | None = None,
+ qsize: QSize | None = None,
+ ):
+ super().__init__(name)
+ self._size = size or Size(POINTER_SIZE)
+ self._qsize = qsize or QSize(0)
+ self._type_container = SymbolOrdered()
+ self._ds_type = BaseTypeEnum.ENUM
- if len(kwargs) == len(self._type_container):
- for n, (k, v) in enumerate(kwargs.items()):
+ def _get_member_name(self, member: BaseTypeDataStructure | Symbol) -> Symbol:
+ match member:
+ case Symbol():
+ return member
- if k in self._type_container:
- container[k] = v
+ case BaseTypeDataStructure():
+ return cast(Symbol, member.name)
- else:
- return TypeStructError(self._name)
+ case _:
+ raise NotImplementedError()
- variable = VariableTemplate(
+ def add_member(
+ self, member: BaseTypeDataStructure | Symbol
+ ) -> EnumDS | ErrorHandler:
+ member_name = self._get_member_name(member)
+
+ if is_valid_member(self, member_name):
+ self._type_container[member_name] = member
+ return self
+
+ return TypeQuantumOnClassicalError(member_name, self.name)
+
+ def add_tmp_member(self, *args: Any, **kwargs: Any) -> Any:
+ raise NotImplementedError()
+
+ def __call__(
+ self,
+ *,
+ var_name: Symbol | CompositeSymbol,
+ flag: VariableKind = VariableKind.IMMUTABLE,
+ **_: Any,
+ ) -> BaseDataContainer | VariableTemplate | ErrorHandler:
+ return VariableTemplate(
var_name=var_name,
type_name=self._name,
- type_ds=self._type_container,
+ ds_data=self._type_container,
+ ds_type=self._ds_type,
flag=flag,
)
- variable(**container)
- return variable
-class UnionDS(BaseTypeDataStructure):
- def __init__(
- self, name: Symbol | CompositeSymbol, size: Size | None = None, qsize: QSize | None = None
- ):
- super().__init__(name)
- self._size = size
- self._qsize = qsize
- self._type_container = SymbolOrdered()
+class RemoteUnionDS(BaseTypeDataStructure):
+ """Class to define data structure for remote union types"""
- def add_member(self, member_type: str, member_name: str) -> UnionDS:
+ def add_member(self, *args: Any, **kwargs: Any) -> Any | ErrorHandler:
+ raise NotImplementedError()
+
+ def add_tmp_member(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
def __call__(
- self,
- *args: Any,
- var_name: str,
- flag: VariableKind = VariableKind.IMMUTABLE,
- **kwargs: dict[WorkingData, WorkingData | BaseDataContainer],
+ self, *, var_name: Symbol | CompositeSymbol, flag: VariableKind, **kwargs: Any
) -> BaseDataContainer | ErrorHandler:
raise NotImplementedError()
-class EnumDS(BaseTypeDataStructure):
+class UnionDS(BaseTypeDataStructure):
+ """Class to define data structure for union types."""
+
def __init__(
- self, name: Symbol | CompositeSymbol, size: Size | None = None, qsize: QSize | None = None
+ self,
+ name: Symbol | CompositeSymbol,
+ size: Size | None = None,
+ qsize: QSize | None = None,
):
super().__init__(name)
- self._size = size
- self._qsize = qsize
+ self._size = size or Size(POINTER_SIZE)
+ self._qsize = qsize or QSize(0)
self._type_container = SymbolOrdered()
+ self._ds_type = BaseTypeEnum.UNION
+
+ def add_member(self, member_type: str, member_name: str) -> UnionDS:
+ raise NotImplementedError()
- def add_member(self, member_type: str, member_name: str) -> EnumDS:
+ def add_tmp_member(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
def __call__(
self,
- *args: Any,
- var_name: str,
+ *,
+ var_name: Symbol | CompositeSymbol,
flag: VariableKind = VariableKind.IMMUTABLE,
- **kwargs: dict[WorkingData, WorkingData | BaseDataContainer],
- ) -> BaseDataContainer | ErrorHandler:
- raise NotImplementedError()
+ **_: Any,
+ ) -> BaseDataContainer | VariableTemplate | ErrorHandler:
+ return VariableTemplate(
+ var_name=var_name,
+ type_name=self._name,
+ ds_data=self._type_container,
+ ds_type=self._ds_type,
+ flag=flag,
+ )
diff --git a/python/src/hhat_lang/core/types/resolve_sizes.py b/python/src/hhat_lang/core/types/resolve_sizes.py
index 4c3ef3a0..6a75f6ee 100644
--- a/python/src/hhat_lang/core/types/resolve_sizes.py
+++ b/python/src/hhat_lang/core/types/resolve_sizes.py
@@ -2,7 +2,8 @@
from typing import Any
-from hhat_lang.core.code.ir import TypeTable
+from hhat_lang.core.code.new_ir import IRGraph, IRNode, get_type
+from hhat_lang.core.code.symbol_table import TypeTable
from hhat_lang.core.types.abstract_base import BaseTypeDataStructure
@@ -10,19 +11,25 @@ def _size_resolver():
pass
-def _qsize_resolver(ds: BaseTypeDataStructure, table: TypeTable) -> int | None:
- if ds.qsize.max is None:
- qsize_max = 0
+def _qsize_resolver(
+ ds: BaseTypeDataStructure, node: IRNode, ir_graph: IRGraph
+) -> int | None:
+ if ds.qsize is not None:
+ if ds.qsize.max is None:
+ qsize_max = 0
- for _, member_type in ds:
- res = _qsize_resolver(table[member_type], table)
+ for _, member_type in ds:
+ if t := get_type(node.irhash, member_type, ir_graph):
+ res = _qsize_resolver(ds=t, node=node, ir_graph=ir_graph)
- if res:
- qsize_max += res
+ if res:
+ qsize_max += res
- ds.qsize.max = qsize_max or None
+ ds.qsize.add_max(qsize_max)
- return ds.qsize.max
+ return ds.qsize.max
+
+ raise ValueError("Quantum type must have QSize defined.")
def ct_size() -> Any:
diff --git a/python/src/hhat_lang/core/types/utils.py b/python/src/hhat_lang/core/types/utils.py
new file mode 100644
index 00000000..46a3833e
--- /dev/null
+++ b/python/src/hhat_lang/core/types/utils.py
@@ -0,0 +1,29 @@
+from __future__ import annotations
+
+from abc import ABC
+from enum import Enum, auto
+
+
+class BaseTypeEnum(Enum):
+ """Enum data type structures for ``BaseTypeDataStructure`` instances"""
+
+ SINGLE = auto()
+ STRUCT = auto()
+ ENUM = auto()
+ UNION = auto()
+
+ REMOTE_UNION = auto()
+ """
+ ``REMOTE_UNION``: a new data structure to be used in the future to handle remote
+ quantum data; name yet to be settled
+ """
+
+
+class AbstractDataTypeStructure(ABC):
+ """Abstract data type structure. To avoid circular imports."""
+
+ _ds_type: BaseTypeEnum
+
+ @property
+ def type(self) -> BaseTypeEnum:
+ return self._ds_type
diff --git a/python/src/hhat_lang/core/utils.py b/python/src/hhat_lang/core/utils.py
index 7ff84a83..14c203e6 100644
--- a/python/src/hhat_lang/core/utils.py
+++ b/python/src/hhat_lang/core/utils.py
@@ -1,14 +1,20 @@
from __future__ import annotations
+import uuid
from abc import ABC, abstractmethod
from collections import OrderedDict
from collections.abc import Mapping
-from typing import Any, Iterator
+from typing import Any, Hashable, Iterator
+from uuid import NAMESPACE_OID
-from hhat_lang.core.data.core import Symbol, CompositeSymbol
+from hhat_lang.core.data.core import CompositeSymbol, Symbol, WorkingData
from hhat_lang.core.error_handlers.errors import ErrorHandler
+def gen_uuid(obj: Hashable) -> int:
+ return int(uuid.uuid5(NAMESPACE_OID, f"{obj}").hex, 16)
+
+
class SymbolOrdered(Mapping):
"""
A special OrderedDict that accepts Symbol as keys but transforms them
@@ -16,29 +22,47 @@ class SymbolOrdered(Mapping):
as `SingleDS`, `StructDS`, etc.
"""
- _data: OrderedDict[Symbol, Any]
+ _data: OrderedDict[WorkingData | Symbol | CompositeSymbol | int, Any]
def __init__(self, data: dict | OrderedDict | None = None):
self._data = OrderedDict() if data is None else OrderedDict(data)
- def __setitem__(self, key: str | Symbol | CompositeSymbol, value: Any) -> None:
+ def __setitem__(
+ self, key: int | str | WorkingData | Symbol | CompositeSymbol, value: Any
+ ) -> None:
if isinstance(key, str):
self._data[Symbol(key)] = value
elif isinstance(key, (Symbol, CompositeSymbol)):
self._data[key] = value
+ elif isinstance(key, WorkingData):
+ self._data[key] = value
+
+ elif isinstance(key, int):
+ self._data[key] = value
+
else:
- raise ValueError(f"{key} ({type(key)}) is not valid key for data structures.")
+ raise ValueError(
+ f"{key} ({type(key)}) is not valid key for data structures."
+ )
- def __getitem__(self, key: str | Symbol | CompositeSymbol) -> Any:
+ def __getitem__(
+ self, key: int | str | WorkingData | Symbol | CompositeSymbol
+ ) -> Any:
if isinstance(key, str):
return self._data[Symbol(key)]
if isinstance(key, (Symbol, CompositeSymbol)):
return self._data[key]
- raise ValueError(key)
+ if isinstance(key, WorkingData):
+ return self._data[key]
+
+ if isinstance(key, int):
+ return self._data[key]
+
+ raise KeyError(key)
def __len__(self) -> int:
return len(self._data)
@@ -48,14 +72,13 @@ def items(self) -> Iterator:
def keys(self) -> Iterator:
for k in self._data.keys():
- yield k.value
+ yield k.value if not isinstance(k, int) else k
def values(self) -> Iterator:
yield from self._data.values()
def __iter__(self) -> Iterator:
- for k in self._data:
- yield k # .name
+ return iter(k for k in self._data)
def __repr__(self) -> str:
return str(self._data)
@@ -68,8 +91,7 @@ def __init__(self, value: Any):
self.value = value
@abstractmethod
- def result(self) -> Any:
- ...
+ def result(self) -> Any: ...
class Ok(Result):
@@ -84,4 +106,3 @@ class Error(Result):
def result(self) -> ErrorHandler:
return self.value
-
diff --git a/python/src/hhat_lang/dialects/heather/code/ast.py b/python/src/hhat_lang/dialects/heather/code/ast.py
deleted file mode 100644
index 492b903e..00000000
--- a/python/src/hhat_lang/dialects/heather/code/ast.py
+++ /dev/null
@@ -1,302 +0,0 @@
-from __future__ import annotations
-
-from hhat_lang.core.code.ast import AST, Node, Terminal
-
-
-###############
-# AST CLASSES #
-###############
-
-
-class Id(Terminal):
- def __init__(self, value: str):
- self._value = (value,)
- self._name = value
-
-
-class CompositeId(Node):
- def __init__(self, *names: Id):
- self._value = names
- self._name = self.__class__.__name__
-
-
-class CompositeIdWithClosure(Node):
- """
- Used for calling many attributes/properties from the same Id root, for instance:
-
- ```
- user-info.{host port}
- dataset.{obj-name pos.{x y}}
- ```
-
- As showed above, it can be nested.
- """
-
- def __init__(self, *values: Id | CompositeId, name: Id | CompositeId):
- self._value = (name, values)
- self._name = self.__class__.__name__
-
-
-class ArgValuePair(Node):
- def __init__(self, arg: Id, value: ValueType):
- self._value = (arg, value)
- self._name = self.__class__.__name__
-
-
-class OnlyValue(Node):
- def __init__(self, value: ValueType):
- self._value = (value,)
- self._name = self.__class__.__name__
-
-
-class Modifier(Node):
- def __init__(self, *modifiers: ArgValuePair):
- self._values = modifiers
- self._name = self.__class__.__name__
-
-
-class ModifiedId(Node):
- """
- A modifier is in the form of `< value ... >` or `< arg:value ... >`.
- It is intended to change the behavior of the modified, which can be a
- variable, a type or a function call.
- """
-
- def __init__(self, name: Id | CompositeId, modifier: Modifier):
- self._value = (name, modifier)
- self._name = self.__class__.__name__
-
-
-class Literal(Terminal):
- def __init__(self, value: str, value_type: str):
- self._value = (value,)
- self._name = value_type
-
-
-class Array(Node):
- pass
-
-
-class Hash(Node):
- pass
-
-
-class Cast(Node):
- """
- A special syntax sugar that is intended to change the type of what it is
- being applied to (usually a variable). The most important use case is to
- cast a quantum data to a classical type.
- """
-
- def __init__(self, name: TypeType, cast_to: TypeType):
- self._value = (name, cast_to)
- self._name = self.__class__.__name__
-
-
-class Expr(Node):
- def __init__(self, *expr: AST):
- self._value = expr
- self._name = self.__class__.__name__
-
-
-class Declare(Node):
- def __init__(self, var_name: Id, var_type: TypeType):
- self._value = (var_name, var_type)
- self._name = self.__class__.__name__
-
-
-class Assign(Node):
- def __init__(self, var_name: TypeType, expr: Expr):
- self._value = (var_name, expr)
- self._name = self.__class__.__name__
-
-
-class DeclareAssign(Node):
- def __init__(
- self,
- var_name: Id,
- var_type: TypeType,
- expr: Expr,
- ):
- self._value = (var_name, var_type, expr)
- self._name = self.__class__.__name__
-
-
-class CallArgs(Node):
- def __init__(self, *args: ArgValuePair | OnlyValue):
- self._values = args
- self._name = self.__class__.__name__
-
-
-class Call(Node):
- def __init__(self, caller: TypeType, args: CallArgs):
- self._value = (caller, args)
- self._name = self.__class__.__name__
-
-
-class MethodCallArgs(Node):
- def __init__(self, *args: ArgValuePair | OnlyValue):
- self._values = args
- self._name = self.__class__.__name__
-
-
-class MethodCall(Node):
- def __init__(self, self_caller: TypeType, args: CallArgs):
- self._value = (self_caller, args)
- self._name = self.__class__.__name__
-
-
-class InsideOption(Node):
- def __init__(self, option: Expr, body: Body):
- self._value = (option, body)
- self._name = self.__class__.__name__
-
-
-class CallWithBodyOptions(Node):
- def __init__(
- self,
- *call_options: InsideOption,
- caller: TypeType,
- args: CallArgs,
- ):
- self._value = (caller, args, call_options)
- self._name = self.__class__.__name__
-
-
-class CallWithArgsBodyOptions(Node):
- def __init__(self, *arg_options: InsideOption, caller: TypeType):
- self._value = (caller, arg_options)
- self._name = self.__class__.__name__
-
-
-class CallWithBody(Node):
- def __init__(
- self, caller: TypeType, args: CallArgs, body: Body
- ):
- self._value = (caller, args, body)
- self._name = self.__class__.__name__
-
-
-class ArgTypePair(Node):
- def __init__(self, arg_name: Id, arg_type: TypeType):
- self._value = (arg_name, arg_type)
- self._name = self.__class__.__name__
-
-
-class FnArgs(Node):
- def __init__(self, *args: ArgTypePair):
- self._values = args
- self._name = self.__class__.__name__
-
-
-class FnDef(Node):
- def __init__(
- self,
- fn_name: Id,
- fn_type: TypeType,
- args: FnArgs,
- body: Body,
- ):
- self._value = (fn_name, fn_type, args, body)
- self._name = self.__class__.__name__
-
-
-class TypeMember(Node):
- def __init__(self, member_name: Id, member_type: TypeType):
- self._value = (member_name, member_type)
- self._name = self.__class__.__name__
-
-
-class SingleTypeMember(Node):
- def __init__(self, member_type: TypeType):
- self._value = (member_type,)
- self._name = self.__class__.__name__
-
-
-class EnumTypeMember(Node):
- def __init__(self, member_name: Id):
- self._value = (member_name,)
- self._name = self.__class__.__name__
-
-
-class TypeDef(Node):
- def __init__(
- self,
- *members: TypeMember | SingleTypeMember | EnumTypeMember,
- type_name: TypeType,
- type_ds: Id,
- ):
- self._value = (type_name, type_ds, members)
- self._name = self.__class__.__name__
-
-
-class TypeImport(Node):
- def __init__(self, type_list: tuple[Id | CompositeId | CompositeIdWithClosure]):
- self._value = type_list
- self._name = self.__class__.__name__
-
-
-class FnImport(Node):
- def __init__(self, fn_list: tuple[Id | CompositeId | CompositeIdWithClosure]):
- self._value = fn_list
- self._name = self.__class__.__name__
-
-
-class Imports(Node):
- """
- Importing types and then functions to the program.
- """
-
- def __init__(self, *, type_import: tuple[TypeImport, ...], fn_import: tuple[FnImport, ...]):
- self._value = (type_import, fn_import)
- self._name = self.__class__.__name__
-
-
-class Body(Node):
- """
- Body of a closure.
- """
-
- def __init__(self, *body: BodyType):
- self._values = body
- self._name = self.__class__.__name__
-
-
-class Main(Node):
- """
- The `main` closure, where the main execution lives.
- """
-
- def __init__(self, *body: AST):
- self._value = body
- self._name = self.__class__.__name__
-
-
-class Program(Node):
- def __init__(self, *, main: Main, imports: Imports):
- self._value = (imports, main)
- self._name = self.__class__.__name__
-
-
-#####################
-# DESCRIPTIVE TYPES #
-#####################
-
-ValueType = Id | CompositeId | ModifiedId | Literal | Array | Hash
-
-TypeType = Id | CompositeId | ModifiedId
-
-BodyType = (
- Call
- | CallWithBody
- | CallWithBodyOptions
- | Expr
- | MethodCall
- | Declare
- | Assign
- | DeclareAssign
- | Array
- | Hash
- | Literal
- | Id
-)
diff --git a/python/src/hhat_lang/dialects/heather/code/ir_builder.py b/python/src/hhat_lang/dialects/heather/code/ir_builder.py
deleted file mode 100644
index 448cace6..00000000
--- a/python/src/hhat_lang/dialects/heather/code/ir_builder.py
+++ /dev/null
@@ -1,445 +0,0 @@
-"""
-In this file there are three distinct sections:
-
-1. The building functions to get from AST to something that IR and the IR tables can handle;
-2. The actual IR tables builders, namely types and functions; and
-3. The main code builder where the `main` closure lies.
-"""
-
-from __future__ import annotations
-
-from typing import Any
-
-from hhat_lang.core.code.ast import AST
-from hhat_lang.core.data.core import (
- Symbol,
- CompositeSymbol,
- CoreLiteral,
-)
-from hhat_lang.dialects.heather.parsing.imports import (
- parse_imports, parse_types, parse_types_compositeid,
- parse_types_compositeidwithclosure
-)
-from hhat_lang.dialects.heather.code.ast import (
- Id,
- CompositeId,
- CompositeIdWithClosure,
- Cast,
- ArgValuePair,
- OnlyValue,
- Modifier,
- ModifiedId,
- Literal,
- Array,
- Hash,
- Expr,
- Declare,
- Assign,
- DeclareAssign,
- CallArgs,
- Call,
- MethodCallArgs,
- MethodCall,
- InsideOption,
- CallWithBodyOptions,
- CallWithBody,
- ArgTypePair,
- FnArgs,
- FnDef,
- TypeMember,
- SingleTypeMember,
- EnumTypeMember,
- TypeDef,
- FnImport,
- TypeImport,
- Imports,
- Body,
- Main,
- Program,
- ValueType,
- TypeType,
- BodyType,
-)
-# for now just a simple IR for the interpreter suffices
-from hhat_lang.dialects.heather.code.simple_ir_builder.ir import IR
-from hhat_lang.dialects.heather.code.simple_ir_builder.builder import (
- define_id,
- define_compositeid,
- define_literal,
- define_argvaluepair,
-)
-
-# TODO: include other implementation modules for the IR, as below.
-# - each one of them should contain all the named functions
-# - the correct IR module should be read from some configuration file
-"""
-from hhat_heather.code.mlir_ir import define_id
-...
-"""
-
-
-########################################################
-# FUNCTION BUILDERS FROM AST TO ACTUAL CODE FOR THE IR #
-########################################################
-
-def _build_id(code: Id) -> Symbol:
- return define_id(code)
-
-
-def _build_compositeid(code: CompositeId) -> CompositeSymbol:
- return define_compositeid(code)
-
-
-def _build_argvaluepair(code: ArgValuePair) -> tuple[Symbol, Any]:
- return define_argvaluepair(code)
-
-
-def _build_onlyvalue(code: OnlyValue) -> Symbol | CompositeSymbol | CoreLiteral | Any:
- return _build_valuetype(code.value[0])
-
-
-def _build_modifier(code: Modifier) -> tuple[tuple[Symbol, Any], ...]:
- mods = ()
-
- for mod in code.value:
-
- arg, value = _build_argvaluepair(mod)
- mods += ((arg, value),)
-
- return mods
-
-
-def _build_modifiedid(code: ModifiedId) -> Any:
- pass
-
-
-def _build_literal(code: Literal) -> CoreLiteral:
- return define_literal(code)
-
-
-def _build_array(code: Array) -> Any:
- pass
-
-
-def _build_hash(code: Hash) -> Any:
- pass
-
-
-def _build_expr(code: Expr) -> Any:
- pass
-
-
-def _build_declare(code: Declare) -> Any:
- var_name = _build_id(code.value[0])
- var_type = _build_typetype(code.value[1])
-
-
-def _build_assign(code: Assign) -> Any:
- pass
-
-
-def _build_declareassign(code: DeclareAssign) -> Any:
- pass
-
-
-def _build_callargs(code: CallArgs) -> Any:
- pass
-
-
-def _build_call(code: Call) -> Any:
- pass
-
-
-def _build_methodcallargs(code: MethodCallArgs) -> Any:
- pass
-
-
-def _build_methodcall(code: MethodCall) -> Any:
- pass
-
-
-def _build_insideoption(code: InsideOption) -> Any:
- pass
-
-
-def _build_callwithbodyoptions(code: CallWithBodyOptions) -> Any:
- pass
-
-
-def _build_callwithbody(code: CallWithBody) -> Any:
- pass
-
-
-def _build_argtypepair(code: ArgTypePair) -> tuple[Symbol, Any]:
- pass
-
-
-def _build_fnargs(code: FnArgs) -> Any:
- pass
-
-
-def _build_fndef(code: FnDef) -> Any:
- pass
-
-
-def _build_typemember(code: TypeMember) -> Any:
- pass
-
-
-def _build_singletypemember(code: SingleTypeMember) -> Any:
- pass
-
-
-def _build_enumtypemember(code: EnumTypeMember) -> Any:
- pass
-
-
-def _build_typedef(code: TypeDef) -> Any:
- pass
-
-
-def _build_typeimport(code: TypeImport) -> Any:
- for k in code:
-
- match k:
- case Id():
- pass
-
- case CompositeId():
- res = parse_types_compositeid(k)
-
- case CompositeIdWithClosure():
- res = parse_types_compositeidwithclosure(k)
-
-
-def _build_fnimport(code: FnImport) -> Any:
- pass
-
-
-def _build_imports(code: Imports) -> Any:
- for k in code:
-
- match k:
- case TypeImport():
- return _build_typeimport(k)
-
- case FnImport():
- return _build_fnimport(k)
-
- case _:
- raise ValueError(f"invalid import syntax\n =>\n{k}\n")
-
-
-def _build_body(code: Body) -> Any:
- for k in code:
-
- _build_bodytype(k)
-
-
-##############################
-# TYPES DESCRIPTORS BUILDERS #
-##############################
-
-
-def _build_valuetype(code: ValueType) -> Any:
- """Build based on the `ValueType` type descriptor."""
-
- match tmp := code:
-
- case Id():
- return _build_id(tmp)
-
- case CompositeId():
- return _build_compositeid(tmp)
-
- case ModifiedId():
- return _build_modifiedid(tmp)
-
- case Literal():
- return _build_literal(tmp)
-
- case Array():
- raise NotImplementedError("array not implemented")
-
- case Hash():
- raise NotImplementedError("hash not implemented")
-
- case _:
- raise ValueError(f"unknown '{code}'.")
-
-
-def _build_typetype(code: TypeType) -> Any:
- """Build based on the `TypeType` type descriptor."""
-
- match code:
- case Id():
- return _build_id(code)
-
- case CompositeId():
- return _build_compositeid(code)
-
- case ModifiedId():
- return _build_modifiedid(code)
-
- case _:
- raise NotImplementedError()
-
-
-def _build_bodytype(code: BodyType) -> Any:
- """Build based on the `BodyType` type descriptor."""
-
- match k := code:
- case Expr():
- _build_expr(k)
-
- case Declare():
- _build_declare(k)
-
- case Assign():
- _build_assign(k)
-
- case DeclareAssign():
- _build_declareassign(k)
-
- case Call():
- _build_call(k)
-
- case MethodCall():
- _build_methodcall(k)
-
- case CallWithBody():
- _build_callwithbody(k)
-
- case CallWithBodyOptions():
- _build_callwithbodyoptions(k)
-
-
-##################
-# TABLE BUILDERS #
-##################
-
-def build_typetable(code: AST) -> Any:
- for k in code:
-
- match k:
-
- case Id():
- pass
-
- case CompositeId():
- pass
-
- case ArgValuePair():
- pass
-
- case OnlyValue():
- pass
-
- case Modifier():
- pass
-
- case ModifiedId():
- pass
-
- case Literal():
- pass
-
- case Array():
- pass
-
- case Hash():
- pass
-
- case Expr():
- pass
-
- case Declare():
- pass
-
- case Assign():
- pass
-
- case DeclareAssign():
- pass
-
- case CallArgs():
- pass
-
- case Call():
- pass
-
- case MethodCallArgs():
- pass
-
- case MethodCall():
- pass
-
- case InsideOption():
- pass
-
- case CallWithBodyOptions():
- pass
-
- case CallWithBody():
- pass
-
- case ArgTypePair():
- pass
-
- case FnArgs():
- pass
-
- case TypeMember():
- pass
-
- case TypeDef():
- pass
-
- case Imports():
- pass
-
- case Body():
- pass
-
- case Main():
- pass
-
- case Program():
- pass
-
- case _:
- raise NotImplementedError()
-
-
-def build_fntable(code: AST) -> Any:
- fn_name = code.value[0]
- fn_type = code.value[1]
- fn_args = _build_fnargs(code.value[2])
- fn_body = _build_body(code.value[3])
-
-
-#############
-# MAIN CODE #
-#############
-
-def build_main(code: AST) -> Any:
- ir = IR()
-
- for k in code:
-
- match k:
-
- case Imports():
- res = parse_imports(k)
-
- case Body():
- pass
-
- case Main():
- pass
-
- case Program():
- pass
-
- case _:
- raise NotImplementedError()
diff --git a/python/src/hhat_lang/dialects/heather/code/mlir_builder/ir.py b/python/src/hhat_lang/dialects/heather/code/mlir_builder/ir.py
deleted file mode 100644
index 316230d7..00000000
--- a/python/src/hhat_lang/dialects/heather/code/mlir_builder/ir.py
+++ /dev/null
@@ -1,12 +0,0 @@
-"""Implement MLIR to be used on `ir_builder.py`."""
-
-from __future__ import annotations
-
-from hhat_heather.code.ast import (
- Id,
- # types descriptors
-)
-
-
-def define_id(code: Id):
- raise NotImplementedError()
diff --git a/python/src/hhat_lang/dialects/heather/code/simple_ir_builder/builder.py b/python/src/hhat_lang/dialects/heather/code/simple_ir_builder/builder.py
deleted file mode 100644
index c57f766a..00000000
--- a/python/src/hhat_lang/dialects/heather/code/simple_ir_builder/builder.py
+++ /dev/null
@@ -1,85 +0,0 @@
-from __future__ import annotations
-
-from typing import Any, cast
-
-from hhat_lang.dialects.heather.code.ast import (
- Id,
- CompositeId,
- Literal,
- ArgValuePair,
- ValueType,
- ModifiedId,
- Array,
- Hash,
- Declare,
- Assign,
- DeclareAssign,
-)
-from hhat_lang.core.code.utils import check_quantum_type_correctness
-from hhat_lang.core.data.core import Symbol, CompositeSymbol, CoreLiteral
-
-
-#########################
-# IR DEFINING FUNCTIONS #
-#########################
-
-def define_id(code: Id) -> Symbol:
- name: str = cast(str, code.value[0])
- return Symbol(name)
-
-
-def define_compositeid(code: CompositeId) -> CompositeSymbol:
- names: tuple[str, ...] = cast(tuple, code.value)
- check_quantum_type_correctness(names)
- return CompositeSymbol(names)
-
-
-def define_literal(code: Literal) -> CoreLiteral:
- return CoreLiteral(code.value[0], code.name)
-
-
-def define_argvaluepair(code: ArgValuePair) -> tuple[Symbol, Any]:
- arg_name = cast(Id, code.value[0])
- arg = define_id(arg_name)
-
- value = define_valuetype(code.value[1])
-
- return arg, value
-
-
-def define_valuetype(code: ValueType) -> Any:
- match code:
-
- case Id():
- return define_id(code)
-
- case CompositeId():
- return define_compositeid(code)
-
- case Literal():
- return define_literal(code)
-
- case ModifiedId():
- raise NotImplementedError()
-
- case Array():
- raise NotImplementedError()
-
- case Hash():
- raise NotImplementedError()
-
- case _:
- raise ValueError(f"unknown '{code}'.")
-
-
-def define_declare(code: Declare) -> Any:
- pass
-
-
-def define_assign(code: Assign) -> Any:
- pass
-
-
-def define_declareassign(code: DeclareAssign) -> Any:
- pass
-
diff --git a/python/src/hhat_lang/dialects/heather/code/simple_ir_builder/ir.py b/python/src/hhat_lang/dialects/heather/code/simple_ir_builder/ir.py
deleted file mode 100644
index 0a7754b8..00000000
--- a/python/src/hhat_lang/dialects/heather/code/simple_ir_builder/ir.py
+++ /dev/null
@@ -1,91 +0,0 @@
-"""
-Simple IR implementation. Intended to be simple for AST conversion and
-readiness for the evaluator.
-"""
-
-from __future__ import annotations
-
-from typing import Any, Iterable
-
-from hhat_lang.core.code.ast import AST
-from hhat_lang.core.code.ir import BaseIR, BaseFnIR, InstrIR, ArgsIR, InstrIRFlag, BlockIR
-from hhat_lang.core.data.core import Symbol, CompositeSymbol, CoreLiteral, CompositeLiteral
-
-
-class IRInstr(InstrIR):
- def __init__(self, name: Symbol | CompositeSymbol, args: IRArgs, flag: InstrIRFlag):
- if (
- isinstance(name, (Symbol, CompositeSymbol))
- and isinstance(args, IRArgs)
- and isinstance(flag, InstrIRFlag)
- ):
- self._name = name
- self._args = args
- self._flag = flag
-
-
-class IRArgs(ArgsIR):
- def __init__(self, *args: Symbol | CompositeSymbol | CoreLiteral | CompositeLiteral):
- if all(
- isinstance(k, (Symbol, CompositeSymbol, CoreLiteral, CompositeLiteral))
- for k in args
- ) or len(args) == 0:
- self._args = args
-
-
-class IRBlock(BlockIR):
- def __init__(self):
- self._instrs = tuple()
-
- def add_instr(self, instr: IRInstr | IRBlock) -> None:
- if isinstance(instr, IRInstr | IRBlock):
- self._instrs += (instr,)
-
-
-################
-# IR BASE CODE #
-################
-
-def compile_to_ir(code: AST) -> IR:
- pass
-
-
-class FnIR(BaseFnIR):
- def __init__(self):
- self._data = dict()
-
- def push(self, *ags: Any, **kwargs: Any) -> Any:
- pass
-
- def get(self, item: Any) -> Any:
- pass
-
- def __setitem__(self, key: Any, value: Any) -> None:
- pass
-
- def __getitem__(self, key: Symbol | CompositeSymbol) -> Any:
- pass
-
- def __contains__(self, item: Any) -> bool:
- pass
-
-
-class IR(BaseIR):
- """
- The IR class that contains all the relevant code to be evaluated, including
- types, functions and `main` body. An evaluator class must use this one to
- execute classical instructions.
- """
-
- def __init__(self):
- super().__init__()
-
- def add_fn(
- self,
- *,
- fn_name: Symbol,
- fn_type: Symbol | CompositeSymbol,
- fn_args: Any,
- body: IRBlock,
- ) -> None:
- ...
diff --git a/python/src/hhat_lang/dialects/heather/code/simple_ir_builder/new_ir.py b/python/src/hhat_lang/dialects/heather/code/simple_ir_builder/new_ir.py
new file mode 100644
index 00000000..06c4dff6
--- /dev/null
+++ b/python/src/hhat_lang/dialects/heather/code/simple_ir_builder/new_ir.py
@@ -0,0 +1,978 @@
+from __future__ import annotations
+
+import sys
+from abc import ABC, abstractmethod
+from enum import auto
+from pathlib import Path
+from typing import Any, cast
+
+from hhat_lang.core.code.abstract import BaseIR, BaseIRModule, IRHash, RefTable
+from hhat_lang.core.code.base import (
+ BaseFnCheck,
+ BaseIRBlock,
+ BaseIRBlockFlag,
+ BaseIRFlag,
+ BaseIRInstr,
+)
+from hhat_lang.core.code.new_ir import (
+ IRGraph,
+ IRNode,
+ get_type,
+)
+from hhat_lang.core.code.symbol_table import SymbolTable
+from hhat_lang.core.data.core import (
+ CompositeLiteral,
+ CompositeSymbol,
+ CompositeWorkingData,
+ CoreLiteral,
+ Symbol,
+ WorkingData,
+)
+from hhat_lang.core.data.utils import VariableKind
+from hhat_lang.core.data.variable import BaseDataContainer
+from hhat_lang.core.error_handlers.errors import HeapInvalidKeyError
+from hhat_lang.core.memory.core import (
+ MemoryManager,
+)
+from hhat_lang.core.types.abstract_base import BaseTypeDataStructure
+from hhat_lang.core.types.builtin_conversion import compatible_types
+from hhat_lang.core.types.builtin_types import builtins_types
+
+###########################
+# IR INSTRUCTIONS CLASSES #
+###########################
+
+
+class IRFlag(BaseIRFlag):
+ """
+ Used to identify the ``IRBaseInstr`` child class purpose. Ex: a ``CallInstr``
+ class is defined with its name as ``IRFlag.CALL``.
+ """
+
+ NULL = auto()
+ CALL = auto()
+ CAST = auto()
+ ASSIGN = auto()
+ DECLARE = auto()
+ DECLARE_ASSIGN = auto()
+ ARGS = auto()
+ ARG_VALUE = auto()
+ OPTION = auto()
+ COND = auto()
+ MATCH = auto()
+ CALL_WITH_BODY = auto()
+ CALL_WITH_OPTION = auto()
+ RETURN = auto()
+
+
+class IRInstr(BaseIRInstr):
+ """
+ Base class for IR instructions. Custom IR instructions names must adhere to
+ IRFlag enum attributes. For example::
+
+
+ class DeclareInstr(IRInstr):
+ def __init__(self, ...):
+ ...
+ super().__init__(..., name=IRFlag.DECLARE)
+ """
+
+ _name: IRFlag
+ args: tuple[IRBlock | WorkingData | CompositeWorkingData, ...] | tuple
+
+ def __init__(
+ self,
+ *args: IRBlock | IRInstr | WorkingData | CompositeWorkingData,
+ name: IRFlag,
+ ):
+ if all(
+ isinstance(k, IRBlock | IRInstr | WorkingData | CompositeWorkingData)
+ for k in args
+ ) and isinstance(name, IRFlag):
+ self._name = name
+ self.args = args
+ super().__init__()
+
+ else:
+ raise ValueError(
+ f"IR instr {self.__class__.__name__} must received name as {type(name)},"
+ f" args as {[type(k) for k in args]}. Check for correct types."
+ )
+
+ @abstractmethod
+ def resolve(
+ self, mem: MemoryManager, node: IRNode, ir_graph: IRGraph, **kwargs: Any
+ ) -> Any:
+ """
+ To resolve instructions during code execution.
+ """
+
+ raise NotImplementedError()
+
+ def __repr__(self) -> str:
+ return f"{self.name}({', '.join(str(k) for k in self.args)})"
+
+
+class CastInstr(IRInstr):
+ def __init__(
+ self,
+ data: WorkingData | CompositeWorkingData | IRInstr,
+ to_type: WorkingData | CompositeWorkingData | ModifierBlock,
+ ):
+ if isinstance(
+ data, WorkingData | CompositeWorkingData | IRInstr
+ ) and isinstance(to_type, WorkingData | CompositeWorkingData | ModifierBlock):
+ super().__init__(data, to_type, name=IRFlag.CAST)
+
+ else:
+ raise ValueError(
+ f"cast operation cannot contain {data} ({type(data)}) "
+ f"and {to_type} ({type(to_type)})"
+ )
+
+ def resolve(self, mem: MemoryManager, ir_graph: IRGraph, **kwargs: Any) -> None:
+ raise NotImplementedError()
+
+
+class CallInstr(IRInstr):
+ def __init__(
+ self,
+ name: Symbol | CompositeSymbol | ModifierBlock,
+ *,
+ args: (
+ ArgsBlock | ArgsValuesBlock | WorkingData | CompositeWorkingData | None
+ ) = None,
+ option: OptionBlock | None = None,
+ body: BodyBlock | None = None,
+ ):
+ instr_args: tuple[IRBlock | IRInstr | WorkingData] | tuple
+
+ if option is None and body is None:
+ instr_args = (args,)
+ flag = IRFlag.CALL
+
+ elif option is not None and body is None:
+ instr_args = (option,)
+ flag = IRFlag.CALL_WITH_OPTION
+
+ elif option is None and body is not None:
+ instr_args = (args, body)
+ flag = IRFlag.CALL_WITH_BODY
+
+ else:
+ raise ValueError(
+ f"cannot contain option ({type(option)}) and body ({type(body)}) "
+ f"in the same instruction."
+ )
+
+ super().__init__(name, *instr_args, name=flag)
+
+ def resolve(
+ self, mem: MemoryManager, node: IRNode, ir_graph: IRGraph, **_: Any
+ ) -> None:
+ caller: Symbol | CompositeSymbol = (
+ self.args[0] # type: ignore [assignment]
+ if isinstance(self.args[0], Symbol | CompositeSymbol)
+ else (
+ self.args[0].name
+ if isinstance(self.args[0], ModifierBlock)
+ else sys.exit("call instr error")
+ )
+ )
+ args: tuple = self.args[1:]
+ num_args: int = len(args)
+ resolved_args = _handle_call_args(*args, mem=mem, node=node, ir_graph=ir_graph)
+
+ fn_header = BaseFnCheck(fn_name=caller, args_types=resolved_args)
+ mem.stack.new(for_fn_use=True)
+ mem.stack.set_fn_entry(*args, fn_header=fn_header)
+ _handle_call_instr(
+ caller=caller,
+ number_args=num_args,
+ mem=mem,
+ node=node,
+ ir_graph=ir_graph,
+ flag=self.name,
+ )
+
+
+class DeclareInstr(IRInstr):
+ def __init__(
+ self,
+ var: Symbol | ModifierBlock,
+ var_type: Symbol | CompositeSymbol | ModifierBlock,
+ ):
+ if isinstance(var, Symbol | ModifierBlock) and isinstance(
+ var_type, Symbol | CompositeSymbol | ModifierBlock
+ ):
+ super().__init__(var, var_type, name=IRFlag.DECLARE)
+
+ else:
+ raise ValueError(
+ f"var must be symbol, got {type(var)} and var type must be symbol"
+ f" or composite symbol, got {type(var_type)}"
+ )
+
+ def resolve(
+ self, mem: MemoryManager, node: IRNode, ir_graph: IRGraph, **_: Any
+ ) -> None:
+ var: Symbol | ModifierBlock = cast(Symbol | ModifierBlock, self.args[0])
+ var_type_symbol: Symbol | CompositeSymbol = cast(
+ Symbol | CompositeSymbol, self.args[1]
+ )
+ _declare_variable(var, var_type_symbol, mem, node.irhash, ir_graph)
+
+
+class AssignInstr(IRInstr):
+ def __init__(
+ self,
+ var: Symbol | ModifierBlock,
+ value: WorkingData | CompositeWorkingData | IRBlock,
+ ):
+ if isinstance(var, Symbol | ModifierBlock) and isinstance(
+ value, WorkingData | CompositeWorkingData | IRBlock
+ ):
+ super().__init__(var, value, name=IRFlag.ASSIGN)
+
+ else:
+ raise ValueError(
+ f"var must be symbol, got {type(var)} and "
+ f"value must be working data or composite working data, got {type(value)}"
+ )
+
+ def resolve(
+ self, mem: MemoryManager, node: IRNode, ir_graph: IRGraph, **_: Any
+ ) -> None:
+ var: Symbol = cast(Symbol, self.args[0])
+ variable = mem.scope.heap[mem.cur_scope].get(var)
+ mem.scope.stack[mem.cur_scope].push(self.args[1])
+
+ # # resolve value to check and assign the correct type
+ # new_args = _get_assign_datatype(
+ # var_type=variable.type,
+ # value=value,
+ # heap_table=heap_table,
+ # types_table=types_table
+ # )
+ # # set new arguments
+ # self.args = (self.args[0], *new_args)
+
+ _assign_variable(variable=variable, mem=mem, node=node, ir_graph=ir_graph)
+
+
+class DeclareAssignInstr(IRInstr):
+ def __init__(
+ self,
+ var: Symbol | ModifierBlock,
+ var_type: Symbol | CompositeSymbol | ModifierBlock,
+ value: WorkingData | CompositeWorkingData | IRInstr | IRBlock,
+ ):
+ if (
+ isinstance(var, Symbol | ModifierBlock)
+ and isinstance(var_type, Symbol | CompositeSymbol | ModifierBlock)
+ and isinstance(
+ value, WorkingData | CompositeWorkingData | IRInstr | IRBlock
+ )
+ ):
+ super().__init__(var, var_type, value, name=IRFlag.DECLARE_ASSIGN)
+
+ else:
+ raise ValueError(
+ f"var must be symbol, got {type(var)}, "
+ f"var type must be symbol or composite symbol, got {type(var_type)} and "
+ f"value must be working data or composite working data, got {type(value)}"
+ )
+
+ def resolve(
+ self, mem: MemoryManager, node: IRNode, ir_graph: IRGraph, **_: Any
+ ) -> None:
+ var: Symbol = cast(Symbol, self.args[0])
+ var_type_symbol: Symbol | CompositeSymbol = cast(
+ Symbol | CompositeSymbol, self.args[1]
+ )
+ _declare_variable(var, var_type_symbol, mem, node.irhash, ir_graph)
+ variable: BaseDataContainer = cast(BaseDataContainer, mem.stack.get(var))
+ mem.stack.push(variable)
+ _assign_variable(variable=variable, mem=mem, node=node, ir_graph=ir_graph)
+
+
+####################
+# IR BLOCK CLASSES #
+####################
+
+
+class IRBlockFlag(BaseIRBlockFlag):
+ """Define all valid IR block flags for IR blocks"""
+
+ BODY = auto()
+ ARGS = auto()
+ ARGS_VALUES = auto()
+ OPTION = auto()
+ RETURN = auto()
+ MODIFIER = auto()
+ MODIFIER_ARGS = auto()
+
+
+class IRBlock(BaseIRBlock, ABC):
+ """
+ IR blocks
+ """
+
+ _name: IRBlockFlag
+
+ def __len__(self) -> int:
+ return len(self.args)
+
+ def __getitem__(self, item: Any) -> Any:
+ return self.args[item]
+
+
+class BodyBlock(IRBlock):
+ _name = IRBlockFlag.BODY
+
+ def __init__(self, *args: IRBlock | IRInstr):
+ if all(isinstance(k, IRBlock | IRInstr) for k in args):
+ if len(args) == 1 and isinstance(args[0], BodyBlock):
+ self.args = args[0].args
+
+ else:
+ self.args = args
+
+ else:
+ raise ValueError(
+ f"args must be block or instruction, but got {tuple(type(k) for k in args)}"
+ )
+
+ def __repr__(self) -> str:
+ return "\n".join(str(k) for k in self.args)
+
+
+class ArgsBlock(IRBlock):
+ _name = IRBlockFlag.ARGS
+
+ args: tuple[WorkingData | CompositeWorkingData | IRBlock | IRInstr, ...] | tuple
+
+ def __init__(self, *args: WorkingData | CompositeWorkingData | IRBlock | IRInstr):
+ if all(
+ isinstance(k, WorkingData | CompositeWorkingData | IRBlock | IRInstr)
+ for k in args
+ ):
+ self.args = args
+
+ else:
+ raise ValueError(
+ f"args must be block or instruction, but got {tuple(type(k) for k in args)}"
+ )
+
+ def __repr__(self) -> str:
+ return " ".join(str(k) for k in self.args)
+
+
+class ArgsValuesBlock(IRBlock):
+ _name = IRBlockFlag.ARGS_VALUES
+
+ args: (
+ tuple[
+ Symbol,
+ ...,
+ ]
+ | tuple
+ )
+ values: tuple[WorkingData | CompositeWorkingData | IRBlock | IRInstr, ...] | tuple
+
+ def __init__(
+ self,
+ *args: tuple[
+ Symbol,
+ WorkingData | CompositeWorkingData | IRBlock | IRInstr,
+ ],
+ ):
+ self.args = ()
+ self.values = ()
+
+ for k in args:
+ if isinstance(k[0], Symbol):
+ self.args += (k[0],)
+ else:
+ raise ValueError(
+ "args values block's args must be symbol or modifier block "
+ )
+
+ if isinstance(k[1], WorkingData | CompositeWorkingData | IRBlock | IRInstr):
+ self.values += (k[1],)
+ else:
+ raise ValueError(
+ "args values block's values must be symbol, literal, ir block or ir instr"
+ )
+
+ def __repr__(self) -> str:
+ return f"ARG-VALUE#[{' '.join(f'{a}:{v}' for a, v in zip(self.args, self.values))}]"
+
+
+class OptionBlock(IRBlock):
+ _name = IRBlockFlag.OPTION
+
+ args: ( # type: ignore [assignment]
+ tuple[
+ tuple[WorkingData | CompositeWorkingData | IRBlock | IRInstr, ...],
+ IRBlock | IRInstr,
+ ]
+ | tuple
+ )
+
+ def __init__(
+ self,
+ option: WorkingData | CompositeWorkingData | IRBlock | IRInstr,
+ block: IRBlock | IRInstr,
+ ):
+ if isinstance(
+ option, WorkingData | CompositeWorkingData | IRBlock | IRInstr
+ ) and isinstance(block, WorkingData | CompositeWorkingData | IRBlock | IRInstr):
+ self.args = (option, block)
+
+ else:
+ raise ValueError(
+ f"option ({type(option)}) or block ({type(block)}) is of wrong type."
+ )
+
+ @property
+ def option(self) -> WorkingData | CompositeWorkingData | IRBlock | IRInstr:
+ return self.args[0]
+
+ @property
+ def block(self) -> WorkingData | CompositeWorkingData | IRBlock | IRInstr:
+ return self.args[1]
+
+ def __repr__(self) -> str:
+ return f"OPTION#[{self.args[0]}:{self.args[1]}]"
+
+
+class ReturnBlock(IRBlock):
+ _name = IRBlockFlag.RETURN
+
+ args: tuple[WorkingData | CompositeWorkingData | IRBlock | IRInstr, ...]
+
+ def __init__(self, *args: WorkingData | CompositeWorkingData | IRBlock | IRInstr):
+ if all(
+ isinstance(k, WorkingData | CompositeWorkingData | IRBlock | IRInstr)
+ for k in args
+ ):
+ self.args = args
+
+ else:
+ raise ValueError("return block got wrong object types")
+
+ def __repr__(self) -> str:
+ return f"RETURN#[{' '.join(str(k) for k in self.args)}]"
+
+
+class ModifierBlock(IRBlock):
+ _name = IRBlockFlag.MODIFIER
+
+ args: tuple[Symbol | CompositeSymbol | IRInstr, ModifierArgsBlock]
+
+ def __init__(
+ self, obj: Symbol | CompositeSymbol | IRInstr, args: ModifierArgsBlock
+ ):
+ if isinstance(obj, Symbol | CompositeSymbol | IRInstr) and isinstance(
+ args, ModifierArgsBlock
+ ):
+ self.args = (obj, args)
+
+ else:
+ raise ValueError(
+ f"modifier block cannot have types {type(obj)} and {type(args)}"
+ )
+
+ @property
+ def obj(self) -> Symbol | CompositeSymbol | IRInstr:
+ return self.args[0]
+
+ @property
+ def mods(self) -> ModifierArgsBlock:
+ return self.args[1]
+
+ def __repr__(self) -> str:
+ return f"{self.obj}<{self.mods}>"
+
+
+class ModifierArgsBlock(IRBlock):
+ _name = IRBlockFlag.MODIFIER_ARGS
+
+ args: tuple[Symbol | CompositeSymbol, ...] | ArgsValuesBlock | ArgsBlock # type: ignore [assignment]
+
+ def __init__(
+ self, args: tuple[Symbol | CompositeSymbol, ...] | ArgsValuesBlock | ArgsBlock
+ ):
+ if isinstance(args, ArgsValuesBlock | ArgsBlock) or all(
+ isinstance(k, Symbol | CompositeSymbol) for k in args
+ ):
+ self.args = args
+
+ else:
+ raise ValueError(
+ f"modifier args must be made of ArgsValuesBlock elements, "
+ f"not {[type(k) for k in args]}"
+ )
+
+ def __repr__(self) -> str:
+ return " ".join(str(k) for k in self.args)
+
+
+##############
+# IR CLASSES #
+##############
+
+
+class IRModule(BaseIRModule):
+ def __init__(
+ self,
+ path: Path,
+ symboltable: SymbolTable,
+ main: BodyBlock | None = None,
+ ):
+ self._path = path
+ self._symbol_table = symboltable
+ self._main = main or BodyBlock()
+
+ def __str__(self) -> str:
+ st = ""
+ if self.symbol_table.type:
+ st += f"{self.symbol_table.type}"
+
+ if self.symbol_table.fn:
+ st += f"{self.symbol_table.fn}"
+
+ if st:
+ st = f"\n - symbol table:{st}"
+
+ main = ""
+ if self.main:
+ main += "\n - main:\n "
+ main += "\n ".join(str(k) for k in self.main)
+ main += "\n"
+
+ return f"{IRHash(self._path)}{st}{main}"
+
+
+class IR(BaseIR):
+ """Hold all the IR content: IR blocks, IR types and IR functions"""
+
+ def __init__(
+ self,
+ *,
+ ref_table: RefTable,
+ ir_module: IRModule,
+ ):
+ if isinstance(ir_module, IRModule) and isinstance(ref_table, RefTable):
+ self._module = ir_module
+ self._ref_table = ref_table
+
+ else:
+ raise ValueError(
+ "cannot have main IR block and symbol table in the same IR"
+ )
+
+ def __repr__(self) -> str:
+ rf = ""
+
+ if self.ref_table.types:
+ rf += "\n".join(f" {t}:{t_def}" for t, t_def in self.ref_table.types)
+
+ if self.ref_table.fns:
+ rf += "\n".join(f" {f}:{f_def}" for f, f_def in self.ref_table.fns)
+
+ if rf:
+ rf = f"\n ref table:\n{rf}\n"
+
+ module = f"\n module:{self.module}"
+
+ return f"\n=IR:start={rf}{module}=IR:end=\n"
+
+
+##################
+# MISC FUNCTIONS #
+##################
+
+
+def _declare_variable(
+ var: Symbol | CompositeSymbol | ModifierBlock,
+ var_type_symbol: Symbol | CompositeSymbol,
+ mem: MemoryManager,
+ node_hash: IRHash,
+ ir_graph: IRGraph,
+) -> None:
+ """
+ Convenient function for resolving variable declaration during the execution execution
+ and store it on the memory for further use.
+
+ Args:
+ var: the actual variable; must be a ``Symbol`` or ``ModifierBlock`` object
+ var_type_symbol:
+ mem: ``MemoryManager`` object
+ node_hash:
+ ir_graph:
+ """
+
+ # we just need the variable for now
+ var_symbol = cast(
+ Symbol | CompositeSymbol, var.args[0] if isinstance(var, ModifierBlock) else var
+ )
+ # TODO: make use of the modifier property through a new code logic later
+
+ if var_symbol in mem.stack:
+ raise ValueError(
+ f"{var_symbol} already in scope memory; cannot re-declare variable"
+ )
+
+ var_type = get_type(
+ node_key=node_hash, importing=var_type_symbol, ir_graph=ir_graph
+ )
+
+ match var_type:
+ case None:
+ raise ValueError(
+ f"var type {var_type} not found on available custom and built-in types"
+ )
+
+ case BaseTypeDataStructure():
+ var_container = var_type(
+ var_name=var_symbol,
+ # TODO: use the modifier to define variable flag and define a default as well
+ flag=VariableKind.MUTABLE,
+ )
+
+ match var_container:
+ case BaseDataContainer():
+ mem.stack.push(var_container)
+
+ case _:
+ raise ValueError(f"{var_container}")
+
+ case _:
+ raise NotImplementedError(
+ f"{var_type} ({type(var_type)}) not implemented yet for variable declaration"
+ )
+
+
+def _get_assign_datatype(
+ var_type: Symbol | CompositeSymbol,
+ value: WorkingData | CompositeWorkingData | IRInstr | IRBlock,
+ mem: MemoryManager,
+ node: IRNode,
+ ir_graph: IRGraph,
+) -> Symbol | CoreLiteral | CoreLiteral | CompositeLiteral | BaseDataContainer:
+ """
+ Convenient function to: (1) check whether the data being assigned to the variable has
+ the correct type, and to (2) resolve any instruction and block.
+
+ For instance, ``int`` data type can be converted to any of the valid integer types,
+ such as ``u64``, ``i64``, so on. However, if the data provided is a ``float`` and the
+ variable is an integer (e.g. ``u64``), it cannot be converted implicitly, so an error
+ will be raised. 'Convertible' data types should be done so explicitly on code,
+ with ``*`` (cast) operation, ex::
+
+ var1:u32 = 4.0*u32
+ var2:f32 = 255*f32
+
+ Data should be prepared to be inserted into the variable container, so any caller or
+ casting should be resolved here.
+
+ Args:
+ var_type: ``CompositeSymbol`` (or ``Symbol``) object of the variable type
+ value: data name as ``WorkingData``, ``CompositeWorkingData``, ``IRInstr`` or
+ ``IRBlock`` object to be assigned to the variable
+ mem: ``MemoryManager`` object
+ node:
+ ir_graph:
+
+ Returns:
+ The data name with adjusted type (if possible) or raise an error, in case data
+ is not compatible
+ """
+
+ new_instr: IRInstr
+
+ match value:
+ case Symbol():
+ res_var = mem.scope.heap[mem.cur_scope].get(value)
+
+ match res_var:
+ case HeapInvalidKeyError():
+ raise ValueError(f"variable {value} is not declared yet")
+
+ case _:
+ if res_var.type == var_type:
+ return value
+
+ case CompositeSymbol():
+ raise NotImplementedError(
+ "composite symbol on variable assignment not implemented yet"
+ )
+
+ case CoreLiteral():
+ data_type = (
+ Symbol(value.type)
+ if isinstance(value.type, str)
+ else CompositeSymbol(value.type)
+ )
+ data_type_tuple = compatible_types.get(data_type, None) or (data_type,) # type: ignore [arg-type]
+
+ if var_type in data_type_tuple:
+ dt_ds = builtins_types.get(data_type) # type: ignore [arg-type]
+
+ if dt_ds:
+ mem.symbol.type.add(data_type, dt_ds)
+
+ else:
+ raise ValueError(f"invalid type {data_type}")
+
+ return CoreLiteral(value.value, data_type.value)
+
+ case CompositeLiteral():
+ raise NotImplementedError(
+ "composite literal on variable assignment not implemente yet"
+ )
+
+ case IRInstr():
+ new_args: (
+ tuple[WorkingData | CompositeWorkingData | BaseDataContainer] | tuple
+ ) = ()
+
+ for k in value:
+ new_args += (
+ _get_assign_datatype(
+ var_type=var_type,
+ value=k,
+ mem=mem,
+ node=node,
+ ir_graph=ir_graph,
+ ),
+ )
+
+ new_instr = value.__class__(*new_args, name=value.name)
+ new_instr.resolve(mem, node, ir_graph)
+
+ return mem.scope.stack[mem.cur_scope].pop()
+
+ case BodyBlock() | ArgsBlock() | ArgsValuesBlock():
+ new_blocks: (
+ tuple[WorkingData | CompositeWorkingData | BaseDataContainer] | tuple
+ ) = ()
+
+ for k in value:
+ new_blocks += (
+ _get_assign_datatype(
+ var_type=var_type,
+ value=k,
+ mem=mem,
+ node=node,
+ ir_graph=ir_graph,
+ ),
+ )
+
+ new_instr = cast(IRInstr, value.__class__(*new_blocks))
+ new_instr.resolve(mem=mem, node=node, ir_graph=ir_graph)
+
+ return mem.scope.stack[mem.cur_scope].pop()
+
+ case OptionBlock():
+ # FIXME: implement option block
+ raise NotImplementedError()
+
+ case _:
+ raise NotImplementedError(
+ f"{value} ({type(value)}) on variable assignment with undefined implementation"
+ )
+
+ raise ValueError(
+ f"data {value} to be assigned is not compatible with target type {var_type}"
+ )
+
+
+def _assign_variable(
+ *,
+ variable: BaseDataContainer,
+ mem: MemoryManager,
+ node: IRNode,
+ ir_graph: IRGraph,
+ **arg_values: Any,
+) -> None:
+ """
+ Convenient function to assign a value to a variable. It calls checks for any
+ data incompatibility and resolvers for any instructions or blocks to be yet
+ evaluated.
+
+ Args:
+ variable: the variable container object
+ mem:
+ node:
+ ir_graph:
+ **arg_values: Any extra argument used
+ """
+
+ args: WorkingData | CompositeWorkingData | IRInstr | IRBlock = mem.scope.stack[
+ mem.cur_scope
+ ].pop()
+ new_args: tuple = (
+ _get_assign_datatype(
+ var_type=variable.type,
+ value=args,
+ mem=mem,
+ node=node,
+ ir_graph=ir_graph,
+ ),
+ )
+
+ if len(new_args) > 0 and len(arg_values) == 0:
+ variable.assign(*new_args)
+
+ elif len(new_args) == 0 and len(arg_values) > 0:
+ variable.assign(**arg_values)
+
+ else:
+ raise NotImplementedError(
+ f"should not have arguments and argument-value together when "
+ f"assigning variable {variable}"
+ )
+
+
+def _get_type_from_data(
+ data: BaseDataContainer | CoreLiteral,
+) -> Symbol | CompositeSymbol:
+ if isinstance(data, CoreLiteral):
+ return (
+ Symbol(data.type)
+ if isinstance(data.type, str)
+ else CompositeSymbol(data.type)
+ )
+
+ if isinstance(data, BaseDataContainer):
+ return data.type
+
+ sys.exit(f"unknown arg value on call args resolution ({type(data)})")
+
+
+def _handle_call_args(
+ *args: IRBlock | IRInstr | WorkingData | CompositeWorkingData,
+ mem: MemoryManager,
+ node: IRNode,
+ ir_graph: IRGraph,
+) -> tuple[CoreLiteral | BaseDataContainer, ...] | tuple:
+ """
+ Convenient function to resolve call arguments.
+
+ Args:
+ *args:
+ mem: ``MemoryManager`` object
+ node:
+ ir_graph:
+ """
+
+ resolved_args: tuple[Symbol | CompositeSymbol, ...] | tuple = ()
+
+ for arg in args:
+ match arg:
+ case tuple() | IRBlock():
+ for k in arg:
+ resolved_args += _handle_call_args(
+ *k, mem=mem, node=node, ir_graph=ir_graph
+ )
+
+ case IRInstr():
+ arg.resolve(mem, node, ir_graph)
+ res_return = mem.stack.get_fn_return()
+ resolved_args += (_get_type_from_data(res_return),)
+
+ case Symbol() | CompositeSymbol():
+ resolved_args += (_get_type_from_data(mem.stack.get(arg)),)
+
+ case CoreLiteral():
+ resolved_args += (arg,)
+
+ return resolved_args
+
+
+def _handle_call_instr(
+ caller: Symbol | CompositeSymbol | ModifierBlock,
+ number_args: int,
+ mem: MemoryManager,
+ node: IRNode,
+ ir_graph: IRGraph,
+ flag: IRFlag,
+) -> None:
+ """
+ Convenient function to handle call instruction and evaluated it.
+
+ Args:
+ caller: the caller name
+ number_args: number of arguments; needed to pop data out of the stack the
+ correct amount of times
+ mem: ``MemoryManager`` object
+ flag: ``IRFlag`` value
+ """
+
+ match flag:
+ case IRFlag.CALL:
+ args_types: tuple[WorkingData] | tuple = ()
+ args: tuple[BaseDataContainer] | tuple = ()
+
+ for _ in range(number_args):
+ res = mem.stack.pop()
+ args += (res,)
+
+ if isinstance(res, CoreLiteral):
+ args_types += (res.type,)
+
+ elif isinstance(res, Symbol):
+ args_types += (res,)
+
+ # TODO: implement modifier resolution before proceeding on function definition
+
+ caller = caller[0] if isinstance(caller, ModifierBlock) else caller
+ fn_entry = BaseFnCheck(
+ fn_name=caller,
+ args_types=args_types,
+ )
+ fn_block: IRBlock = cast(IRBlock, mem.symbol.fn.get(fn_entry, None))
+
+ if fn_block is None:
+ raise ValueError(
+ f"function {caller} with arg type signature {args_types} not found"
+ )
+
+ # FIXME: depth_counter value needs to come from the execution global depth counter
+ fn_scope = mem.new_scope(fn_block, depth_counter=1)
+ _resolve_fn_block(fn_block, mem, node, ir_graph)
+ mem.free_last_scope(to_return=True)
+
+ case IRFlag.CALL_WITH_BODY:
+ pass
+
+ case IRFlag.CALL_WITH_OPTION:
+ pass
+ pass
+
+
+def _resolve_fn_block(
+ data: IRBlock | IRInstr, mem: MemoryManager, node: IRNode, ir_graph: IRGraph
+) -> None:
+ """
+ Convenient function to resolve function blocks. Whenever it's called from outside,
+ a new scope from ``MemoryManager`` must be created and freed after it finishes
+ execution and return to the outside scope.
+
+ Args:
+ data: IR block or IR instruction object
+ mem: ``MemoryManager`` object
+ node:
+ ir_graph:
+ """
+
+ match data:
+ case IRBlock():
+ for k in data:
+ _resolve_fn_block(k, mem, node, ir_graph)
+
+ case IRInstr():
+ data.resolve(mem=mem, node=node, ir_graph=ir_graph)
diff --git a/python/src/hhat_lang/dialects/heather/code/simple_ir_builder/new_ir_builder.py b/python/src/hhat_lang/dialects/heather/code/simple_ir_builder/new_ir_builder.py
new file mode 100644
index 00000000..bd803917
--- /dev/null
+++ b/python/src/hhat_lang/dialects/heather/code/simple_ir_builder/new_ir_builder.py
@@ -0,0 +1,68 @@
+from __future__ import annotations
+
+from pathlib import Path
+from typing import Mapping
+
+from hhat_lang.core.code.base import BaseFnCheck
+from hhat_lang.core.code.new_ir import build_reftable
+from hhat_lang.core.code.symbol_table import SymbolTable
+from hhat_lang.core.data.core import CompositeSymbol, Symbol
+from hhat_lang.core.data.fn_def import FnDef
+from hhat_lang.core.types.abstract_base import BaseTypeDataStructure
+from hhat_lang.dialects.heather.code.simple_ir_builder.new_ir import (
+ IR,
+ BodyBlock,
+ IRModule,
+)
+from hhat_lang.dialects.heather.parsing.utils import FnsDict, TypesDict
+
+TypesTyping = TypesDict | dict[Symbol | CompositeSymbol, Path]
+FnsTyping = FnsDict | dict[BaseFnCheck, Path]
+
+
+def build_ir_module(
+ *,
+ path: Path | str,
+ types: tuple[BaseTypeDataStructure, ...] | None = None,
+ fns: tuple[FnDef, ...] | None = None,
+ main: BodyBlock | None = None,
+) -> IRModule:
+ """
+ Build an ``IRModule`` instance from types, functions and/or main body block.
+
+ Args:
+ path: the ``IRModule`` path instance or str
+ types: tuple of ``BaseTypeDataStructure`` elements or ``None``. Default ``None``
+ fns: tuple of ``FnDef`` elements or ``None``. Default ``None``
+ main: a ``BodyBlock`` instance or ``None``. Default ``None``
+
+ Returns:
+ The ``IRModule`` instance
+ """
+
+ types = types or ()
+ fns = fns or ()
+ st = SymbolTable()
+ path = path if isinstance(path, Path) else Path(path)
+
+ for t in types:
+ st.type.add(t.name, t)
+
+ for f in fns:
+ st.fn.add(f.fn_check, f)
+
+ return IRModule(path=path, symboltable=st, main=main)
+
+
+def build_ir(
+ *,
+ path: Path | str,
+ ref_types: TypesTyping | None = None,
+ ref_fns: FnsTyping | None = None,
+ types: tuple[BaseTypeDataStructure, ...] | None = None,
+ fns: tuple[FnDef, ...] | None = None,
+ main: BodyBlock | None = None,
+) -> IR:
+ ref_table = build_reftable(types=ref_types, fns=ref_fns)
+ ir_module = build_ir_module(path=path, types=types, fns=fns, main=main)
+ return IR(ref_table=ref_table, ir_module=ir_module)
diff --git a/python/src/hhat_lang/dialects/heather/code/ssa_ir_builder/ir.py b/python/src/hhat_lang/dialects/heather/code/ssa_ir_builder/ir.py
deleted file mode 100644
index c3ee7c0a..00000000
--- a/python/src/hhat_lang/dialects/heather/code/ssa_ir_builder/ir.py
+++ /dev/null
@@ -1,282 +0,0 @@
-from __future__ import annotations
-
-from typing import Any, Iterable
-
-from hhat_lang.core.data.core import Symbol, CompositeSymbol, CoreLiteral
-
-
-# TODO: continue to implement this approach in the future
-
-
-class SSACounter:
- _count: int
-
- def __init__(self):
- # count starts at -1 to align with list indexes
- self._count = -1
-
- def inc(self):
- self._count += 1
- return self._count
-
- def reset(self):
- """Use this mainly when testing code"""
- self._count = -1
-
-
-class IRModifier:
- """
- Modifier generates some extra behaviors for the data it's applied on.
-
- Data can be a variable, a type, a function/instruction/operation, or
- anything that has a `Symbol` and can be manipulated at runtime.
- """
-
- _ssa: SSA
- _mods: dict[int | Symbol, Any] | dict
-
- def __init__(self, ssa: SSA, *, amods: tuple | None = None, kmods: dict | None = None):
- self._ssa = ssa
-
- # check whether amods (mod arguments) is not empty:
- if amods:
-
- for n, p in enumerate(amods):
- if isinstance(p, (Symbol, CompositeSymbol, SSA)):
- self._mods[n] = p
-
- else:
- raise ValueError(f"unsupported mod for {self._ssa}: {p}")
-
- # check whether kmods (mod key-value pairs) is not empty instead:
- elif kmods:
-
- for k, v in kmods.items():
- if (
- isinstance(k, Symbol)
- and isinstance(v, (Symbol, CompositeSymbol, SSA, CoreLiteral))
- ):
- self._mods[k] = v
-
- else:
- raise ValueError(f"unsupported mod and param for {self._ssa}: {k} -> {v}")
-
- # if nothing is provided, the mod is empty:
- else:
- # no modifier generated; empty data
- self._mods = dict()
-
- @property
- def ssa(self) -> SSA:
- return self._ssa
-
- @property
- def symbol(self) -> Symbol:
- return self._ssa.symbol
-
- @property
- def mods(self) -> dict[int | Symbol, Any] | dict:
- return self._mods
-
- def __repr__(self) -> str:
- mod_repr = " ".join(f"{k}:{v}" for k, v in self._mods.items()) if self._mods else ""
- return f"$mod({self.ssa})[{mod_repr}]"
-
-
-class SSA:
- """
- SSA form data value.
-
- Contains the `Symbol` and the `SSACounter` index. Optionally, it will
- either have a `SSAPhi` or an `IRModifier`, but cannot have both.
- """
-
- _symbol: Symbol
- _idx: int | None
- _phi: SSAPhi | None
- _mod: IRModifier | None
-
- def __init__(self, value: Symbol):
- if isinstance(value, Symbol):
- self._phi = None
- self._symbol = value
- self._idx = None
- self._mod = None
-
- else:
- raise ValueError("SSA value must be a symbol.")
-
- @property
- def symbol(self) -> Symbol:
- return self._symbol
-
- @property
- def name(self) -> str:
- return self._symbol.value
-
- @property
- def idx(self) -> int:
- return self._idx
-
- @property
- def phi(self) -> None | SSAPhi:
- return self._phi
-
- @property
- def mod(self) -> None | IRModifier:
- return self._mod
-
- def set_idx(self, idx: int) -> None:
- if self.idx is None:
- self._idx = idx
-
- @classmethod
- def get_ssa(cls, value: Symbol | SSAPhi) -> SSA:
- """Get a new SSA from a Symbol or a SSAPhi."""
-
- if isinstance(value, SSAPhi):
- new_ssa = SSA(value.symbol)
- new_ssa.set_phi(value)
- return new_ssa
-
- return SSA(value)
-
- def set_phi(self, value: SSAPhi) -> None:
- if isinstance(value, SSAPhi):
- self._phi = value
-
- else:
- raise ValueError(f"{value} is not a Phi/SSAPhi ({type(value)}).")
-
- def set_mod(self, value: IRModifier) -> None:
- if isinstance(value, IRModifier):
- self._mod = value
-
- else:
- raise ValueError(f"{value} is not a modifier ({type(value)}).")
-
- def __eq__(self, other: Any) -> bool:
- if isinstance(other, SSA):
- return self._symbol == other._symbol and self._idx == other._idx
- return False
-
- def __hash__(self) -> int:
- return hash((self._symbol, self._idx))
-
- def __repr__(self) -> str:
- phi = f"<{self.phi}>" if self.phi else ""
- mod = f"<{self.mod}>" if self.mod else ""
- return f"%{self.name}#{self.idx}{phi or mod}"
-
-
-class SSAPhi:
- """
- The phi function for disambiguity between a variable coming from a control flow.
- """
-
- _symbol: Symbol
- _args: tuple[SSA, ...]
-
- def __init__(self, *vars: SSA):
- if self._check_args(*vars):
- self._args = vars
- self._symbol = vars[0].symbol
-
- else:
- raise ValueError("SSAPhi must contain the same variables.")
-
- @property
- def symbol(self) -> Symbol:
- return self._symbol
-
- def _check_args(self, *args: SSA) -> bool:
- return len(set(k.name for k in args)) == 1
-
- def __eq__(self, other: Any) -> bool:
- if isinstance(other, SSAPhi):
- return self._symbol == other._symbol and self._args == other._args
- return False
-
- def __hash__(self) -> int:
- return hash((self._symbol, self._args))
-
- def __repr__(self) -> str:
- return f"ø({','.join(str(k) for k in self._args)})"
-
-
-class IRVar:
- """
- Holds a list of all the SSA forms for a given variable. Each SSA form
- index matches the list index, so it's easy to compare, retrieve or do
- optimizations with it.
- """
-
- _symbol: Symbol
- _data: list[SSA, ...] | list
- _ssa_counter: SSACounter
-
- def __init__(self, symbol: Symbol):
- self._symbol = symbol
- self._data = []
- self._ssa_counter = SSACounter()
-
- @property
- def symbol(self) -> Symbol:
- return self._symbol
-
- @property
- def data(self) -> list[SSA, ...]:
- return self._data
-
- def ssa_inc(self) -> int:
- return self._ssa_counter.inc()
-
- def push(self, name: Symbol | SSA | SSAPhi | IRModifier) -> None:
- match name:
- case SSA():
- if self.symbol == name.symbol:
- name.set_idx(self.ssa_inc())
- self._data.append(name)
- return
-
- case IRModifier():
- if self.symbol == name.symbol:
- new_ssa = SSA.get_ssa(name.symbol)
- new_ssa.set_idx(self.ssa_inc())
- new_ssa.set_mod(name)
- self._data.append(new_ssa)
- return
-
- case SSAPhi():
- if self.symbol == name.symbol:
- new_ssa = SSA.get_ssa(name)
- new_ssa.set_idx(self.ssa_inc())
- self._data.append(new_ssa)
- return
-
- case Symbol():
- if self.symbol == name:
- new_ssa = SSA.get_ssa(name)
- new_ssa.set_idx(self.ssa_inc())
- self._data.append(new_ssa)
- return
-
- case _:
- raise ValueError(
- f"IRVar only accepts Symbol, SSA, SSAPhi or IRModifier."
- f" ({name} ({type(name)}))"
- )
-
- raise ValueError("IRVar cannot accept a different symbol (variable).")
-
- def __len__(self) -> int:
- return len(self._data)
-
- def __getitem__(self, index: int) -> SSA:
- return self._data[index]
-
- def __iter__(self) -> Iterable:
- yield from self._data
-
- def __repr__(self) -> str:
- return f"var:{self.symbol}.{self._data}"
diff --git a/python/src/hhat_lang/dialects/heather/execution/__init__.py b/python/src/hhat_lang/dialects/heather/execution/__init__.py
new file mode 100644
index 00000000..0995c923
--- /dev/null
+++ b/python/src/hhat_lang/dialects/heather/execution/__init__.py
@@ -0,0 +1,17 @@
+"""
+Use to execute classical functions test. It follows the regular code execution path::
+
+ raw code -> AST -> IR
+ _______|__________
+ | |
+ v v
+ quantum branch classical branch
+ | |-> memory
+ | |-> executor
+ |-> program
+ |-> executor
+ | -> low level language
+ | -> target (simulator or QPU device)
+ | -> classical branch (if needed)
+
+"""
diff --git a/python/src/hhat_lang/dialects/heather/code/ssa_ir_builder/__init__.py b/python/src/hhat_lang/dialects/heather/execution/classical/__init__.py
similarity index 100%
rename from python/src/hhat_lang/dialects/heather/code/ssa_ir_builder/__init__.py
rename to python/src/hhat_lang/dialects/heather/execution/classical/__init__.py
diff --git a/python/src/hhat_lang/dialects/heather/execution/classical/executor.py b/python/src/hhat_lang/dialects/heather/execution/classical/executor.py
new file mode 100644
index 00000000..f6a0a17c
--- /dev/null
+++ b/python/src/hhat_lang/dialects/heather/execution/classical/executor.py
@@ -0,0 +1,27 @@
+"""
+Execute classical branch instructions. Quantum branch may use it
+to execute classical instructions that are not supported by the
+quantum low level language and/or the target backend.
+"""
+
+from __future__ import annotations
+
+from typing import Any
+
+from hhat_lang.core.execution.abstract_base import BaseEvaluator
+from hhat_lang.core.memory.core import MemoryManager
+from hhat_lang.dialects.heather.code.simple_ir_builder.new_ir import IRBlock
+
+
+class Evaluator(BaseEvaluator):
+ def __init__(self, mem: MemoryManager, **_kwargs: Any):
+ self._mem = mem
+
+ def walk(self, code: Any, mem: MemoryManager, **kwargs: Any) -> Any:
+ pass
+
+ def run(self, code: IRBlock, **kwargs: Any) -> Any:
+ pass
+
+ def __call__(self, code: IRBlock, **kwargs: Any) -> Any:
+ pass
diff --git a/python/src/hhat_lang/dialects/heather/execution/executor.py b/python/src/hhat_lang/dialects/heather/execution/executor.py
new file mode 100644
index 00000000..c56e9f51
--- /dev/null
+++ b/python/src/hhat_lang/dialects/heather/execution/executor.py
@@ -0,0 +1,20 @@
+from __future__ import annotations
+
+from typing import Any
+
+from hhat_lang.dialects.heather.code.simple_ir_builder.new_ir import IRBlock
+
+
+class Evaluator:
+ def __init__(self, code: IRBlock):
+ if isinstance(code, IRBlock):
+ self._code = code
+
+ else:
+ raise ValueError("code to be evaluated must be an IR type.")
+
+ def walk(self, *args: Any, **kwargs: Any) -> Any:
+ pass
+
+ def run(self):
+ pass
diff --git a/python/src/hhat_lang/dialects/heather/execution/new_ir.py b/python/src/hhat_lang/dialects/heather/execution/new_ir.py
new file mode 100644
index 00000000..0c4d9574
--- /dev/null
+++ b/python/src/hhat_lang/dialects/heather/execution/new_ir.py
@@ -0,0 +1,60 @@
+from __future__ import annotations
+
+from typing import Any
+
+from hhat_lang.core.code.abstract import BaseIR, IRHash
+from hhat_lang.core.code.new_ir import IRGraph
+from hhat_lang.core.data.core import CompositeSymbol, Symbol
+from hhat_lang.core.execution.abstract_base import BaseIRManager
+from hhat_lang.dialects.heather.code.simple_ir_builder.new_ir import IR
+
+
+class IRManager(BaseIRManager):
+ """Handle IR codes for Heather dialect through ``IRGraph`` instance"""
+
+ def __init__(self):
+ self._graph = IRGraph()
+
+ def add_ir(self, ir: IR) -> None:
+ """
+ Add a single IR
+
+ Args:
+ ir: the ``IR`` instance to be added to the manager
+ """
+
+ self._graph.add_node(ir)
+
+ def link_ir(
+ self,
+ *refs: Symbol | CompositeSymbol,
+ ir_importing: BaseIR,
+ ir_imported: BaseIR,
+ **kwargs: Any,
+ ) -> None:
+ """
+ Link two IRs, where one is the importer (importing IR) and the other is the imported IR.
+
+ Args:
+ *refs: list of symbols of types or functions from the ``ir_imported`` instance
+ ir_importing: the ``IR`` instance that will import data from another ``IR`` instance
+ ir_imported: the imported ``IR``, which contains the data to be imported
+ **kwargs: Extra data if needed
+ """
+
+ importing = IRHash.get_key(ir_importing)
+ imported = IRHash.get_key(ir_imported)
+ self._graph.add_edge(*refs, node_key=importing, link_key=imported)
+
+ def update_ir(self, prev_ir: IR, new_ir: IR) -> None:
+ """
+ Update a current ``IR`` instance for a new ``IR`` instance. It will replace the current
+ one on the IR graph as well, from its node position to all the current's relationships.
+
+ Args:
+ prev_ir: (the current, or to be) previous ``IR`` instance
+ new_ir: the new ``IR`` instance, to replace the current ``IR`` instance
+ """
+
+ prev_key = IRHash.get_key(prev_ir)
+ self._graph.update_node(prev_key, new_ir)
diff --git a/python/src/hhat_lang/dialects/heather/execution/quantum/__init__.py b/python/src/hhat_lang/dialects/heather/execution/quantum/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/python/src/hhat_lang/dialects/heather/execution/quantum/program.py b/python/src/hhat_lang/dialects/heather/execution/quantum/program.py
new file mode 100644
index 00000000..cc16ab1b
--- /dev/null
+++ b/python/src/hhat_lang/dialects/heather/execution/quantum/program.py
@@ -0,0 +1,109 @@
+"""
+Quantum program is handler for quantum data/variable that executes
+the quantum content done by a classical casting request. For example::
+
+ u32*@2
+
+casts a quantum data `@2` into a `u32` data type. The same as::
+
+ u32*@redim(@1<@u3>)
+
+will cast the resulting set of instructions from `@redim(@1<@u3>)`
+into `u32`. It also is valid for quantum variables::
+
+ @v1:@u3 = @redim(@0)
+ number:u32 = u32*@v1
+
+
+The quantum program workflow is as follows:
+
+- Instructions are analyzed according to the low level language and target
+backend support (lower level counterparts, LLC)
+
+ - If classical instructions are supported, they will be handled by those
+ - If not, they will fall back into this dialect's classical branch execution
+
+- Memory is handled by the dialect and shared when appropriate to the LCC
+- All the quantum-specific optimizations are handled by the LLC
+- Quantum instructions are then executed and results are collected
+- Casting protocols apply the according source type to target type at the results
+- Results are sent back to the execution workflow as the target type data
+
+
+"""
+
+from __future__ import annotations
+
+from typing import Any, Type
+
+from hhat_lang.core.code.symbol_table import SymbolTable
+from hhat_lang.core.data.core import WorkingData
+from hhat_lang.core.error_handlers.errors import ErrorHandler
+from hhat_lang.core.execution.abstract_base import BaseEvaluator
+from hhat_lang.core.execution.abstract_program import BaseProgram
+from hhat_lang.core.lowlevel.abstract_qlang import BaseLowLevelQLang
+from hhat_lang.core.memory.core import IndexManager, Stack
+from hhat_lang.dialects.heather.code.simple_ir_builder.new_ir import IRBlock
+
+# TODO: the imports below must come from the config file, not hardcoded
+from hhat_lang.low_level.target_backend.qiskit.openqasm.code_executor import (
+ execute_program,
+)
+
+
+class Program(BaseProgram):
+ def __init__(
+ self,
+ *,
+ qdata: WorkingData,
+ idx: IndexManager,
+ block: IRBlock,
+ executor: BaseEvaluator,
+ symboltable: SymbolTable,
+ qlang: Type[ # type: ignore [type-arg]
+ BaseLowLevelQLang[
+ WorkingData,
+ IRBlock,
+ IndexManager,
+ BaseEvaluator,
+ Stack,
+ SymbolTable,
+ ]
+ ],
+ ):
+ if (
+ isinstance(qdata, WorkingData)
+ and isinstance(idx, IndexManager)
+ and isinstance(block, IRBlock)
+ ):
+ self._qdata = qdata
+ self._idx = idx
+ self._block = block
+ self._executor = executor
+ self._qstack = Stack()
+ self._symbol = symboltable
+ self._qlang = qlang(
+ self._qdata,
+ self._block,
+ self._idx,
+ self._executor,
+ self._qstack,
+ self._symbol,
+ )
+
+ else:
+ raise ValueError(
+ f"Quantum program got invalid parameters: {qdata=} | {idx=} {block=}"
+ )
+
+ @property
+ def qstack(self) -> Stack:
+ return self._qstack
+
+ def run(self, debug: bool = False) -> Any | ErrorHandler:
+ qlang_code = self._qlang.gen_program()
+
+ if debug:
+ print(qlang_code)
+
+ return execute_program(qlang_code, self._qdata, debug)
diff --git a/python/src/hhat_lang/dialects/heather/grammar/fn_grammar.py b/python/src/hhat_lang/dialects/heather/grammar/fn_grammar.py
new file mode 100644
index 00000000..46d42e2b
--- /dev/null
+++ b/python/src/hhat_lang/dialects/heather/grammar/fn_grammar.py
@@ -0,0 +1,74 @@
+from __future__ import annotations
+
+from typing import Any
+
+from arpeggio import Kwd
+from arpeggio.peg import EOF, OneOrMore, Optional, ZeroOrMore
+
+from hhat_lang.dialects.heather.grammar.generic_grammar import (
+ assign,
+ assign_ds,
+ assignargs,
+ body,
+ declare,
+ declareassign,
+ declareassign_ds,
+ expr,
+ full_id,
+ id_composite_value,
+ many_import,
+ simple_id,
+ single_import,
+)
+from hhat_lang.dialects.heather.grammar.type_grammar import typeimport
+
+
+def fn_program() -> Any:
+ return ZeroOrMore(imports), ZeroOrMore(fns), Optional(main), EOF
+
+
+def imports() -> Any:
+ return Kwd("use"), "(", OneOrMore([typeimport, fnimport]), ")"
+
+
+def fnimport() -> Any:
+ return Kwd("fn"), ":", [single_import, many_import]
+
+
+def fns() -> Any:
+ return Kwd("fn"), simple_id, fnargs, Optional(full_id), fn_body
+
+
+def fnargs() -> Any:
+ return "(", ZeroOrMore(argtype), ")"
+
+
+def argtype() -> Any:
+ return simple_id, ":", id_composite_value
+
+
+def fn_body() -> Any:
+ return (
+ "{",
+ ZeroOrMore(
+ [
+ fn_return,
+ declareassign,
+ declareassign_ds,
+ declare,
+ assignargs,
+ assign_ds,
+ assign,
+ expr,
+ ]
+ ),
+ "}",
+ )
+
+
+def fn_return() -> Any:
+ return "::", expr
+
+
+def main() -> Any:
+ return Kwd("main"), body
diff --git a/python/src/hhat_lang/dialects/heather/grammar/generic_grammar.py b/python/src/hhat_lang/dialects/heather/grammar/generic_grammar.py
new file mode 100644
index 00000000..a6f61918
--- /dev/null
+++ b/python/src/hhat_lang/dialects/heather/grammar/generic_grammar.py
@@ -0,0 +1,185 @@
+from __future__ import annotations
+
+from typing import Any
+
+from arpeggio import Kwd, OneOrMore, Optional, ZeroOrMore
+from arpeggio import RegExMatch as _
+
+
+def id_composite_value() -> Any:
+ return [("[", full_id, "]"), full_id]
+
+
+def callargs() -> Any:
+ return simple_id, "=", valonly
+
+
+def valonly() -> Any:
+ return [array, full_id, literal]
+
+
+def array() -> Any:
+ return "[", ZeroOrMore([literal, composite_id_with_closure, full_id]), "]"
+
+
+def simple_id() -> Any:
+ return _(r"@?[a-zA-Z][a-zA-Z0-9\-_]*")
+
+
+def composite_id() -> Any:
+ return simple_id, OneOrMore(".", simple_id)
+
+
+def composite_id_with_closure() -> Any:
+ return (
+ [composite_id, simple_id],
+ ".",
+ "{",
+ OneOrMore([composite_id_with_closure, composite_id, simple_id]),
+ "}",
+ )
+
+
+def modifier() -> Any:
+ return "<", [ref, pointer, OneOrMore([callargs, valonly])], ">"
+
+
+def full_id() -> Any:
+ return [composite_id, simple_id], Optional(modifier)
+
+
+def ref() -> Any:
+ return Kwd("&")
+
+
+def pointer() -> Any:
+ return Kwd("*")
+
+
+def literal() -> Any:
+ return [t_float, t_null, t_bool, t_str, t_int, qt_bool, qt_int], Optional(
+ ":", composite_id
+ )
+
+
+def t_null() -> Any:
+ return Kwd("null")
+
+
+def t_bool() -> Any:
+ return [Kwd("true"), Kwd("false")]
+
+
+def t_str() -> Any:
+ return _(r'"([^"]*)"')
+
+
+def t_int() -> Any:
+ return _(r"-?([1-9]\d*|0)")
+
+
+def t_float() -> Any:
+ return _(r"-?\d+\.\d+")
+
+
+def qt_bool() -> Any:
+ return [Kwd("@true"), Kwd("@false")]
+
+
+def qt_int() -> Any:
+ return _(r"-?\@([1-9]\d*|0)")
+
+
+def comment() -> Any:
+ return [_(r"\/\/([^\n]*)\n"), _(r"\/\-.*?\-\/")]
+
+
+def single_import() -> Any:
+ return [composite_id_with_closure, full_id]
+
+
+def many_import() -> Any:
+ return "[", OneOrMore(single_import), "]"
+
+
+def body() -> Any:
+ return (
+ "{",
+ ZeroOrMore([declareassign, declareassign_ds, declare, assign, expr]),
+ "}",
+ )
+
+
+def expr() -> Any:
+ return [
+ cast,
+ assign_ds,
+ callwithargsoptions,
+ callwithbodyoptions,
+ callwithbody,
+ call,
+ array,
+ full_id,
+ literal,
+ ]
+
+
+def declare() -> Any:
+ return simple_id, Optional(modifier), ":", full_id
+
+
+def assign() -> Any:
+ return full_id, "=", expr
+
+
+def assign_ds() -> Any:
+ return full_id, ".{", [OneOrMore(assignargs), OneOrMore(expr)], "}"
+
+
+def declareassign() -> Any:
+ return simple_id, Optional(modifier), ":", full_id, "=", expr
+
+
+def declareassign_ds() -> Any:
+ return (
+ simple_id,
+ Optional(modifier),
+ ":",
+ full_id,
+ "=",
+ ".{",
+ OneOrMore(assignargs),
+ "}",
+ )
+
+
+def cast() -> Any:
+ return [call, literal, full_id], "*", full_id
+
+
+def call() -> Any:
+ return full_id, "(", args, ")", Optional(modifier)
+
+
+def args() -> Any:
+ return ZeroOrMore([callargs, cast, call, valonly])
+
+
+def assignargs() -> Any:
+ return [composite_id, simple_id], "=", expr
+
+
+def option() -> Any:
+ return [call, array, full_id], ":", [body, expr]
+
+
+def callwithbodyoptions() -> Any:
+ return full_id, "(", args, ")", "{", OneOrMore(option), "}"
+
+
+def callwithargsoptions() -> Any:
+ return full_id, "(", OneOrMore(option), ")"
+
+
+def callwithbody() -> Any:
+ return full_id, "(", args, ")", body
diff --git a/python/src/hhat_lang/dialects/heather/grammar/grammar.peg b/python/src/hhat_lang/dialects/heather/grammar/grammar.peg
index 14cb50d1..96519bd7 100644
--- a/python/src/hhat_lang/dialects/heather/grammar/grammar.peg
+++ b/python/src/hhat_lang/dialects/heather/grammar/grammar.peg
@@ -1,56 +1,68 @@
-program = imports* (type_file*) / (fns* main?) EOF
+program = imports* ((fns* main?) / (type_file*)) EOF
-imports = ( 'use' '(' (typeimport / fnimport)+ ')' )*
+imports = 'use' '(' (typeimport / fnimport)+ ')'
typeimport = 'type' ':' ( single_import / many_import )
fnimport = 'fn' ':' ( single_import / many_import )
-single_import = composite_id_with_closure / id
+single_import = composite_id_with_closure / full_id
many_import = '[' single_import+ ']'
type_file = 'type' ( typesingle / typestruct / typeenum / typeunion )
typesingle = simple_id ':' id_composite_value
-typestruct = simple_id '{' typesingle* '}'
+typemember = simple_id ':' id_composite_value
+typestruct = simple_id '{' typemember* '}'
typeenum = simple_id '{' enummember* '}'
typeunion = simple_id 'union' '{' unionmember* '}'
enummember = simple_id / typestruct
-unionmember = typesingle / typestruct / typeenum
+unionmember = typemember / typestruct / typeenum
+type_trait = 'trait' full_id '{' fns* '}'
+typespace = 'typespace' ( trait_id / full_id ) '{' fns* '}'
-fns = 'fn' simple_id fnargs id? body
+fns = 'fn' simple_id fnargs full_id? fn_body
fnargs = '(' argtype* ')'
argtype = simple_id ':' id_composite_value
-
-id_composite_value = ( '[' id ']' ) / id
+fn_body = '{' ( fn_return / declareassign / declareassign_ds / declare / assignargs / assign_ds / assign / expr )* '}'
+fn_return = "::" expr
+id_composite_value = ( '[' full_id ']' ) / full_id
main = 'main' body
-body = '{' (declare / assign / declareassign / expr)* '}'
-expr = cast / call / callwithbody / callwithbodyoptions / callwithargsbodyoptions / array
-declare = simple_id modifier? ':' id
-assign = id '=' expr
-declareassign = simple_id modifier? ':' id '=' expr
-cast = id '*' ( call / literal / id )
-call = id '(' args* ')'
-args = callargs / call / valonly
-callargs = simple_id ':' valonly
-valonly = array / literal / id
-callwithbodyoptions = id '(' args* ')' body
-callwithargsbodyoptions = id '(' (expr body)+ ')'
-callwithbody = id body
-
-array = '[' ( literal / composite_id_with_closure / id )* ']'
+body = '{' ( declareassign / declareassign_ds / declare / assign / expr)* '}'
+expr = cast / assign_ds / callwithargsoptions / callwithbodyoptions / callwithbody / call / array / full_id / literal
+declare = simple_id modifier? ':' full_id
+assign = full_id '=' expr
+assign_ds = full_id '.{' ( assignargs+ / expr+ ) '}'
+declareassign = simple_id modifier? ':' full_id '=' expr
+declareassign_ds = simple_id modifier? ':' full_id '=' '.{' assignargs+ '}'
+cast = ( call / literal / full_id ) '*' full_id
+call = (trait_id '.')? full_id '(' args ')' modifier?
+args = ( callargs / cast / call / valonly )*
+assignargs = ( composite_id / simple_id ) '=' expr
+callargs = simple_id '=' valonly
+valonly = array / full_id / literal
+option = (( call / array / full_id ) ':' ( body / expr ))
+callwithbodyoptions = full_id '(' args ')' '{' option+ '}'
+callwithargsoptions = full_id '(' option+ ')'
+callwithbody = full_id '(' args ')' body
+
+array = '[' ( literal / composite_id_with_closure / full_id )* ']'
simple_id = r'@?[a-zA-Z][a-zA-Z0-9\-_]*'
composite_id = simple_id ('.' simple_id)+
-composite_id_with_closure = ( simple_id / composite_id ) '.' '{' ( simple_id / composite_id / composite_id_with_closure ) '}'
-modifier = '<' ( valonly+ / callargs+ ) '>'
-id = ( composite_id / simple_id ) modifier?
-
-literal = null / bool / str / int / float / q__bool / q__int
-null = 'null'
-bool = 'true' / 'false'
-str = r'"([^"]*)"'
-int = r'-?([1-9]\d*|0)'
-float = r'0(\.\d+)?|[1-9]\d*(\.\d+)?'
-q__bool = '@true' / '@false'
-q__int = r'-?\@([1-9]\d*|0)'
-
-comment = ( r'\/\/([^\n]*)\n' ) / ( r'\/\-.*?\-\/' )
\ No newline at end of file
+composite_id_with_closure = ( composite_id / simple_id ) '.' '{' ( composite_id_with_closure / composite_id / simple_id )+ '}'
+modifier = '<' ( ref / pointer / ( callargs / valonly )+ ) '>'
+trait_id = simple_id '#' full_id
+full_id = ( composite_id / simple_id ) modifier?
+
+ref = '&'
+pointer = '*'
+
+literal = ( float / null / bool / str / int / q__bool / q__int ) (':' composite_id)?
+t_null = 'null'
+t_bool = 'true' / 'false'
+t_str = r'"([^"]*)"'
+t_int = r'-?([1-9]\d*|0)'
+t_float = r'-?\d+\.\d+'
+qt_bool = '@true' / '@false'
+qt_int = r'-?\@([1-9]\d*|0)'
+
+comment = ( r'\/\/([^\n]*)\n' ) / ( r'\/\-.*?\-\/' )
diff --git a/python/src/hhat_lang/dialects/heather/grammar/type_grammar.py b/python/src/hhat_lang/dialects/heather/grammar/type_grammar.py
new file mode 100644
index 00000000..2371aa46
--- /dev/null
+++ b/python/src/hhat_lang/dialects/heather/grammar/type_grammar.py
@@ -0,0 +1,49 @@
+from __future__ import annotations
+
+from typing import Any
+
+from arpeggio import Kwd
+from arpeggio.peg import EOF, OneOrMore, ZeroOrMore
+
+from hhat_lang.dialects.heather.grammar.generic_grammar import (
+ id_composite_value,
+ many_import,
+ simple_id,
+ single_import,
+)
+
+
+def type_program() -> Any:
+ return (ZeroOrMore(imports), ZeroOrMore(type_file), EOF)
+
+
+def imports() -> Any:
+ return Kwd("use"), "(", OneOrMore(typeimport), ")"
+
+
+def typeimport() -> Any:
+ return Kwd("type"), ":", [single_import, many_import]
+
+
+def type_file() -> Any:
+ return Kwd("type"), [typesingle, typestruct, typeenum]
+
+
+def typesingle() -> Any:
+ return simple_id, ":", id_composite_value
+
+
+def typemember() -> Any:
+ return simple_id, ":", id_composite_value
+
+
+def typestruct() -> Any:
+ return simple_id, "{", ZeroOrMore(typemember), "}"
+
+
+def typeenum() -> Any:
+ return simple_id, "{", ZeroOrMore(enummember), "}"
+
+
+def enummember() -> Any:
+ return [simple_id, typestruct]
diff --git a/python/src/hhat_lang/dialects/heather/interpreter/classical/executor.py b/python/src/hhat_lang/dialects/heather/interpreter/classical/executor.py
index 56918a64..12abf8e1 100644
--- a/python/src/hhat_lang/dialects/heather/interpreter/classical/executor.py
+++ b/python/src/hhat_lang/dialects/heather/interpreter/classical/executor.py
@@ -8,7 +8,7 @@
from typing import Any
-from hhat_lang.core.code.ir import BodyIR, BlockIR, TypeIR, BaseFnIR
+from hhat_lang.core.code.ir import BaseFnIR, BlockIR, BodyIR, TypeIR
from hhat_lang.core.execution.abstract_base import BaseEvaluator
from hhat_lang.core.memory.core import MemoryManager
diff --git a/python/src/hhat_lang/dialects/heather/interpreter/quantum/program.py b/python/src/hhat_lang/dialects/heather/interpreter/quantum/program.py
index 80227206..31133394 100644
--- a/python/src/hhat_lang/dialects/heather/interpreter/quantum/program.py
+++ b/python/src/hhat_lang/dialects/heather/interpreter/quantum/program.py
@@ -17,7 +17,8 @@
The quantum program workflow is as follows:
-- Instructions are analyzed according to the low level language and target backend support (lower level counterparts, LLC)
+- Instructions are analyzed according to the low level language and target
+backend support (lower level counterparts, LLC)
- If classical instructions are supported, they will be handled by those
- If not, they will fall back into this dialect's classical branch interpreter
@@ -33,16 +34,15 @@
from __future__ import annotations
-from typing import Any, Callable, Type
+from typing import Any, Type
from hhat_lang.core.code.ir import BlockIR
-from hhat_lang.core.data.core import Symbol, WorkingData
+from hhat_lang.core.data.core import WorkingData
from hhat_lang.core.error_handlers.errors import ErrorHandler
from hhat_lang.core.execution.abstract_base import BaseEvaluator
from hhat_lang.core.execution.abstract_program import BaseProgram
from hhat_lang.core.lowlevel.abstract_qlang import BaseLowLevelQLang
-from hhat_lang.core.memory.core import IndexManager
-
+from hhat_lang.core.memory.core import BaseStack, IndexManager, Stack, SymbolTable
from hhat_lang.dialects.heather.code.simple_ir_builder.ir import IRBlock
# TODO: the imports below must come from the config file, not hardcoded
@@ -59,7 +59,17 @@ def __init__(
idx: IndexManager,
block: IRBlock,
executor: BaseEvaluator,
- qlang: Type[BaseLowLevelQLang[WorkingData, IRBlock | BlockIR, IndexManager, BaseEvaluator]],
+ symboltable: SymbolTable,
+ qlang: Type[ # type: ignore [type-arg]
+ BaseLowLevelQLang[
+ WorkingData,
+ IRBlock | BlockIR,
+ IndexManager,
+ BaseEvaluator,
+ Stack,
+ SymbolTable,
+ ]
+ ],
):
if (
isinstance(qdata, WorkingData)
@@ -70,10 +80,25 @@ def __init__(
self._idx = idx
self._block = block
self._executor = executor
- self._qlang = qlang(self._qdata, self._block, self._idx, self._executor)
+ self._qstack = Stack()
+ self._symbol = symboltable
+ self._qlang = qlang(
+ self._qdata,
+ self._block,
+ self._idx,
+ self._executor,
+ self._qstack,
+ self._symbol,
+ )
else:
- raise ValueError(f"Quantum program got invalid parameters: {qdata=} | {idx=} {block=}")
+ raise ValueError(
+ f"Quantum program got invalid parameters: {qdata=} | {idx=} {block=}"
+ )
+
+ @property
+ def qstack(self) -> BaseStack:
+ return self._qstack
def run(self, debug: bool = False) -> Any | ErrorHandler:
qlang_code = self._qlang.gen_program()
diff --git a/python/src/hhat_lang/dialects/heather/parsing/imports.py b/python/src/hhat_lang/dialects/heather/parsing/imports.py
deleted file mode 100644
index c5830481..00000000
--- a/python/src/hhat_lang/dialects/heather/parsing/imports.py
+++ /dev/null
@@ -1,47 +0,0 @@
-"""To handle the `imports` part, for both types and functions"""
-
-from __future__ import annotations
-
-import os
-from functools import reduce
-from operator import iconcat
-from pathlib import Path
-from typing import Any
-
-from hhat_lang.core.code.ast import AST
-
-from hhat_lang.dialects.heather.code.ast import (
- Imports,
- CompositeId,
- CompositeIdWithClosure
-)
-
-
-def parse_types(code: Any) -> Any:
- pass
-
-
-def parse_types_compositeid(code: CompositeId) -> Any:
- # get the type path from the code
- type_path = Path(*reduce(iconcat, code.value, ()))
- # join the type path with its full path from the project path
- type_path = Path(".").resolve() / "hhat_types" / type_path
- # add .hat for the type file name (which should be the last item in the tuple)
- file_name = type_path.name + ".hat"
- full_path = type_path.parent / file_name
-
- if full_path.exists():
- data = open(full_path, "r").read()
-
-
-def parse_types_compositeidwithclosure(code: CompositeIdWithClosure) -> Any:
- pass
-
-
-def parse_fns(code: Any) -> Any:
- pass
-
-
-def parse_imports(code: Imports) -> Any:
- pass
-
diff --git a/python/src/hhat_lang/dialects/heather/parsing/ir_visitor.py b/python/src/hhat_lang/dialects/heather/parsing/ir_visitor.py
new file mode 100644
index 00000000..ec0b017d
--- /dev/null
+++ b/python/src/hhat_lang/dialects/heather/parsing/ir_visitor.py
@@ -0,0 +1,812 @@
+"""Attempt to parse directly to IR"""
+
+from __future__ import annotations
+
+from itertools import chain
+from pathlib import Path
+from typing import Any, Callable, Mapping
+
+from arpeggio import (
+ NonTerminal,
+ ParserPython,
+ PTNodeVisitor,
+ SemanticActionResults,
+ Terminal,
+ visit_parse_tree,
+)
+
+from hhat_lang.core.code.base import BaseFnCheck
+from hhat_lang.core.code.new_ir import IRGraph
+from hhat_lang.core.data.core import (
+ CompositeLiteral,
+ CompositeSymbol,
+ CompositeWorkingData,
+ CoreLiteral,
+ Symbol,
+ WorkingData,
+)
+from hhat_lang.core.data.fn_def import FnDef
+from hhat_lang.core.error_handlers.errors import ErrorHandler
+from hhat_lang.core.imports import TypeImporter
+from hhat_lang.core.imports.importer import FnImporter
+from hhat_lang.core.types.abstract_base import BaseTypeDataStructure, QSize, Size
+from hhat_lang.core.types.builtin_types import builtins_types
+from hhat_lang.core.types.core import EnumDS, SingleDS, StructDS
+from hhat_lang.dialects.heather.code.simple_ir_builder.new_ir import (
+ IR,
+ ArgsBlock,
+ ArgsValuesBlock,
+ AssignInstr,
+ BodyBlock,
+ CallInstr,
+ CastInstr,
+ DeclareAssignInstr,
+ DeclareInstr,
+ IRBlock,
+ IRInstr,
+ ModifierArgsBlock,
+ ModifierBlock,
+ OptionBlock,
+ ReturnBlock,
+)
+from hhat_lang.dialects.heather.code.simple_ir_builder.new_ir_builder import build_ir
+from hhat_lang.dialects.heather.grammar import WHITESPACE
+from hhat_lang.dialects.heather.grammar.fn_grammar import fn_program
+from hhat_lang.dialects.heather.grammar.generic_grammar import comment
+from hhat_lang.dialects.heather.grammar.type_grammar import type_program
+from hhat_lang.dialects.heather.parsing.utils import FnsDict, ImportDicts, TypesDict
+
+#########################
+# PARSING WITH PEG FILE #
+#########################
+
+
+def read_grammar() -> str:
+ grammar_path = Path(__file__).parent.parent / "grammar" / "grammar.peg"
+
+ if grammar_path.exists():
+ return open(grammar_path, "r").read()
+
+ raise ValueError("No grammar found on the grammar directory.")
+
+
+############################
+# PARSING WITH PYTHON CODE #
+############################
+
+
+def parser_grammar_code(program_fn: Callable) -> ParserPython:
+ return ParserPython(
+ program_fn, comment_def=comment, ws=WHITESPACE, memoization=True
+ )
+
+
+###################
+# PARSER FUNCTION #
+###################
+
+
+def parse(
+ grammar_parser: Callable[[Callable], ParserPython],
+ program_rule: Callable,
+ raw_code: str,
+ project_root: Path,
+ module_path: Path,
+ ir_graph: IRGraph,
+) -> IR:
+ parse_tree = grammar_parser(program_rule).parse(raw_code)
+ return visit_parse_tree(
+ parse_tree,
+ ParserIRVisitor(
+ grammar_parser,
+ project_root,
+ module_path,
+ ir_graph,
+ ),
+ )
+
+
+###########################
+# PARSER IR VISITOR CLASS #
+###########################
+
+
+class ParserIRVisitor(PTNodeVisitor):
+ """Visitor for parsing using IR code logic instead of AST's"""
+
+ _root: Path
+ _module_path: Path
+ _ir_graph: IRGraph
+ _grammar: Callable[[Callable], ParserPython]
+
+ def __init__(
+ self,
+ grammar_parser: Callable[[Callable], ParserPython],
+ project_root: Path,
+ module_path: Path,
+ ir_graph: IRGraph,
+ ):
+ super().__init__()
+ self._grammar = grammar_parser
+ self._root = project_root
+ self._module_path = module_path
+ self._ir_graph = ir_graph
+
+ @property
+ def grammar(self) -> Callable[[Callable], ParserPython]:
+ return self._grammar
+
+ @property
+ def project_root(self) -> Path:
+ return self._root
+
+ @property
+ def module_path(self) -> Path:
+ return self._module_path
+
+ @property
+ def ir_graph(self) -> IRGraph:
+ return self._ir_graph
+
+ def visit_type_program(self, _: NonTerminal, child: SemanticActionResults) -> IR:
+ refs: dict[Symbol | CompositeSymbol, Path] = dict()
+ types: tuple[BaseTypeDataStructure, ...] | tuple = ()
+
+ for k in child:
+ match k:
+ case ImportDicts():
+ refs.update(k.types)
+
+ case BaseTypeDataStructure():
+ types += (k,)
+
+ case _:
+ print(f"[type-program] ?? {type(k)}")
+
+ visited_ir = build_ir(
+ path=self._module_path,
+ ref_types=refs,
+ ref_fns=dict(),
+ types=types,
+ fns=(),
+ main=None,
+ )
+
+ self._ir_graph.add_node(visited_ir)
+
+ return visited_ir
+
+ def visit_fn_program(self, _: NonTerminal, child: SemanticActionResults) -> IR:
+ main: BodyBlock | None = None
+ ref_types: dict[Symbol | CompositeSymbol, Path] = dict()
+ ref_fns: dict[BaseFnCheck, Path] = dict()
+ types: tuple[BaseTypeDataStructure, ...] | tuple = ()
+ fns: tuple[FnDef, ...] | tuple = ()
+
+ for k in child:
+ match k:
+ case IRBlock():
+ if isinstance(k, BodyBlock):
+ main = k
+
+ case ImportDicts():
+ ref_types.update(k.types)
+ ref_fns.update(k.fns)
+
+ case BaseTypeDataStructure():
+ types += (k,)
+
+ case FnDef():
+ fns += (k,)
+
+ case _:
+ print(f"[?] unknown child of type {type(k)}")
+
+ visited_ir = build_ir(
+ path=self._module_path,
+ ref_types=ref_types,
+ ref_fns=ref_fns,
+ types=types,
+ fns=fns,
+ main=main,
+ )
+
+ if isinstance(main, BodyBlock):
+ self._ir_graph.add_main_node(visited_ir)
+
+ else:
+ self._ir_graph.add_node(visited_ir)
+
+ return visited_ir
+
+ def visit_type_file(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> BaseTypeDataStructure:
+ return child[0]
+
+ def visit_typesingle(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> SingleDS | ErrorHandler:
+ # TODO: implement a better resolver to account for custom and circular imports;
+ # for now, just check if it's built-in.
+
+ btype = builtins_types[child[1]]
+ single = SingleDS(name=child[0], size=btype.size, qsize=btype.qsize)
+ return single.add_member(btype)
+
+ def visit_typemember(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> tuple[
+ Symbol | CompositeSymbol | BaseTypeDataStructure, Symbol | CompositeSymbol
+ ]:
+ # Fow now, it will try to fetch built-in types, otherwise it will
+ # save the check for later
+ member_type = builtins_types.get(child[1], child[1])
+ member_name = child[0]
+ return member_type, member_name
+
+ def visit_typestruct(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> BaseTypeDataStructure:
+ size, qsize = _fetch_struct_size_qsize(child[1:])
+ struct = StructDS(name=child[0], size=size, qsize=qsize)
+
+ # second, populate struct
+ for t, m in child[1:]:
+ if isinstance(t, BaseTypeDataStructure):
+ struct.add_member(t, m)
+
+ else:
+ struct.add_tmp_member(t, m)
+
+ return struct
+
+ def visit_enummember(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> Symbol | CompositeSymbol | BaseTypeDataStructure:
+ # should have just one
+ if len(child) != 1:
+ raise ValueError("enum member must be a single attribute")
+
+ return child[0]
+
+ def visit_typeenum(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> BaseTypeDataStructure:
+ size, qsize = _fetch_enum_size_qsize(child[1:])
+ enum_ds = EnumDS(name=child[0], size=size, qsize=qsize)
+
+ for member in child[1:]:
+ enum_ds.add_member(member)
+
+ return enum_ds
+
+ def visit_typeunion(self, _: NonTerminal, child: SemanticActionResults) -> Any:
+ raise NotImplementedError()
+
+ def visit_fns(self, _: NonTerminal, child: SemanticActionResults) -> FnDef:
+ if len(child) == 4:
+ return FnDef(
+ fn_name=child[0], fn_args=child[1], fn_type=child[2], fn_body=child[3]
+ )
+
+ return FnDef(fn_name=child[0], fn_args=child[1], fn_type=None, fn_body=child[2])
+
+ def visit_fnargs(self, _: NonTerminal, child: SemanticActionResults) -> ArgsBlock:
+ return ArgsBlock(*child)
+
+ def visit_argtype(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> ArgsValuesBlock:
+ return ArgsValuesBlock((child[0], child[1]))
+
+ def visit_fn_body(self, _: NonTerminal, child: SemanticActionResults) -> BodyBlock:
+ return BodyBlock(*child)
+
+ def visit_body(self, _: NonTerminal, child: SemanticActionResults) -> BodyBlock:
+ values: tuple[IRInstr | IRBlock, ...] | tuple = ()
+ for k in child:
+ match k:
+ case IRInstr():
+ values += (k,)
+
+ case IRBlock():
+ values += (k,)
+
+ case _:
+ print(f" -> something else: {k} ({type(k)})")
+
+ return BodyBlock(*values)
+
+ def visit_declare(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> DeclareInstr:
+ if len(child) == 2:
+ return DeclareInstr(var=child[0], var_type=child[1])
+
+ if len(child) == 3:
+ return DeclareInstr(
+ var=ModifierBlock(obj=child[0], args=child[1]), var_type=child[2]
+ )
+
+ raise ValueError("declaring variable must have only variable and its type")
+
+ def visit_assign(self, _: NonTerminal, child: SemanticActionResults) -> AssignInstr:
+ return AssignInstr(var=child[0], value=child[1])
+
+ def visit_assign_ds(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> AssignInstr:
+ return AssignInstr(var=child[0], value=ArgsBlock(*child[1:]))
+
+ def visit_declareassign(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> DeclareAssignInstr:
+ if len(child) == 3:
+ return DeclareAssignInstr(var=child[0], var_type=child[1], value=child[2])
+
+ if len(child) == 4:
+ return DeclareAssignInstr(
+ var=ModifierBlock(obj=child[0], args=child[1]),
+ var_type=child[2],
+ value=child[3],
+ )
+
+ raise ValueError("declaring and assigning cannot contain more than 4 elements")
+
+ def visit_declareassign_ds(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> Any:
+ if isinstance(child[1], ModifierArgsBlock):
+ return DeclareAssignInstr(
+ var=ModifierBlock(obj=child[0], args=child[1]),
+ var_type=child[2],
+ value=ArgsBlock(*child[3:]) if len(child) > 4 else child[3],
+ )
+
+ return DeclareAssignInstr(
+ var=child[0], var_type=child[1], value=ArgsBlock(*child[2:])
+ )
+
+ def visit_fn_return(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> ReturnBlock:
+ return ReturnBlock(*child)
+
+ def visit_expr(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> IRBlock | IRInstr | Symbol | CompositeSymbol:
+ # returning the child; there should exist only one element
+ return child[0]
+
+ def visit_cast(self, _: NonTerminal, child: SemanticActionResults) -> CastInstr:
+ return CastInstr(data=child[0], to_type=child[1])
+
+ def visit_call(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> CallInstr | ModifierBlock:
+ if len(child) == 1:
+ # only the caller is present
+ return CallInstr(name=child[0])
+
+ if len(child) == 2:
+ # possible cases: trait_id, args, or modifier
+ match child[1]:
+ # args option
+ case ArgsBlock() | ArgsValuesBlock():
+ return CallInstr(name=child[0], args=child[1])
+
+ # modifier option
+ case ModifierArgsBlock():
+ return ModifierBlock(obj=CallInstr(name=child[0]), args=child[1])
+
+ # trait_id option
+ case Symbol() | CompositeSymbol() | ModifierBlock():
+ raise NotImplementedError("trait not implemented yet")
+
+ case _:
+ raise NotImplementedError("unkown case")
+
+ if len(child) == 3:
+ # possible cases: trait_id and args, trait_id and modifier, args and modifier
+ match (child[1], child[2]):
+ # args and modifier
+ case (ArgsBlock() | ArgsValuesBlock(), ModifierArgsBlock()):
+ return ModifierBlock(
+ CallInstr(name=child[0], args=child[1]), args=child[2]
+ )
+
+ # trait and something
+ case _:
+ raise NotImplementedError()
+
+ if len(child) == 4:
+ # everything together :)
+ raise NotImplementedError()
+
+ raise ValueError("call cannot have len 0 or > 4")
+
+ def visit_trait_id(self, _: NonTerminal, child: SemanticActionResults) -> Any:
+ raise NotImplementedError()
+
+ def visit_args(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> ArgsBlock | ArgsValuesBlock:
+ argsvalues: tuple[ArgsValuesBlock, ...] | tuple = ()
+ args: (
+ tuple[WorkingData | CompositeWorkingData | IRInstr | ModifierBlock] | tuple
+ ) = ()
+
+ for k in child:
+ match k:
+ case ArgsValuesBlock():
+ argsvalues += (k,)
+
+ case IRInstr() | ModifierBlock():
+ args += (k,)
+
+ case WorkingData() | CompositeWorkingData():
+ args += (k,)
+
+ case _:
+ raise ValueError(f"unexpected value from args ({k}, {type(k)})")
+
+ if len(argsvalues) != 0 and len(args) != 0:
+ raise ValueError(
+ "arguments in functino call cannot mix key-value pairs with value only"
+ )
+
+ if args:
+ return ArgsBlock(*args)
+
+ return ArgsValuesBlock(*argsvalues)
+
+ def visit_assignargs(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> ArgsValuesBlock:
+ if len(child) == 2:
+ return ArgsValuesBlock((child[0], child[1]))
+
+ raise ValueError(
+ "assigning arg with value cannot have more than an argument and a value"
+ )
+
+ def visit_callargs(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> ArgsValuesBlock:
+ return ArgsValuesBlock((child[0], child[1]))
+
+ def visit_valonly(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> WorkingData | CompositeWorkingData:
+ return child[0]
+
+ def visit_option(self, _: NonTerminal, child: SemanticActionResults) -> OptionBlock:
+ return OptionBlock(*child[1:], block=child[0])
+
+ def visit_callwithbody(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> CallInstr:
+ raise NotImplementedError()
+
+ def visit_callwithbodyoptions(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> CallInstr:
+ args: tuple = ()
+ body: BodyBlock | None = None
+
+ for k in child[1:]:
+ match k:
+ case ArgsBlock() | ArgsValuesBlock():
+ args += (k,)
+ case BodyBlock():
+ body = k
+ case _:
+ raise ValueError(
+ f"unexpected value on call with body options {k} ({type(k)})"
+ )
+
+ args_entry = ArgsBlock(*args) if args else None
+ return CallInstr(name=child[0], args=args_entry, body=body)
+
+ def visit_callwithargsoptions(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> CallInstr:
+ return CallInstr(name=child[0], option=child[1])
+
+ def visit_id_composite_value(
+ self, node: NonTerminal, child: SemanticActionResults
+ ) -> CompositeSymbol | tuple:
+ # Id composite value should have only one value
+ return child[0]
+
+ def visit_imports(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> ImportDicts:
+ types = TypesDict()
+ fns = FnsDict()
+
+ for k in child:
+ match k:
+ case TypesDict():
+ types.update(k)
+
+ case FnsDict():
+ fns.update(k)
+
+ parsed_imports = ImportDicts(types=types, fns=fns)
+ return parsed_imports
+
+ def visit_typeimport(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> TypesDict:
+ if isinstance(child[0], tuple):
+ types = TypesDict()
+ importer = TypeImporter(self._root, self._grammar, type_program, parse)
+ res = importer.import_types(child[0], self._ir_graph)
+
+ for k, v in res.items():
+ types[k] = v
+
+ return types
+
+ raise ValueError("type import not tuple?")
+
+ def visit_fnimport(self, _: NonTerminal, child: SemanticActionResults) -> FnsDict:
+ if isinstance(child[0], tuple):
+ fns = FnsDict()
+ importer = FnImporter(self._root, self._grammar, fn_program, parse)
+ res = importer.import_fns(child[0], self._ir_graph)
+
+ for k, v in res.items():
+ fns[k] = v
+
+ return fns
+
+ raise ValueError("fn import no tuple?")
+
+ def visit_single_import(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> tuple[Symbol | CompositeSymbol] | tuple:
+ if len(child) > 1:
+ raise ValueError("single import cannot contain more than one import")
+
+ return child[0] if isinstance(child[0], tuple) else (child[0],)
+
+ def visit_many_import(self, _: NonTerminal, child: SemanticActionResults) -> tuple:
+ return tuple(chain.from_iterable(child))
+
+ def visit_main(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> IRBlock | tuple:
+ if len(child) == 0:
+ return ()
+
+ return BodyBlock(*child)
+
+ def visit_composite_id_with_closure(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> tuple[CompositeSymbol, ...] | tuple:
+ return _flatten_recursive_closure(child)
+
+ def visit_full_id(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> Symbol | CompositeSymbol | ModifierBlock:
+ if len(child) == 1:
+ return child[0]
+
+ if len(child) == 2:
+ return ModifierBlock(obj=child[0], args=child[1])
+
+ raise NotImplementedError("symbol with modifier not implemented yet")
+
+ def visit_modifier(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> ModifierArgsBlock:
+ return ModifierArgsBlock(tuple(k for k in child))
+
+ def visit_array(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> CompositeWorkingData:
+ raise NotImplementedError("array not implemented yet")
+
+ def visit_composite_id(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> CompositeSymbol:
+ return CompositeSymbol(value=_resolve_data_to_str(child))
+
+ def visit_simple_id(self, node: Terminal, _: None) -> Symbol:
+ return Symbol(value=node.value)
+
+ def visit_ref(self, node: Terminal, _: None) -> Symbol:
+ return Symbol(value=node.value, symbol_type="`ref")
+
+ def visit_pointer(self, node: Terminal, _: None) -> Symbol:
+ return Symbol(value=node.value, symbol_type="`pointer")
+
+ def visit_literal(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> CoreLiteral | CompositeLiteral:
+ if len(child) == 1:
+ return child[0]
+
+ if len(child) == 2:
+ if isinstance(child[0], CoreLiteral) and isinstance(
+ child[1], Symbol | CompositeSymbol
+ ):
+ return CoreLiteral(value=child[0].value, lit_type=child[1].value)
+
+ raise ValueError(f"unknown literal {child}")
+
+ def visit_complex(
+ self, _: NonTerminal, child: SemanticActionResults
+ ) -> CompositeLiteral:
+ raise NotImplementedError("complex type not implemented yet")
+
+ def visit_t_null(self, node: Terminal, _: None) -> CoreLiteral:
+ return CoreLiteral(value=node.value, lit_type="null")
+
+ def visit_t_bool(self, node: Terminal, _: None) -> CoreLiteral:
+ return CoreLiteral(value=node.value, lit_type="bool")
+
+ def visit_t_str(self, node: NonTerminal, _: None) -> CoreLiteral:
+ return CoreLiteral(value=node.value, lit_type="str")
+
+ def visit_t_int(self, node: Terminal, _: None) -> CoreLiteral:
+ return CoreLiteral(value=node.value, lit_type="int")
+
+ def visit_t_float(self, node: Terminal, _: None) -> CoreLiteral:
+ return CoreLiteral(value=node.value, lit_type="float")
+
+ def visit_t_imag(self, node: Terminal, _: None) -> CoreLiteral:
+ return CoreLiteral(value=node.value, lit_type="imag")
+
+ def visit_qt_bool(self, node: Terminal, _: None) -> CoreLiteral:
+ return CoreLiteral(value=node.value, lit_type="@bool")
+
+ def visit_qt_int(self, node: Terminal, _: None) -> CoreLiteral:
+ return CoreLiteral(value=node.value, lit_type="@int")
+
+
+def _resolve_data_to_str(
+ data: SemanticActionResults | tuple | WorkingData | CompositeWorkingData | str,
+) -> tuple | tuple[str, ...]:
+ match data:
+ case WorkingData():
+ return (data.value,)
+
+ case CompositeWorkingData():
+ return _resolve_data_to_str(data.value)
+
+ case SemanticActionResults() | tuple():
+ pure_data: tuple | tuple[str, ...] = ()
+
+ for k in data:
+ if isinstance(k, WorkingData):
+ pure_data += (k.value,)
+
+ elif isinstance(k, str):
+ pure_data += (k,)
+
+ elif isinstance(k, CompositeWorkingData):
+ pure_data += k.value
+
+ return pure_data
+
+ case str():
+ return (data,)
+
+ case _:
+ raise NotImplementedError()
+
+
+def _flatten_recursive_closure(
+ data: (
+ SemanticActionResults
+ | tuple[str | Symbol | CompositeSymbol | list | tuple, ...]
+ ),
+) -> tuple | tuple[CompositeSymbol, ...]:
+ members: tuple | tuple[CompositeSymbol, ...] = ()
+ parent: str | WorkingData | CompositeWorkingData | None = None
+ composite_members: tuple[CompositeSymbol, ...] | tuple = ()
+
+ for n, k in enumerate(data):
+ if n == 0:
+ if isinstance(k, Symbol):
+ parent = k.value
+ continue
+ elif isinstance(k, str):
+ parent = k
+ continue
+
+ if isinstance(k, SemanticActionResults | tuple):
+ members += _flatten_recursive_closure(k)
+
+ else:
+ members += (_resolve_data_to_str(k),)
+
+ if parent is None:
+ return members
+
+ for k in members:
+ composite_members += (CompositeSymbol(_resolve_data_to_str(parent) + k),) # type: ignore [operator]
+
+ return composite_members
+
+
+def _fetch_struct_size_qsize(
+ obj: (
+ SemanticActionResults | tuple[Symbol | CompositeSymbol | BaseTypeDataStructure]
+ ),
+) -> tuple[Size | None, QSize | None]:
+ """
+ Fetch size and qsize attributes for struct data type. If members are not built-in types,
+ it will be skipped and be resolved later on, when all the types are defined.
+
+ Args:
+ obj: the parser holder of type members
+
+ Returns:
+ A tuple of ``size`` and ``qsize``. They can be ``Size`` or ``None``, and
+ ``QSize`` or ``None``, respectively.
+ """
+
+ count_size = 0
+ count_qsize_min = 0
+ count_qsize_max = 0
+
+ # first, count the size and qsize
+ for member_type, member_name in obj:
+ if isinstance(member_type, BaseTypeDataStructure):
+ count_size += member_type.size.size
+ count_qsize_min += member_type.qsize.min
+ count_qsize_max += member_type.qsize.max or 0
+
+ size = Size(count_size) if count_size > 0 else None
+ qsize = (
+ None
+ if count_qsize_min == 0 and count_qsize_max == 0
+ else QSize(count_qsize_min, count_qsize_max or None)
+ )
+ return size, qsize
+
+
+def _fetch_enum_size_qsize(
+ obj: (
+ SemanticActionResults | tuple[Symbol | CompositeSymbol | BaseTypeDataStructure]
+ ),
+) -> tuple[Size | None, QSize | None]:
+ """
+ Fetch size and qsize attributes for enum data type. If members are not built-in types,
+ it will be skipped and be resolved later on, when all the types are defined.
+
+ Args:
+ obj: the parser holder of type members
+
+ Returns:
+ A tuple of ``size`` and ``qsize``. They can be ``Size`` or ``None``, and
+ ``QSize`` or ``None``, respectively.
+ """
+
+ count_size = 0
+ count_qsize_min = 0
+ count_qsize_max = 0
+
+ for member in obj:
+ if isinstance(member, BaseTypeDataStructure):
+ if member.size.size > count_size:
+ count_size = member.size.size
+
+ if member.qsize.min > count_qsize_min:
+ count_qsize_min = member.qsize.min
+
+ if member.qsize.max is not None and member.qsize.max > count_qsize_max:
+ count_qsize_max = member.qsize.max
+
+ size = Size(count_size) if count_size > 0 else None
+ qsize = (
+ None
+ if count_qsize_min == 0
+ else QSize(count_qsize_min, count_qsize_max or None)
+ )
+ return size, qsize
diff --git a/python/src/hhat_lang/dialects/heather/parsing/run.py b/python/src/hhat_lang/dialects/heather/parsing/run.py
deleted file mode 100644
index 378ee156..00000000
--- a/python/src/hhat_lang/dialects/heather/parsing/run.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from __future__ import annotations
-
-from pathlib import Path
-
-from arpeggio import visit_parse_tree
-from arpeggio.cleanpeg import ParserPEG
-
-from hhat_lang.core.code.ast import AST
-
-from hhat_lang.dialects.heather.grammar import WHITESPACE
-from hhat_lang.dialects.heather.parsing.visitor import ParserVisitor
-
-
-def read_grammar() -> str:
- grammar_path = Path(__file__).parent.parent / "grammar" / "grammar.peg"
-
- if grammar_path.exists():
- return open(grammar_path, "r").read()
-
- raise ValueError("No grammar found on the grammar directory.")
-
-
-def parse_grammar() -> ParserPEG:
- grammar = read_grammar()
- return ParserPEG(
- language_def=grammar,
- root_rule_name="program",
- comment_rule_name="comment",
- reduce_tree=True,
- ws=WHITESPACE
- )
-
-
-def parse(raw_code: str) -> AST:
- parser = parse_grammar()
- parse_tree = parser.parse(raw_code)
- return visit_parse_tree(parse_tree, ParserVisitor())
-
-
-def parse_file(file: str | Path) -> AST:
- with open(file, "r") as f:
- data = f.read()
-
- return parse(data)
diff --git a/python/src/hhat_lang/dialects/heather/parsing/utils.py b/python/src/hhat_lang/dialects/heather/parsing/utils.py
new file mode 100644
index 00000000..f0605ca2
--- /dev/null
+++ b/python/src/hhat_lang/dialects/heather/parsing/utils.py
@@ -0,0 +1,119 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from pathlib import Path
+from typing import Any, Iterable, Iterator
+
+from hhat_lang.core.code.base import BaseFnCheck
+from hhat_lang.core.data.core import Symbol
+from hhat_lang.core.imports.utils import BaseImports
+
+
+class ImportDicts(BaseImports):
+ def __init__(self, types: TypesDict, fns: FnsDict):
+ if isinstance(types, TypesDict) and isinstance(fns, FnsDict):
+ self.types = types
+ self.fns = fns
+
+
+class TypesDict(Mapping):
+ """
+ A special dict-like class that holds types definitions, with key as
+ ``CompositeSymbol`` and value as ``BaseTypeDataStructure`` object.
+ """
+
+ _data: dict[Symbol, Path]
+
+ def __init__(self, data: dict | None = None):
+ self._data = data if isinstance(data, dict) else dict()
+
+ def __setitem__(self, key: Symbol, value: Path) -> None:
+ if isinstance(key, Symbol) and isinstance(value, Path):
+ self._data[key] = value
+
+ else:
+ raise ValueError(f"{key} ({type(key)}) is not valid key for types")
+
+ def __getitem__(self, key: Symbol, /) -> Path:
+ if isinstance(key, Symbol):
+ return self._data[key]
+
+ raise KeyError(key)
+
+ def __len__(self) -> int:
+ return len(self._data)
+
+ def items(self) -> Iterator:
+ yield from self._data.items()
+
+ def keys(self) -> Iterator:
+ return iter(self._data.keys())
+
+ def values(self) -> Iterator:
+ return iter(self._data.values())
+
+ def update(self, data: Mapping) -> None:
+ self._data.update({k: v for k, v in data.items()})
+
+ def __iter__(self) -> Iterable:
+ yield from self._data.keys()
+
+ def __repr__(self) -> str:
+ return str(self._data)
+
+
+class FnsDict(Mapping):
+ """
+ A special dict-like class that holds functions definitions, with key
+ as ``BaseFnKey`` and value as ``FnDef`` object.
+ """
+
+ _data: dict[BaseFnCheck, Path]
+
+ def __init__(self, data: dict | None = None):
+ self._data = data if isinstance(data, dict) else dict()
+
+ def __setitem__(self, key: BaseFnCheck, value: Path) -> None:
+ if isinstance(key, BaseFnCheck) and isinstance(value, Path):
+ if key.name in self._data:
+ self._data[key] = value
+
+ else:
+ self._data.update({key: value})
+
+ else:
+ raise ValueError(f"{key} ({type(key)}) is not valid key for types")
+
+ def __getitem__(self, key: BaseFnCheck, /) -> Path:
+ if isinstance(key, BaseFnCheck):
+ return self._data[key]
+
+ raise KeyError(key)
+
+ def __len__(self) -> int:
+ return len(self._data)
+
+ def _items(self) -> Iterable:
+ return iter(self._data.items())
+
+ def items(self) -> Iterable:
+ return iter(self._data.items())
+
+ def keys(self) -> Iterator:
+ return iter(self._data.keys())
+
+ def values(self) -> Iterator:
+ return iter(self._data.values())
+
+ def update(self, data: Mapping) -> None:
+ self._data.update({k: v for k, v in data.items()})
+
+ def __iter__(self) -> Iterable:
+ """Iterates over the (BaseFnKey, FnDef) pairs"""
+ yield from self._items()
+
+ def __contains__(self, item: Any) -> bool:
+ return item in self._data.keys()
+
+ def __repr__(self) -> str:
+ return str(self._data)
diff --git a/python/src/hhat_lang/dialects/heather/parsing/visitor.py b/python/src/hhat_lang/dialects/heather/parsing/visitor.py
deleted file mode 100644
index 4e10e160..00000000
--- a/python/src/hhat_lang/dialects/heather/parsing/visitor.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from __future__ import annotations
-
-from arpeggio import PTNodeVisitor, NonTerminal, SemanticActionResults
-
-from hhat_lang.core.code.ast import AST
-
-from hhat_lang.dialects.heather.code.ast import (
- Program,
- Main,
- Imports,
- TypeImport,
- TypeDef,
- TypeMember,
- Id,
- CompositeId,
- ArgValuePair,
- ArgTypePair,
- SingleTypeMember,
- EnumTypeMember,
-)
-
-
-class ParserVisitor(PTNodeVisitor):
- def visit_program(self, node: NonTerminal, child: SemanticActionResults) -> AST:
- pass
diff --git a/python/src/hhat_lang/low_level/quantum_lang/openqasm/v2/__init__.py b/python/src/hhat_lang/low_level/quantum_lang/openqasm/v2/__init__.py
index 02b0f978..983a0afb 100644
--- a/python/src/hhat_lang/low_level/quantum_lang/openqasm/v2/__init__.py
+++ b/python/src/hhat_lang/low_level/quantum_lang/openqasm/v2/__init__.py
@@ -1,6 +1,5 @@
from __future__ import annotations
-
DEFAULT_HEADER = """
OPENQASM 2.0;
include "qelib1.inc";
diff --git a/python/src/hhat_lang/low_level/quantum_lang/openqasm/v2/instructions.py b/python/src/hhat_lang/low_level/quantum_lang/openqasm/v2/instructions.py
index fe8e643d..83d7247c 100644
--- a/python/src/hhat_lang/low_level/quantum_lang/openqasm/v2/instructions.py
+++ b/python/src/hhat_lang/low_level/quantum_lang/openqasm/v2/instructions.py
@@ -2,50 +2,101 @@
from typing import Any
-from hhat_lang.core.code.instructions import QInstr, CInstr
+from hhat_lang.core.code.instructions import CInstr, QInstr, QInstrFlag
from hhat_lang.core.code.utils import InstrStatus
+from hhat_lang.core.data.core import (
+ CompositeLiteral,
+ CompositeMixData,
+ CoreLiteral,
+ Symbol,
+)
+from hhat_lang.core.data.variable import BaseDataContainer
+from hhat_lang.core.error_handlers.errors import (
+ HeapInvalidKeyError,
+ IndexUnknownError,
+)
from hhat_lang.core.execution.abstract_base import BaseEvaluator
-
+from hhat_lang.core.memory.core import MemoryDataTypes
+from hhat_lang.core.utils import Error, Ok, Result
##########################
# CLASSICAL INSTRUCTIONS #
##########################
+
class If(CInstr):
name = "if"
@staticmethod
- def _instr(cond_test: str, instr: str) -> str:
+ def _instr(cond_test: str | tuple[str, ...], instr: str | tuple[str, ...]) -> str:
return f"if({cond_test}) {instr};"
def _translate_instrs(
self,
- cond_test: tuple[str, ...],
- instrs: tuple[str, ...],
- **kwargs: Any
+ cond_test: tuple[MemoryDataTypes],
+ instrs: tuple[MemoryDataTypes],
+ **kwargs: Any,
) -> tuple[tuple[str, ...], InstrStatus]:
"""
Translate `If` instruction. Number of condition tests (`cond_test`) must
match the number of instructions (`instrs`).
"""
- return (
- tuple(
- self._instr(c, i) for c, i in zip(cond_test, instrs)
- ),
- InstrStatus.DONE
- )
+ transformed_instrs: tuple[str, ...] = ()
+
+ for c, i in zip(cond_test, instrs):
+ c_value: str | tuple[str, ...]
+
+ match c:
+ case BaseDataContainer():
+ c_value = c.name.value
+
+ case CoreLiteral() | Symbol():
+ c_value = c.value
+
+ case CompositeLiteral() | CompositeMixData():
+ raise NotImplementedError()
+
+ case _:
+ raise NotImplementedError()
+
+ i_value: str | tuple[str, ...]
+
+ match i:
+ case BaseDataContainer():
+ i_value = i.name.value
+
+ case CoreLiteral() | Symbol():
+ i_value = i.value
+
+ case CompositeLiteral() | CompositeMixData():
+ raise NotImplementedError()
+
+ case _:
+ raise NotImplementedError()
+
+ transformed_instrs += (self._instr(c_value, i_value),)
+
+ return transformed_instrs, InstrStatus.DONE
def __call__(
- self,
- *,
- executor: BaseEvaluator,
- **kwargs: Any
+ self, *, executor: BaseEvaluator, **kwargs: Any
) -> tuple[tuple[str, ...], InstrStatus]:
"""Transforms `if` instruction to openQASMv2.0 code."""
self._instr_status = InstrStatus.RUNNING
- instrs, status = self._translate_instrs(**kwargs)
+
+ # conditional test must be in the first position of the stack
+ cond_test = executor.mem.stack.pop()
+ cond_test_tuple = cond_test if isinstance(cond_test, tuple) else (cond_test,)
+
+ # instructions must be in the following position of the stack
+ if_instrs = executor.mem.stack.pop()
+ if_instrs_tuple = if_instrs if isinstance(if_instrs, tuple) else (if_instrs,)
+
+ instrs, status = self._translate_instrs(
+ cond_test=cond_test_tuple, instrs=if_instrs_tuple
+ )
self._instr_status = status
return instrs, status
@@ -54,6 +105,7 @@ def __call__(
# QUANTUM INSTRUCTIONS #
########################
+
class QRedim(QInstr):
name = "@redim"
@@ -62,16 +114,12 @@ def _instr(idx: int) -> str:
return f"h q[{idx}];"
def _translate_instrs(
- self,
- idxs: tuple[int, ...]
+ self, idxs: tuple[int, ...]
) -> tuple[tuple[str, ...], InstrStatus]:
return tuple(self._instr(k) for k in idxs), InstrStatus.DONE
def __call__(
- self,
- *,
- idxs: tuple[int, ...],
- **_kwargs: Any
+ self, *, idxs: tuple[int, ...], **_kwargs: Any
) -> tuple[tuple[str, ...], InstrStatus]:
"""Transforms `@redim` instruction to openQASMv2.0 code"""
@@ -89,8 +137,7 @@ def _instr(idxs: tuple[int, ...]) -> str:
return f"cx q[{idxs[0]}], q[{idxs[1]}];"
def _translate_instrs(
- self,
- idxs: tuple[tuple[int, ...], ...]
+ self, idxs: tuple[tuple[int, ...], ...]
) -> tuple[tuple[str, ...], InstrStatus]:
return tuple(self._instr(k) for k in idxs), InstrStatus.DONE
@@ -99,7 +146,7 @@ def __call__(
*,
idxs: tuple[tuple[int, ...], ...],
executor: BaseEvaluator,
- **_kwargs: Any
+ **_kwargs: Any,
) -> tuple[tuple[str, ...], InstrStatus]:
"""Transforms `@sync` instruction to openQASMv2.0 code."""
@@ -118,11 +165,7 @@ class QIf(QInstr):
name = "@if"
def __call__(
- self,
- *,
- idxs: tuple[int, ...],
- executor: BaseEvaluator,
- **kwargs: Any
+ self, *, idxs: tuple[int, ...], executor: BaseEvaluator, **kwargs: Any
) -> tuple[tuple[str, ...], InstrStatus]:
"""Transforms `@if` instruction to openQASMv2.0 code."""
@@ -130,3 +173,141 @@ def __call__(
self._instr_status = InstrStatus.RUNNING
raise NotImplementedError()
+
+
+class QNot(QInstr):
+ name = "@not"
+
+ @staticmethod
+ def _instr(idx: int) -> str:
+ return f"x q[{idx}];"
+
+ def _translate_instrs(
+ self, idxs: tuple[int, ...]
+ ) -> tuple[tuple[str, ...], InstrStatus]:
+ return tuple(self._instr(k) for k in idxs), InstrStatus.DONE
+
+ def __call__(
+ self, *, idxs: tuple[int, ...], **_kwargs: Any
+ ) -> tuple[tuple[str, ...], InstrStatus]:
+ """Transforms `@not` instruction to openQASMv2.0 code"""
+ self._instr_status = InstrStatus.RUNNING
+ instrs, status = self._translate_instrs(idxs)
+ self._instr_status = status
+ return instrs, status
+
+
+class QNez(QInstr):
+ """Quantum not-equal-zero instruction."""
+
+ name = "@nez"
+ flag = QInstrFlag.SKIP_GEN_ARGS
+
+ @staticmethod
+ def _get_mask_idxs(
+ mask: CoreLiteral | BaseDataContainer | Symbol,
+ num_idxs: int,
+ executor: BaseEvaluator | None = None,
+ ) -> Result:
+ """Return indexes from ``mask`` that are non-zero.
+
+ If ``mask`` is a variable or a symbol reference to a variable, the
+ current value is fetched from ``executor``'s memory manager.
+ """
+
+ match mask:
+ case CoreLiteral():
+ lit = mask
+
+ case Symbol() if mask.value in ("@true", "@false"):
+ bool_val = "@1" if mask.value == "@true" else "@0"
+ lit = CoreLiteral(bool_val, "@bool")
+
+ case BaseDataContainer() | Symbol():
+ if executor is None:
+ return Error(IndexUnknownError())
+
+ var = executor.mem.heap[mask if isinstance(mask, Symbol) else mask.name]
+
+ if isinstance(var, HeapInvalidKeyError):
+ return Error(var)
+
+ val = var.get(var.type if hasattr(var, "type") else None)
+
+ if isinstance(val, list):
+ val = val[-1]
+
+ if not isinstance(val, CoreLiteral):
+ return Error(IndexUnknownError())
+
+ lit = val
+
+ case _:
+ return Error(IndexUnknownError())
+
+ mask_bits = lit.bin[::-1]
+ idxs: tuple[int, ...] = tuple(
+ i for i, bit in enumerate(mask_bits) if bit == "1" and i < num_idxs
+ )
+
+ return Ok(idxs)
+
+ @staticmethod
+ def _instr(idx: int, body_instr: QInstr) -> str:
+ if hasattr(body_instr, "_instr"):
+ return body_instr._instr(idx) # type: ignore[attr-defined]
+ raise NotImplementedError("body instruction missing '_instr' method")
+
+ def _translate_instrs(
+ self,
+ idxs: tuple[int, ...],
+ mask: CoreLiteral | BaseDataContainer | Symbol,
+ body_instr: QInstr,
+ executor: BaseEvaluator | None = None,
+ **kwargs: Any,
+ ) -> tuple[tuple[str, ...], InstrStatus]:
+ """Translate ``@nez`` instruction."""
+
+ mask_res = self._get_mask_idxs(mask, len(idxs), executor)
+
+ match mask_res:
+ case Ok():
+ mask_idxs = mask_res.result()
+
+ case Error():
+ # error while obtaining mask indexes
+ return (mask_res.result(),), InstrStatus.ERROR # type: ignore[return-value]
+
+ case _:
+ return tuple(), InstrStatus.ERROR
+
+ if not mask_idxs:
+ return tuple(), InstrStatus.DONE
+
+ selected = tuple(idxs[i] for i in mask_idxs)
+ return (
+ tuple(self._instr(i, body_instr) for i in selected),
+ InstrStatus.DONE,
+ )
+
+ def __call__(
+ self,
+ *,
+ idxs: tuple[int, ...],
+ mask: CoreLiteral | BaseDataContainer | Symbol,
+ body_instr: QInstr,
+ executor: BaseEvaluator | None = None,
+ **kwargs: Any,
+ ) -> tuple[tuple[str, ...], InstrStatus]:
+ """Transforms ``@nez`` instruction to OpenQASM v2.0 code."""
+
+ self._instr_status = InstrStatus.RUNNING
+ instrs, status = self._translate_instrs(
+ idxs=idxs,
+ mask=mask,
+ body_instr=body_instr,
+ executor=executor,
+ **kwargs,
+ )
+ self._instr_status = status
+ return instrs, status
diff --git a/python/src/hhat_lang/low_level/quantum_lang/openqasm/v2/qlang.py b/python/src/hhat_lang/low_level/quantum_lang/openqasm/v2/qlang.py
index 879c599d..c31e7e00 100644
--- a/python/src/hhat_lang/low_level/quantum_lang/openqasm/v2/qlang.py
+++ b/python/src/hhat_lang/low_level/quantum_lang/openqasm/v2/qlang.py
@@ -1,25 +1,28 @@
from __future__ import annotations
-from typing import Any, Callable
-
import importlib
import inspect
+from typing import Any, Callable, Iterable, cast
-from hhat_lang.core.code.ir import BlockIR, InstrIR, TypeIR, InstrIRFlag
+from hhat_lang.core.code.instructions import QInstrFlag
from hhat_lang.core.code.utils import InstrStatus
from hhat_lang.core.data.core import (
- Symbol, CoreLiteral, CompositeSymbol, CompositeLiteral,
- CompositeMixData
+ CompositeLiteral,
+ CompositeMixData,
+ CompositeSymbol,
+ CoreLiteral,
+ Symbol,
)
from hhat_lang.core.data.variable import BaseDataContainer
-from hhat_lang.core.error_handlers.errors import ErrorHandler, InstrNotFoundError, InstrStatusError
+from hhat_lang.core.error_handlers.errors import (
+ ErrorHandler,
+ InstrNotFoundError,
+ InstrStatusError,
+)
from hhat_lang.core.execution.abstract_base import BaseEvaluator
from hhat_lang.core.lowlevel.abstract_qlang import BaseLowLevelQLang
-from hhat_lang.core.memory.core import IndexManager, MemoryManager
-from hhat_lang.core.utils import Result, Ok, Error
-from hhat_lang.dialects.heather.code.ast import Literal
-
-from hhat_lang.dialects.heather.code.simple_ir_builder.ir import IRBlock, IRInstr, IRArgs
+from hhat_lang.core.utils import Error, Ok, Result
+from hhat_lang.dialects.heather.code.simple_ir_builder.new_ir import IRBlock, IRInstr
class LowLeveQLang(BaseLowLevelQLang):
@@ -39,31 +42,64 @@ def end_qlang(self) -> tuple[str, ...]:
# TODO: check whether some qubits were previously measured and
# handle the rest appropriately
- return "measure q -> c;",
+ return ("measure q -> c;",)
+
+ def _gen_literal_int(self, literal: CoreLiteral) -> tuple[str, ...]:
+ if literal in self._idx:
+ (literal.type)
+ return tuple(f"x q[{n}];" for n, k in enumerate(literal.bin) if k == "1")
+
+ return ("",)
- def gen_literal(self, literal: CoreLiteral, **_kwargs: Any) -> tuple[str, ...] | ErrorHandler:
+ def _gen_literal_bool(self, literal: CoreLiteral) -> tuple[str, ...]:
+ return tuple("x q[")
+
+ def gen_literal(
+ self, literal: CoreLiteral, **_kwargs: Any
+ ) -> tuple[str, ...] | ErrorHandler:
"""Generate QASM code from literal data"""
+ match literal.type:
+ case "@int" | "@u2" | "@u3" | "@u4":
+ return self._gen_literal_int(literal)
+
+ case "@bool":
+ return self._gen_literal_bool(literal)
+
+ case _:
+ raise NotImplementedError(
+ "Generating quantum literal not implemented yet."
+ )
+
return tuple(f"x q[{n}];" for n, k in enumerate(literal.bin) if k == "1")
def gen_var(
- self,
- var: BaseDataContainer,
- executor: BaseEvaluator
+ self, var: BaseDataContainer | Symbol, executor: BaseEvaluator
) -> tuple[str, ...] | ErrorHandler:
"""Generate QASM code from variable data"""
- var_data = executor.mem.heap[var.name]
- code_tuple = ()
-
- for member, data in var_data:
+ var_data = executor.mem.heap[var if isinstance(var, Symbol) else var.name]
+ code_tuple: tuple[str, ...] = ()
+ for member, data in cast(Iterable[tuple[Any, Any]], var_data):
match data:
case Symbol():
- code_tuple += self.gen_var(data, executor=self._executor)
+ d_res = self.gen_var(data, executor=self._executor)
+
+ if isinstance(d_res, tuple):
+ code_tuple += d_res
+
+ else:
+ return d_res
case CoreLiteral():
- code_tuple += self.gen_literal(data)
+ d_res = self.gen_literal(data)
+
+ if isinstance(d_res, tuple):
+ code_tuple += d_res
+
+ else:
+ return d_res
case CompositeSymbol():
# TODO: implement it
@@ -77,9 +113,8 @@ def gen_var(
# TODO: implement it
raise NotImplementedError()
- case InstrIR():
-
- match res := self.gen_instrs(data, executor=self._executor):
+ case IRInstr():
+ match res := self.gen_instrs(instr=data, executor=self._executor):
case Ok():
code_tuple += res.result()
@@ -91,18 +126,28 @@ def gen_var(
return code_tuple
-
- def gen_args(self, args: tuple[Any, ...], **kwargs: Any) -> Result:
- code_tuple = ()
+ def gen_args(self, args: tuple[Any, ...], **kwargs: Any) -> Result | ErrorHandler:
+ code_tuple: tuple[str, ...] = ()
for k in args:
-
match k:
case Symbol():
- code_tuple += self.gen_var(k, executor=self._executor)
+ res = self.gen_var(k, executor=self._executor)
+
+ if isinstance(res, tuple):
+ code_tuple += res
+
+ else:
+ return res
case CoreLiteral():
- code_tuple += self.gen_literal(k)
+ res = self.gen_literal(k)
+
+ if isinstance(res, tuple):
+ code_tuple += res
+
+ else:
+ return res
case CompositeSymbol():
# TODO: implement it
@@ -116,17 +161,16 @@ def gen_args(self, args: tuple[Any, ...], **kwargs: Any) -> Result:
# TODO: implement it
raise NotImplementedError()
- case InstrIR():
-
- match res := self.gen_instrs(k, **kwargs):
+ case IRInstr():
+ match instr_res := self.gen_instrs(instr=k, **kwargs):
case Ok():
- code_tuple += res.result()
+ code_tuple += instr_res.result()
case Error():
- return res.result()
+ return instr_res.result()
case ErrorHandler():
- return res
+ return instr_res
case _:
# unknown case, needs investigation
@@ -135,9 +179,7 @@ def gen_args(self, args: tuple[Any, ...], **kwargs: Any) -> Result:
return Ok(code_tuple)
def gen_instrs(
- self,
- instr: InstrIR | BlockIR,
- **kwargs: Any
+ self, *, instr: IRInstr | IRBlock, **kwargs: Any
) -> Result | ErrorHandler:
"""
Transforms each of the instructions into an OpenQASM v2 code or
@@ -152,18 +194,47 @@ def gen_instrs(
A tuple with OpenQASM v2 code strings
"""
+ if not isinstance(instr, IRInstr):
+ return InstrNotFoundError(getattr(instr, "name", None))
+
instr_module = importlib.import_module(
name="hhat_lang.low_level.quantum_lang.openqasm.v2.instructions",
)
for name, obj in inspect.getmembers(instr_module, inspect.isclass):
-
- if (x:= getattr(obj, "name", False)) and x == instr.name:
- res_instr, res_status = obj()(
- idxs=self._idx.in_use_by[self._qdata],
- executor=self._executor,
+ if (x := getattr(obj, "name", False)) and x == instr.name:
+ skip_gen = (
+ getattr(obj, "flag", QInstrFlag.NONE) == QInstrFlag.SKIP_GEN_ARGS
)
+ if skip_gen:
+ args: tuple[Any, ...] = tuple(cast(Iterable[Any], instr.args))
+ if len(args) != 2:
+ return InstrStatusError(instr.name)
+
+ mask, body = args
+
+ body_cls = None
+ for n, o in inspect.getmembers(instr_module, inspect.isclass):
+ if getattr(o, "name", False) == body:
+ body_cls = o
+ break
+
+ if body_cls is None:
+ return InstrNotFoundError(body)
+
+ res_instr, res_status = obj()(
+ idxs=self._idx.in_use_by[self._qdata],
+ mask=mask,
+ body_instr=body_cls(),
+ executor=self._executor,
+ )
+ else:
+ res_instr, res_status = obj()(
+ idxs=self._idx.in_use_by[self._qdata],
+ executor=self._executor,
+ )
+
if res_status == InstrStatus.DONE:
return Ok(res_instr)
@@ -173,14 +244,13 @@ def gen_instrs(
# back to H-hat dialect to execute it
else:
# TODO: falls back to dialect execution
- pass
+ raise NotImplementedError(
+ f"low-level qlang instr error: {x} ({type(x)})"
+ )
return InstrNotFoundError(instr.name)
- def gen_program(
- self,
- **kwargs: Any
- ) -> str:
+ def gen_program(self, **kwargs: Any) -> str:
"""
Produces the program as a string code written in OpenQASM v2.
@@ -191,17 +261,31 @@ def gen_program(
A string with the OpenQASM v2 code.
"""
- code = ""
- code += "\n".join(self.init_qlang()) + "\n"
+ body_code = ""
- for instr in self._code:
+ instr_module = importlib.import_module(
+ "hhat_lang.low_level.quantum_lang.openqasm.v2.instructions"
+ )
- if instr.args:
+ for instr in self._code: # type: ignore [attr-defined]
+ instr_cls = None
+ for name, obj in inspect.getmembers(instr_module, inspect.isclass):
+ if getattr(obj, "name", False) == instr.name:
+ instr_cls = obj
+ break
+
+ skip_gen = False
+ if instr_cls is not None:
+ skip_gen = (
+ getattr(instr_cls, "flag", QInstrFlag.NONE)
+ == QInstrFlag.SKIP_GEN_ARGS
+ )
+ if instr.args and not skip_gen:
match gen_args := self.gen_args(instr.args):
-
case Ok():
- code += "\n".join(gen_args.result()) + "\n"
+ if gen_args.result():
+ body_code += "\n".join(gen_args.result()) + "\n"
# TODO: implement it better
case Error():
@@ -211,13 +295,10 @@ def gen_program(
raise gen_args
match gen_instr := self.gen_instrs(
- instr=instr,
- idx=self._idx,
- executor=self._executor
- ):
-
+ instr=instr, idx=self._idx, executor=self._executor
+ ):
case Ok():
- code += "\n".join(gen_instr.result())
+ body_code += "\n".join(gen_instr.result())
case Error():
raise gen_instr.result()
@@ -226,6 +307,12 @@ def gen_program(
case ErrorHandler():
raise gen_instr
+ if not body_code:
+ return ""
+
+ code = ""
+ code += "\n".join(self.init_qlang()) + "\n\n"
+ code += body_code
code += "\n"
code += "\n".join(self.end_qlang()) + "\n"
return code
diff --git a/python/src/hhat_lang/low_level/target_backend/qiskit/openqasm/code_executor.py b/python/src/hhat_lang/low_level/target_backend/qiskit/openqasm/code_executor.py
index d0434d3c..130d7d8d 100644
--- a/python/src/hhat_lang/low_level/target_backend/qiskit/openqasm/code_executor.py
+++ b/python/src/hhat_lang/low_level/target_backend/qiskit/openqasm/code_executor.py
@@ -2,8 +2,8 @@
from typing import Any
-from qiskit import qasm2, QuantumCircuit, transpile
-from qiskit.primitives.containers.pub_result import PubResult, DataBin
+from qiskit import QuantumCircuit, qasm2, transpile
+from qiskit.primitives.containers.pub_result import DataBin, PubResult
# TODO: to set the configuration's simulator instead of a fixed simulator
from qiskit_aer import AerSimulator
@@ -11,8 +11,8 @@
from hhat_lang.core.data.core import Symbol, WorkingData
from hhat_lang.core.error_handlers.errors import (
+ ErrorHandler,
InvalidQuantumComputedResult,
- ErrorHandler
)
@@ -22,7 +22,7 @@ def load_qasm(code: str) -> QuantumCircuit:
def sample_circuit(
circuit: QuantumCircuit,
- qdata: str | Symbol,
+ qdata: str | WorkingData,
metadata: dict[str, Any] | None = None,
) -> Any | ErrorHandler:
"""
@@ -44,17 +44,18 @@ def sample_circuit(
if job_res:
pub_res: PubResult = job_res[0]
databin: DataBin = pub_res.data
- res = (getattr(databin, "c", None) or getattr(databin, "meas", None)).get_counts()
- return res
+ res = getattr(databin, "c", None) or getattr(databin, "meas", None)
+
+ if res is not None:
+ res = res.get_counts()
+ return res
# job_res is None, then something went wrong
return InvalidQuantumComputedResult(qdata)
def execute_program(
- code: str,
- qdata: str | WorkingData,
- debug: bool = False
+ code: str, qdata: str | WorkingData, debug: bool = False
) -> Any | ErrorHandler:
"""
Execute the quantum program from a quantum data `qdata`. First, it is passed as a
@@ -67,7 +68,6 @@ def execute_program(
res = sample_circuit(circ, qdata)
match res:
-
# in case it had an error
case InvalidQuantumComputedResult():
# TODO: define properly what to do next
@@ -75,7 +75,6 @@ def execute_program(
# should contain the counts with bitstrings as keys (`Counter`?)
case _:
-
if debug:
print(res)
diff --git a/python/src/hhat_lang/toolchain/cli/cli.py b/python/src/hhat_lang/toolchain/cli/cli.py
new file mode 100644
index 00000000..bb1bb3c1
--- /dev/null
+++ b/python/src/hhat_lang/toolchain/cli/cli.py
@@ -0,0 +1,262 @@
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from typing import Optional
+
+import typer
+from rich import print
+from rich.console import Console
+from rich.panel import Panel
+
+from hhat_lang.toolchain.project.new import (
+ create_new_file,
+ create_new_project,
+ create_new_type_file,
+)
+from hhat_lang.toolchain.project.run import run_project
+
+
+def get_proj_dir() -> Path:
+ current = Path().absolute()
+ while current != current.parent:
+ if (current / "src" / "main.hat").exists():
+ return current
+ current = current.parent
+ raise ValueError("Not inside a H-hat project directory or src/main.hat missing")
+
+
+app = typer.Typer(
+ name="hat",
+ help="Command line interface for H-hat language toolchain",
+ no_args_is_help=True,
+)
+
+console = Console()
+
+
+def version_callback(value: bool):
+ if value:
+ print("[blue]H-hat Language Toolchain[/blue] version 0.1.0")
+ raise typer.Exit()
+
+
+@app.callback()
+def common(
+ version: bool = typer.Option(
+ None,
+ "--version",
+ "-v",
+ help="Show version and exit.",
+ callback=version_callback,
+ is_eager=True,
+ ),
+):
+ """
+ H-hat Language Toolchain - A quantum programming language toolchain
+
+ Use 'hat help ' for detailed information about a command.
+ """
+ pass
+
+
+@app.command()
+def help(command: Optional[str] = typer.Argument(None, help="Command to get help for")):
+ """
+ Show help about commands.
+
+ Examples:
+ hat help # Show all available commands
+ hat help new # Show help for the new command
+ hat help run # Show help for the run command
+ """
+ if command is None:
+ console.print(
+ Panel.fit(
+ "[bold]H-hat Language Toolchain[/bold]\n\n"
+ "[bold]Available commands:[/bold]\n"
+ " [bold]new[/bold] Create a new project, file, or type file\n"
+ " [bold]run[/bold] Run the current H-hat project\n"
+ " [bold]help[/bold] Show this help message\n\n"
+ "Use [bold]hat help [/bold] for detailed information about a command.",
+ title="hat - Command Line Interface",
+ border_style="blue",
+ )
+ )
+ else:
+ # Simulate --help flag for the specified command
+ sys.argv = ["hat", command, "--help"]
+ try:
+ app()
+ except SystemExit:
+ pass
+
+
+@app.command()
+def new(
+ project_name: Optional[str] = typer.Argument(
+ None, help="Name of the project to create"
+ ),
+ file_name: str = typer.Option(None, "--file", "-f", help="Create a new file"),
+ type_file: str = typer.Option(None, "--type", "-t", help="Create a new type file"),
+):
+ """
+ Create a new project, file, or type file.
+
+ This command can create:
+ - A new H-hat project with required structure
+ - A new .hat file within a project
+ - A new type definition file within a project
+
+ Examples:
+ hat new myproject # Create a new project
+ hat new -f module/myfile # Create a new file (and directories if needed)
+ hat new -t custom_type # Create a new type file
+ """
+ try:
+ if project_name and not (file_name or type_file):
+ # Create new project
+ create_new_project(Path(project_name))
+ console.print(
+ Panel(
+ f"Project [bold]{project_name}[/bold] created successfully!\n\n"
+ f"To get started, run:\n"
+ f" cd {project_name}\n"
+ f" hat run",
+ title="✓ Success",
+ border_style="green",
+ )
+ )
+
+ elif file_name:
+ try:
+ proj_dir = get_proj_dir()
+ except ValueError as e:
+ console.print(
+ Panel(
+ str(e)
+ + "\n\nPlease make sure you're inside a H-hat project directory.",
+ title="⚠ Error",
+ border_style="red",
+ )
+ )
+ raise typer.Exit(1)
+ else:
+ file_path = Path(file_name)
+ if (proj_dir / f"{file_path}.hat").is_file():
+ raise FileExistsError(f"File {file_path}.hat already exists")
+ if file_path.parent != Path("."):
+ file_path.parent.mkdir(parents=True, exist_ok=False)
+ create_new_file(proj_dir, f"{file_path}.hat")
+ console.print(
+ Panel(
+ f"File [bold]{file_name}.hat[/bold] created successfully!",
+ title="✓ Success",
+ border_style="green",
+ )
+ )
+
+ elif type_file:
+ proj_dir = get_proj_dir()
+ try:
+ create_new_type_file(proj_dir, Path(type_file))
+ console.print(
+ Panel(
+ f"Type file [bold]{type_file}.hat[/bold] created successfully!",
+ title="✓ Success",
+ border_style="green",
+ )
+ )
+ except ValueError as e:
+ console.print(
+ Panel(
+ str(e)
+ + "\n\nPlease make sure you're inside a H-hat project directory.",
+ title="⚠ Error",
+ border_style="red",
+ )
+ )
+ raise typer.Exit(1)
+
+ else:
+ console.print(
+ Panel(
+ "Please specify what to create (project, file, or type)\n\n"
+ "Examples:\n"
+ " hat new myproject # Create a new project\n"
+ " hat new -f module/myfile # Create a new file\n"
+ " hat new -t custom_type # Create a new type file",
+ title="⚠ Missing Arguments",
+ border_style="yellow",
+ )
+ )
+ raise typer.Exit(1)
+
+ except FileExistsError as e:
+ console.print(
+ Panel(
+ f"{str(e)}\n\nPlease choose a different name or remove the existing one.",
+ title="⚠ Error",
+ border_style="red",
+ )
+ )
+ raise typer.Exit(1)
+ except Exception as e:
+ console.print(
+ Panel(
+ f"An unexpected error occurred: {str(e)}\n\n"
+ "If this persists, please report it as an issue.",
+ title="⚠ Error",
+ border_style="red",
+ )
+ )
+ raise typer.Exit(1)
+
+
+@app.command()
+def run():
+ """
+ Run the current H-hat project.
+
+ This command must be executed from within a H-hat project directory
+ that contains a main.hat file.
+
+ Example:
+ hat run # Run the current project
+ """
+ try:
+ # make sure we are in the proj dir, throw err if not
+ get_proj_dir()
+ run_project()
+ console.print(
+ Panel(
+ "Project executed successfully!",
+ title="✓ Success",
+ border_style="green",
+ )
+ )
+ except FileNotFoundError:
+ console.print(
+ Panel(
+ "main.hat not found in current directory.\n\n"
+ "Make sure you're in a H-hat project directory with a main.hat file.",
+ title="⚠ Error",
+ border_style="red",
+ )
+ )
+ raise typer.Exit(1)
+ except Exception as e:
+ console.print(
+ Panel(
+ f"An error occurred while running the project: {str(e)}\n\n"
+ "Please check your code for errors.",
+ title="⚠ Error",
+ border_style="red",
+ )
+ )
+ raise typer.Exit(1)
+
+
+def main():
+ """Entry point for the CLI"""
+ app()
diff --git a/python/src/hhat_lang/toolchain/project/__init__.py b/python/src/hhat_lang/toolchain/project/__init__.py
index e69de29b..98885d40 100644
--- a/python/src/hhat_lang/toolchain/project/__init__.py
+++ b/python/src/hhat_lang/toolchain/project/__init__.py
@@ -0,0 +1,17 @@
+from __future__ import annotations
+
+# base folder names
+SOURCE_FOLDER_NAME = "src"
+TYPES_FOLDER_NAME = "hat_types"
+DOCS_FOLDER_NAME = "docs"
+TESTS_FOLDER_NAME = "tests"
+PROOFS_FOLDER_NAME = "proofs"
+
+# files
+MAIN_FILE_NAME = "main.hat"
+
+MAIN_DOC_FILE_NAME = f"{MAIN_FILE_NAME}.md"
+
+# paths
+SOURCE_TYPES_PATH = f"{SOURCE_FOLDER_NAME}/{TYPES_FOLDER_NAME}"
+DOCS_TYPES_PATH = f"{DOCS_FOLDER_NAME}/{TYPES_FOLDER_NAME}"
diff --git a/python/src/hhat_lang/toolchain/project/new.py b/python/src/hhat_lang/toolchain/project/new.py
index af32a160..d1135470 100644
--- a/python/src/hhat_lang/toolchain/project/new.py
+++ b/python/src/hhat_lang/toolchain/project/new.py
@@ -6,16 +6,33 @@
from pathlib import Path
from typing import Any
+from hhat_lang.toolchain.project import (
+ DOCS_FOLDER_NAME,
+ DOCS_TYPES_PATH,
+ MAIN_DOC_FILE_NAME,
+ MAIN_FILE_NAME,
+ SOURCE_FOLDER_NAME,
+ SOURCE_TYPES_PATH,
+ TESTS_FOLDER_NAME,
+)
from hhat_lang.toolchain.project.utils import str_to_path
+def _is_project_scope(project_name: str | Path, some_path: Path) -> bool:
+ project_name = str_to_path(project_name)
+
+ if some_path.is_relative_to(project_name):
+ return True
+
+ return False
+
+
######################
# CREATE NEW PROJECT #
######################
-def create_new_project(project_name: str | Path) -> Any:
- project_name = str_to_path(project_name)
+def create_new_project(project_name: Path) -> Any:
_create_template_folders(project_name)
_create_template_files(project_name)
@@ -25,36 +42,55 @@ def _create_template_folders(project_name: Path) -> Any:
os.mkdir(project_name)
# create project template structure
- os.mkdir(project_name / "src")
- os.mkdir(project_name / "src" / "hat_types")
- os.mkdir(project_name / "src" / "hat_docs")
- os.mkdir(project_name / "src" / "hat_docs" / "hat_types")
- os.mkdir(project_name / "tests")
+ os.mkdir(project_name / SOURCE_FOLDER_NAME)
+ os.mkdir(project_name / SOURCE_TYPES_PATH)
+ os.mkdir(project_name / DOCS_FOLDER_NAME)
+ os.mkdir(project_name / DOCS_TYPES_PATH)
+ os.mkdir(project_name / TESTS_FOLDER_NAME)
# os.mkdir(project_name / "proofs") # TODO: once proofs are incorporated, include them
def _create_template_files(project_name: Path) -> Any:
- open(project_name / "src" / "main.hat", "w").close()
- open(project_name / "src" / "hat_docs" / "main.hat.md", "w").close()
+ open(project_name / SOURCE_FOLDER_NAME / MAIN_FILE_NAME, "w").close()
+ open(project_name / DOCS_FOLDER_NAME / MAIN_DOC_FILE_NAME, "w").close()
###################
# CREATE NEW FILE #
###################
-def create_new_file(project_name: str | Path, file_name: str | Path) -> Any:
- project_name = str_to_path(project_name)
- file_name = str_to_path(file_name)
- doc_file = file_name.parent / (file_name.name + ".md")
- open(project_name / file_name, "w").close()
- open(project_name / doc_file, "w").close()
+def create_new_file(project_root: Path, file_name: str | Path) -> Path:
+ file_name = str(file_name) + ".hat"
+ doc_file = file_name + ".md"
+ file_path: Path = project_root / SOURCE_FOLDER_NAME / file_name
+ if file_path.parent != Path("."):
+ file_path.parent.mkdir(parents=True, exist_ok=True)
-def create_new_type_file(project_name: str | Path, file_name: str | Path) -> Any:
- project_name = str_to_path(project_name)
- file_name = str_to_path(file_name)
- doc_file = file_name.parent / (file_name.name + ".md")
+ doc_path = project_root / DOCS_FOLDER_NAME / doc_file
+ if doc_path.parent != Path("."):
+ doc_path.parent.mkdir(parents=True, exist_ok=True)
+
+ open(file_path, "w").close()
+ open(doc_path, "w").close()
+
+ return file_path
+
+
+def create_new_type_file(project_path: Path, file_name: str | Path) -> Path:
+ file_name = str(file_name) + ".hat"
+ doc_file = file_name + ".md"
+
+ file_path: Path = project_path / SOURCE_TYPES_PATH / file_name
+ if file_path.parent != Path("."):
+ file_path.parent.mkdir(parents=True, exist_ok=True)
+
+ doc_path = project_path / DOCS_TYPES_PATH / doc_file
+ if doc_path.parent != Path("."):
+ doc_path.parent.mkdir(parents=True, exist_ok=True)
+
+ open(file_path, "w").close()
+ open(doc_path, "w").close()
- open(project_name / "src" / "hat_types" / file_name, "w").close()
- open(project_name / "src" / "hat_docs" / "hat_types" / doc_file, "w").close()
+ return file_path
diff --git a/python/src/hhat_lang/toolchain/project/run.py b/python/src/hhat_lang/toolchain/project/run.py
new file mode 100644
index 00000000..ed9e7b42
--- /dev/null
+++ b/python/src/hhat_lang/toolchain/project/run.py
@@ -0,0 +1,5 @@
+from __future__ import annotations
+
+
+def run_project():
+ raise NotImplementedError("no implementation yet")
diff --git a/python/tests/core/test_type_ds.py b/python/tests/core/test_type_ds.py
index f2e30cb9..d7adf3c5 100644
--- a/python/tests/core/test_type_ds.py
+++ b/python/tests/core/test_type_ds.py
@@ -2,15 +2,15 @@
from collections import OrderedDict
-from hhat_lang.core.data.core import CoreLiteral, Symbol
+from hhat_lang.core.data.core import CompositeSymbol, CoreLiteral, Symbol
from hhat_lang.core.error_handlers.errors import (
TypeAndMemberNoMatchError,
TypeQuantumOnClassicalError,
VariableWrongMemberError,
)
-from hhat_lang.core.types.builtin import QU3, U32
-from hhat_lang.core.types.core import SingleDS, StructDS
-
+from hhat_lang.core.types.builtin_types import QU3, U32, U64
+from hhat_lang.core.types.core import EnumDS, SingleDS, StructDS
+from hhat_lang.core.types.utils import BaseTypeEnum
# TODO: refactor the types to use `BuiltinSingleDS` or respective data
# types so properties can be compared and addressed properly.
@@ -21,7 +21,8 @@ def test_single_ds() -> None:
user_type1 = SingleDS(name=Symbol("user_type1"))
user_type1.add_member(U32)
- var1 = user_type1(lit_108, var_name=Symbol("var1"))
+ var1 = user_type1(var_name=Symbol("var1"))
+ var1.assign(lit_108)
assert var1.name == Symbol("var1")
assert var1.type == Symbol("user_type1")
@@ -35,9 +36,14 @@ def test_single_ds() -> None:
def test_single_ds_quantum() -> None:
lit_q2 = CoreLiteral("@2", "@u3")
+ # type @type1:@u3
qtype1 = SingleDS(name=Symbol("@type1"))
qtype1.add_member(QU3)
- qvar1 = qtype1(lit_q2, var_name=Symbol("@var1"))
+
+ # @var1:@type1
+ qvar1 = qtype1(var_name=Symbol("@var1"))
+ # @var1 = @2:@u3
+ qvar1.assign(lit_q2)
assert qvar1.name == Symbol("@var1")
assert qvar1.type == Symbol("@type1")
@@ -60,7 +66,8 @@ def test_struct_ds() -> None:
point = StructDS(name=Symbol("point"))
point.add_member(U32, Symbol("x")).add_member(U32, Symbol("y"))
- p = point(lit_25, lit_17, var_name=Symbol("p"))
+ p = point(var_name=Symbol("p"))
+ p.assign(x=lit_25, y=lit_17)
assert p.name == Symbol("p")
assert p.type == Symbol("point")
@@ -75,17 +82,82 @@ def test_struct_ds_quantum() -> None:
lit_8 = CoreLiteral("8", "u32")
lit_q2 = CoreLiteral("@2", "@u3")
+ # type @sample {counts:u32 @d:@u3}
qsample = StructDS(name=Symbol("@sample"))
- qsample.add_member(U32, Symbol("counts")).add_member(QU3, Symbol("@d"))
- qvar = qsample(lit_8, lit_q2, var_name=Symbol("@var"))
+ (qsample.add_member(U32, Symbol("counts")).add_member(QU3, Symbol("@d")))
+
+ # @var:@sample
+ qvar = qsample(var_name=Symbol("@var"))
+ # @var.{8 @2:@u3}
+ qvar.assign(lit_8, lit_q2)
assert qvar.name == Symbol("@var")
assert qvar.type == Symbol("@sample")
+ assert qvar._ds_type == BaseTypeEnum.STRUCT
assert qvar.is_quantum is True
assert qvar.data == OrderedDict({Symbol("counts"): lit_8, Symbol("@d"): [lit_q2]})
assert qvar.get(Symbol("counts")) == lit_8 and qvar.get(Symbol("@d")) == [lit_q2]
+ # @var2:@sample
+ qvar2 = qsample(var_name=Symbol("@var2"))
+ # @var2.{counts=8 @d=@2:@u3}
+ qvar2.assign(counts=lit_8, q__d=lit_q2)
+
+ assert qvar2.name == Symbol("@var2")
+ assert qvar2.type == Symbol("@sample")
+ assert qvar2.is_quantum is True
+ assert qvar2.data == OrderedDict({Symbol("counts"): lit_8, Symbol("@d"): [lit_q2]})
+ assert qvar2.get(Symbol("counts")) == lit_8 and qvar2.get(Symbol("@d")) == [lit_q2]
+
def test_struct_ds_quantum_wrong() -> None:
qtype = StructDS(name=Symbol("@type"))
assert isinstance(qtype.add_member(QU3, Symbol("data")), TypeAndMemberNoMatchError)
+
+
+def test_enum_ds() -> None:
+ connect_enum = CompositeSymbol(("command", "CONNECT"))
+ _connect = Symbol("CONNECT")
+ _join = Symbol("JOIN")
+ _quit = Symbol("QUIT")
+
+ # type command {CONNECT JOIN QUIT}
+ command = EnumDS(name=Symbol("command"))
+ command.add_member(_connect).add_member(_join).add_member(_quit)
+
+ # opt:command
+ opt = command(var_name=Symbol("opt"))
+ # opt=command.CONNECT
+ opt.assign(connect_enum)
+
+ assert opt.name == Symbol("opt")
+ assert opt.type == Symbol("command")
+ assert opt._ds_type is BaseTypeEnum.ENUM
+ assert opt.data == OrderedDict({0: _connect})
+ assert opt.get() == _connect
+ assert opt.get("z") == _connect
+ assert opt.is_quantum is False
+
+
+def test_enum_ds_with_struct() -> None:
+ _none = Symbol("NONE")
+ _res = StructDS(name=Symbol("RESULT")).add_member(U64, Symbol("result"))
+
+ # type option {NONE RESULT{result:u64}}
+ option = EnumDS(name=Symbol("option"))
+ option.add_member(_none).add_member(_res)
+
+ # var:option
+ var = option(var_name=Symbol("var"))
+
+ res_val = _res(var_name=_res.name).assign(CoreLiteral("16", "u64"))
+ # var=option.RESULT.{16:u64}
+ var.assign(res_val)
+
+ assert var.name == Symbol("var")
+ assert var.type == Symbol("option")
+ assert var._ds_type is BaseTypeEnum.ENUM
+ assert var.data == OrderedDict({0: res_val})
+ assert var.get() == res_val
+ assert var.get("z") == res_val
+ assert var.is_quantum is False
diff --git a/python/tests/dialects/heather/code/test_ssa_ir.py b/python/tests/dialects/heather/code/test_ssa_ir.py
deleted file mode 100644
index e51e40b7..00000000
--- a/python/tests/dialects/heather/code/test_ssa_ir.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from __future__ import annotations
-
-import pytest
-
-from hhat_lang.core.data.core import Symbol
-
-from hhat_lang.dialects.heather.code.ssa_ir_builder.ir import SSA, SSAPhi, IRVar
-
-
-# TODO: include IRModifier tests
-
-
-def test_ssa() -> None:
- s = Symbol("s")
- assert SSA(s)
-
- ssa = SSA(s)
-
- # SSA only accepts Symbol
- with pytest.raises(ValueError):
- SSA(SSAPhi(ssa, ssa))
-
- # to get SSA out of SSAPhi, uses `get_ssa` method
- assert SSA.get_ssa(SSAPhi(ssa, ssa))
-
- # SSAPhi only accepts SSA
- with pytest.raises(AttributeError):
- SSAPhi(s, s)
-
-
-def test_irvar() -> None:
- s = Symbol("s")
- irs = IRVar(s)
- irs.push(s)
- irs.push(s)
- irs.push(s)
- s2, s3 = irs.data[-2:]
- irs.push(SSAPhi(s2, s3))
-
- assert len(irs) == 4
- assert irs[-1].phi == SSAPhi(s2, s3), "IRVar index does not contain phi."
- assert len(irs) -1 == irs[-1].idx, "IRVar index does not match SSA index."
diff --git a/python/tests/dialects/heather/interpreter/quantum/test_program.py b/python/tests/dialects/heather/interpreter/quantum/test_program.py
deleted file mode 100644
index 331c2e6e..00000000
--- a/python/tests/dialects/heather/interpreter/quantum/test_program.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from __future__ import annotations
-
-from itertools import product
-
-import pytest
-
-from hhat_lang.core.code.ir import TypeIR, InstrIRFlag
-from hhat_lang.core.data.core import Symbol, CoreLiteral
-from hhat_lang.core.memory.core import MemoryManager
-from hhat_lang.dialects.heather.code.simple_ir_builder.ir import IRBlock, FnIR, IRInstr, IRArgs
-from hhat_lang.dialects.heather.interpreter.classical.executor import Evaluator
-from hhat_lang.dialects.heather.interpreter.quantum.program import Program
-from hhat_lang.low_level.quantum_lang.openqasm.v2.qlang import LowLeveQLang
-
-
-def test_simple_empty_redim_program(MAX_ATOL_STATES_GATE: float) -> None:
- qv = Symbol("@v")
-
- mem = MemoryManager(5)
- mem.idx.add(qv, 1)
- mem.idx.request(qv)
-
- ex = Evaluator(mem, TypeIR(), FnIR())
-
- block = IRBlock()
- block.add_instr(IRInstr(Symbol("@redim"), IRArgs(), InstrIRFlag.CALL))
-
- program = Program(qdata=qv, idx=mem.idx, block=block, qlang=LowLeveQLang, executor=ex)
-
- res = program.run(debug=False)
- assert (abs(res["1"] - res["0"])/(res["1"] + res["0"])) < MAX_ATOL_STATES_GATE
-
-
-@pytest.mark.parametrize(
- "ql",
- [CoreLiteral("@0", "@u2"), CoreLiteral("@2", "@u2")]
-)
-def test_simple_literal_redim_program(ql: CoreLiteral, MAX_ATOL_STATES_GATE: float) -> None:
- mem = MemoryManager(5)
- mem.idx.add(ql, 2)
- mem.idx.request(ql)
-
- ex = Evaluator(mem, TypeIR(), FnIR())
-
- block = IRBlock()
- block.add_instr(IRInstr(Symbol("@redim"), IRArgs(ql), InstrIRFlag.CALL))
-
- program = Program(qdata=ql, idx=mem.idx, block=block, qlang=LowLeveQLang, executor=ex)
-
- res = program.run(debug=False)
-
- assert {"".join(k) for k in product("01", repeat=2)} == set(res.keys())
- assert all(abs(1/4 - k/sum(res.values())) < MAX_ATOL_STATES_GATE for k in res.values())
diff --git a/python/tests/dialects/heather/parsing/ex_fn01.hat b/python/tests/dialects/heather/parsing/ex_fn01.hat
index 3620b96e..a8127f70 100644
--- a/python/tests/dialects/heather/parsing/ex_fn01.hat
+++ b/python/tests/dialects/heather/parsing/ex_fn01.hat
@@ -1 +1 @@
-fn sum (a:u64 b:u64) u64 { add(a b) }
+fn sum (a:u64 b:u64) u64 { ::add(a b) }
diff --git a/python/tests/dialects/heather/parsing/ex_fn02.hat b/python/tests/dialects/heather/parsing/ex_fn02.hat
index df9dc457..30f927fa 100644
--- a/python/tests/dialects/heather/parsing/ex_fn02.hat
+++ b/python/tests/dialects/heather/parsing/ex_fn02.hat
@@ -1 +1 @@
-fn @sum (@a:@u3 @b:@u3) @u3 { @add(@a @b) }
+fn @sum (@a:@u3 @b:@u3) @u3 { ::@add(@a @b) }
diff --git a/python/tests/dialects/heather/parsing/ex_main02.hat b/python/tests/dialects/heather/parsing/ex_main02.hat
index be62d9b7..1dd75593 100644
--- a/python/tests/dialects/heather/parsing/ex_main02.hat
+++ b/python/tests/dialects/heather/parsing/ex_main02.hat
@@ -1,4 +1,4 @@
main {
// add numbers
print(add(1 2))
-}
\ No newline at end of file
+}
diff --git a/python/tests/dialects/heather/parsing/ex_main03.hat b/python/tests/dialects/heather/parsing/ex_main03.hat
new file mode 100644
index 00000000..56332f5d
--- /dev/null
+++ b/python/tests/dialects/heather/parsing/ex_main03.hat
@@ -0,0 +1,3 @@
+use (type:geometry.euclidian.space)
+
+main {}
diff --git a/python/tests/dialects/heather/parsing/ex_main04.hat b/python/tests/dialects/heather/parsing/ex_main04.hat
new file mode 100644
index 00000000..d329b0b5
--- /dev/null
+++ b/python/tests/dialects/heather/parsing/ex_main04.hat
@@ -0,0 +1,3 @@
+use (type:geometry.{euclidian.space differential.form})
+
+main {}
diff --git a/python/tests/dialects/heather/parsing/ex_main05.hat b/python/tests/dialects/heather/parsing/ex_main05.hat
new file mode 100644
index 00000000..d7893661
--- /dev/null
+++ b/python/tests/dialects/heather/parsing/ex_main05.hat
@@ -0,0 +1,15 @@
+use (
+ type:[
+ geometry.{euclidian2.{line plane} differential2.normal}
+ std.{io.socket base.@sync_bool}
+ ]
+ fn:math.{sin floor}
+)
+
+main {
+ l:line=.{x=41}
+ p:plane
+ p.{x=250 y=600}
+ print(sin(0.0))
+ @d:@sync_bool=.{@source=@true @target=@false}
+}
diff --git a/python/tests/dialects/heather/parsing/test_parse.py b/python/tests/dialects/heather/parsing/test_parse.py
deleted file mode 100644
index f2f6c75f..00000000
--- a/python/tests/dialects/heather/parsing/test_parse.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from __future__ import annotations
-
-import pytest
-from pathlib import Path
-
-from hhat_lang.dialects.heather.parsing.run import (
- parse_grammar,
- parse,
- parse_file,
-)
-
-THIS = Path(__file__).parent
-
-
-def test_parse_grammar() -> None:
- assert parse_grammar()
-
-
-@pytest.mark.parametrize(
- "hat_file",
- ["ex_type01.hat", "ex_type02.hat"]
-)
-def test_parse_type_sample_file(hat_file) -> None:
- hat_file = (THIS / hat_file).resolve()
- assert parse_file(hat_file)
-
-
-@pytest.mark.parametrize(
- "hat_file",
- ["ex_fn01.hat", "ex_fn02.hat"]
-)
-def test_parse_fn_sample_file(hat_file) -> None:
- hat_file = (THIS / hat_file).resolve()
- assert parse_file(hat_file)
-
-
-@pytest.mark.parametrize(
- "hat_file",
- ["ex_main01.hat", "ex_main02.hat"]
-)
-def test_parse_main_sample_file(hat_file) -> None:
- hat_file = (THIS / hat_file).resolve()
- assert parse_file(hat_file)
diff --git a/python/tests/dialects/heather/parsing/test_parse_with_ir.py b/python/tests/dialects/heather/parsing/test_parse_with_ir.py
new file mode 100644
index 00000000..062dd0f3
--- /dev/null
+++ b/python/tests/dialects/heather/parsing/test_parse_with_ir.py
@@ -0,0 +1,208 @@
+from __future__ import annotations
+
+import cProfile
+import os
+import shutil
+from copy import deepcopy
+from pathlib import Path
+from pstats import SortKey, Stats, StatsProfile
+from typing import Callable
+
+import pytest
+
+from hhat_lang.core.code.abstract import IRHash
+from hhat_lang.core.code.new_ir import IRGraph
+from hhat_lang.dialects.heather.grammar.fn_grammar import fn_program
+from hhat_lang.dialects.heather.parsing.ir_visitor import parse, parser_grammar_code
+from hhat_lang.toolchain.project.new import (
+ create_new_file,
+ create_new_project,
+ create_new_type_file,
+)
+
+THIS = Path(__file__).parent
+
+
+def types_ex_main04(files: tuple[Path, ...]) -> None:
+ with open(files[0], "a") as f:
+ f.write("type space {x:i64 y:u64 z:i64}\ntype surface:u64\ntype volume:u64\n")
+ print(f"{THIS=} | folder {files[0].parent}:{os.listdir(files[0].parent)}")
+
+ with open(files[1], "a") as f:
+ f.write("type form {vol:u64}\n")
+
+
+def fns_ex_main04(files: tuple[Path, ...]) -> None:
+ pass
+
+
+def types_ex_main05(files: tuple[Path, ...]) -> None:
+ with open(files[0], "a") as f:
+ f.write("type point:i64\ntype line {x:i32}\ntype surface:u64\n")
+
+ with open(files[1], "a") as f:
+ f.write("type plane {x:i32 y:i32}\n")
+
+ with open(files[2], "a") as f:
+ f.write("type normal {dx:i32 dy:i32 dz:i32}")
+
+ with open(files[3], "a") as f:
+ f.write("type socket {raw:u32}")
+
+ with open(files[4], "a") as f:
+ f.write("type @sync_bool {@source:@bool @target:@bool}")
+
+
+def fns_ex_main05(files: tuple[Path, ...]) -> None:
+ with open(files[0], "a") as f:
+ f.write(
+ # floor()
+ "fn floor (x:f64) i64 {\n"
+ " xi:i64 = x*i64\n"
+ " ::if(and(ltz(x) ne(x xi*f64)):sub(xi 1) true:xi)\n"
+ "}\n"
+ # mod-2pi()
+ "fn mod-2pi (theta:f64) f64 {\n"
+ " two-pi:f64 = 6.283185307179586\n"
+ " quot:i64 = floor(div(theta two-pi))\n"
+ " ::sub(theta mul(two-pi quot*f64))\n"
+ "}\n"
+ # mod-pi()
+ "fn mod-pi (theta:f64) f64 {\n"
+ " pi:f64 = 3.141592653589793\n"
+ " two-pi:f64 = 6.283185307179586\n"
+ " quot:i64 = floor(div(add(theta pi) two-pi))\n"
+ " ::sub(theta mul(two-pi quot*f64))\n"
+ "}\n"
+ # abs()
+ "fn abs (x:f64) f64 {\n"
+ " bit63:u64 = 9223372036854775807 // sub(pow(2 63) 1), clear sign bit\n"
+ " b:u64\n"
+ " memcpy(b<&> x<&> sizeof(b))\n"
+ " memcpy(x<&> b-and(b bit63)<&> sizeof(x))\n"
+ " ::x\n"
+ "}\n"
+ # sin()
+ "fn sin (theta:f64) f64 {\n"
+ " pi:f64 = 3.141592653589793\n"
+ " pi2:f64 = pow(pi 2.0)\n"
+ " new-theta:f64 = mod-pi(theta)\n"
+ " abs-theta:f64 = if(ltz(new-theta):neg(new-theta) true:new-theta)\n"
+ " quad-approx:f64 = sub(div(4.0 pi) div(mul(4.0 abs(new-theta)) pi2))\n"
+ " ::mul(new-theta quad-approx)\n"
+ "}\n"
+ )
+
+
+@pytest.mark.parametrize(
+ "type_fn, fn_fn, file_name, type_files, fn_files",
+ [
+ (
+ types_ex_main04,
+ fns_ex_main04,
+ "ex_main04.hat",
+ ("geometry/euclidian", "geometry/differential"),
+ (),
+ ),
+ (
+ types_ex_main05,
+ fns_ex_main05,
+ "ex_main05.hat",
+ (
+ "geometry/euclidian2",
+ "geometry/euclidian2",
+ "geometry/differential2",
+ "std/io",
+ "std/base",
+ ),
+ ("math",),
+ ),
+ ],
+)
+def test_parse_type_ir(
+ type_fn: Callable,
+ fn_fn: Callable,
+ file_name: str,
+ type_files: tuple[str, ...],
+ fn_files: tuple[str, ...],
+) -> None:
+ # # uncomment below to enable cProfile-ing the code, to
+ # # check for time execution bottlenecks
+ # pr = cProfile.Profile()
+ # pr.enable()
+
+ project_name = "parse-test"
+ project_root = Path(THIS / project_name).resolve()
+
+ project_main_file_cp = project_root / "src" / file_name
+ project_main_file = project_root / "src" / "main.hat"
+
+ try:
+ if not project_root.exists():
+ create_new_project(project_root)
+
+ types_path = ()
+
+ for k in type_files:
+ types_path += (create_new_type_file(project_root, k),)
+
+ type_fn(types_path)
+ assert all(k.exists() for k in types_path)
+
+ fns_path = ()
+
+ for f in fn_files:
+ fns_path += (create_new_file(project_root, f),)
+
+ fn_fn(fns_path)
+ assert all(k.exists() for k in fns_path)
+
+ shutil.copy(src=(THIS / file_name), dst=project_main_file_cp)
+ shutil.move(project_main_file_cp, project_main_file)
+
+ code = open(project_main_file.resolve(), "r").read()
+
+ try:
+ ir_graph = IRGraph()
+ parse(
+ parser_grammar_code,
+ fn_program,
+ code,
+ project_root,
+ project_main_file,
+ ir_graph,
+ )
+
+ ir_graph.build()
+ main_node = ir_graph.nodes[IRHash(project_main_file)]
+ assert ir_graph.nodes[ir_graph.main_node] == main_node
+
+ # # uncomment below for prints of the raw code and the IR graph
+ # print(f"\nnum nodes in the graph: {len(ir_graph.nodes)}")
+ # print(
+ # f"main node has hash {ir_graph.main_node} and is on position"
+ # f"{get_hash(hash(ir_graph.main_node), ir_graph.nodes.phf)}"
+ # )
+ # print(f"[!] code:\n{code}\n")
+ # print(f"\nir graph:\n{ir_graph}")
+
+ finally:
+ pass
+
+ finally:
+ # # comment the line below to avoid deleting the folder with the project;
+ # # useful for debugging possible project toolchain-related errors
+ # shutil.rmtree(project_root)
+ pass
+
+ # # uncomment below to enable cProfile-ing the code, to
+ # # check for time execution bottlenecks
+ # pr.disable()
+ # pr.print_stats(sort=SortKey.CUMULATIVE)
+ # ps = Stats(pr).sort_stats(SortKey.CUMULATIVE)
+ #
+ # print(f"print callers:\n")
+ # ps.print_callers()
+ #
+ # print("print callees:\n")
+ # ps.print_callees()
diff --git a/python/tests/lowlevel/qlang/openqasm/v2/test_lowlevelqlang.py b/python/tests/lowlevel/qlang/openqasm/v2/test_lowlevelqlang.py
deleted file mode 100644
index a56c09cc..00000000
--- a/python/tests/lowlevel/qlang/openqasm/v2/test_lowlevelqlang.py
+++ /dev/null
@@ -1,70 +0,0 @@
-from __future__ import annotations
-
-from hhat_lang.core.code.ir import TypeIR, InstrIRFlag
-from hhat_lang.core.data.core import Symbol, CoreLiteral
-from hhat_lang.core.memory.core import MemoryManager
-from hhat_lang.dialects.heather.interpreter.classical.executor import Evaluator
-from hhat_lang.dialects.heather.code.simple_ir_builder.ir import (
- FnIR,
- IRBlock,
- IRInstr,
- IRArgs,
-)
-from hhat_lang.low_level.quantum_lang.openqasm.v2.qlang import LowLeveQLang
-
-
-def test_gen_program_single_empty_redim() -> None:
- code_snippet = """OPENQASM 2.0;
-include "qelib1.inc";
-qreg q[1];
-creg c[1];
-
-h q[0];
-measure q -> c;
-"""
-
- qv = Symbol("@v")
-
- mem = MemoryManager(5)
- mem.idx.add(qv, 1)
- mem.idx.request(qv)
-
- ex = Evaluator(mem, TypeIR(), FnIR())
-
- block = IRBlock()
- block.add_instr(IRInstr(Symbol("@redim"), IRArgs(), InstrIRFlag.CALL))
-
- qlang = LowLeveQLang(Symbol("@v"), block, mem.idx, ex)
- res = qlang.gen_program()
-
- assert res == code_snippet
-
-
-def test_gen_program_single_q0_redim() -> None:
- code_snippet = """OPENQASM 2.0;
-include "qelib1.inc";
-qreg q[1]
-creg c[1]
-
-h q[0];
-measure q -> c;
-"""
-
- mem = MemoryManager(5)
- mem.idx.request(Symbol("@v"), 3)
-
- ex = Evaluator(mem, TypeIR(), FnIR())
-
- block = IRBlock()
- block.add_instr(
- IRInstr(
- name=Symbol("@redim"),
- args=IRArgs(CoreLiteral(Symbol("@5").value, "@u3")),
- flag=InstrIRFlag.CALL
- )
- )
-
- qlang = LowLeveQLang(Symbol("@v"), block, mem.idx, ex)
- res = qlang.gen_program()
- print(res)
- # assert res == code_snippet
diff --git a/python/tests/test-cli.py b/python/tests/test-cli.py
new file mode 100644
index 00000000..fb3477a5
--- /dev/null
+++ b/python/tests/test-cli.py
@@ -0,0 +1,130 @@
+from __future__ import annotations
+
+import os
+import shutil
+from pathlib import Path
+
+import pytest
+from typer.testing import CliRunner
+
+from hhat_lang.toolchain.cli.cli import app
+
+runner = CliRunner()
+
+
+@pytest.fixture
+def temp_dir():
+ """Provide a temporary directory for tests"""
+ original_cwd = os.getcwd()
+ temp_dir = "temp"
+ os.mkdir(temp_dir)
+ os.chdir(temp_dir)
+ yield temp_dir
+ os.chdir(original_cwd)
+ shutil.rmtree(temp_dir)
+
+
+def test_help_command():
+ """Test the help command displays all available commands"""
+ result = runner.invoke(app, ["help"])
+ assert result.exit_code == 0
+ assert "Available commands:" in result.stdout
+ assert "new" in result.stdout
+ assert "run" in result.stdout
+ assert "help" in result.stdout
+
+
+def test_help_specific_command():
+ """Test help for a specific command shows detailed information"""
+ result = runner.invoke(app, ["help", "new"])
+ assert result.exit_code == 0
+ assert "Create a new project, file, or type file" in result.stdout
+ assert "--file" in result.stdout
+ assert "--type" in result.stdout
+
+
+def test_create_new_project(temp_dir):
+ """Test creating a new project succeeds"""
+ result = runner.invoke(app, ["new", "testproject"])
+ assert result.exit_code == 0
+ assert "created successfully" in result.stdout
+ assert (Path() / "testproject").exists()
+ assert (Path() / "testproject" / "src" / "main.hat").exists()
+
+
+def test_create_project_exists(temp_dir):
+ """Test creating a project fails when directory exists"""
+ runner.invoke(app, ["new", "testproject"])
+ # Try to create it again
+ result = runner.invoke(app, ["new", "testproject"])
+ assert result.exit_code == 1
+ assert "Error" in result.stdout
+ assert "exists" in result.stdout
+
+
+def test_create_file_in_project(temp_dir):
+ """Test creating a new file inside a project directory"""
+ runner.invoke(app, ["new", "testproject"])
+ os.chdir("testproject")
+ # Create a new file
+ result = runner.invoke(app, ["new", "-f", "module/testfile"])
+ assert result.exit_code == 0
+ assert "created successfully" in result.stdout
+ assert (Path() / "module" / "testfile.hat").exists()
+
+
+def test_create_file_outside_project(temp_dir):
+ """Test creating a file fails outside project directory"""
+ result = runner.invoke(app, ["new", "-f", "testfile"])
+ assert result.exit_code == 1
+ assert "Error" in result.stdout
+ assert "project directory" in result.stdout
+
+
+def test_create_existing_file(temp_dir):
+ """Test creating a file fails when it already exists"""
+ runner.invoke(app, ["new", "testproject"])
+ os.chdir("testproject")
+ runner.invoke(app, ["new", "-f", "testfile"])
+ result = runner.invoke(app, ["new", "-f", "testfile"])
+ assert result.exit_code == 1
+ assert "Error" in result.stdout
+ assert "already exists" in result.stdout
+
+
+def test_create_type_file(temp_dir):
+ """Test creating a new type file inside a project directory"""
+ runner.invoke(app, ["new", "testproject"])
+ os.chdir("testproject")
+ result = runner.invoke(app, ["new", "-t", "customtype"])
+ assert result.exit_code == 0
+ assert "created successfully" in result.stdout
+ assert "customtype.hat" in result.stdout
+
+
+def test_run_project(temp_dir):
+ """Test running a project with empty main.hat"""
+ runner.invoke(app, ["new", "testproject"])
+ os.chdir("testproject")
+ # Run the project
+ result = runner.invoke(app, ["run"])
+ assert result.exit_code == 1 # expect an error
+ assert "Error" in result.stdout
+ assert "no implementation yet" in result.stdout
+
+
+def test_run_outside_project(temp_dir):
+ """Test running outside a project directory fails"""
+ result = runner.invoke(app, ["run"])
+ assert result.exit_code == 1
+ assert "Error" in result.stdout
+ # We don't test for the exact error message since it's wrapped in a panel
+ # and the formatting might change
+
+
+def test_version():
+ """Test version flag shows version information"""
+ result = runner.invoke(app, ["--version"])
+ assert result.exit_code == 0
+ assert "H-hat Language Toolchain" in result.stdout
+ assert "version" in result.stdout
diff --git a/rust/README.md b/rust/README.md
new file mode 100644
index 00000000..dbce04f8
--- /dev/null
+++ b/rust/README.md
@@ -0,0 +1,15 @@
+# Rust Implementation of H-hat
+
+Rust implementation of H-hat. It includes the rule system and key features for development of the language, and a reference dialect ([Heather](hhat_lang/src/hhat_dialects/src/hhat-heather/README.md)) to evaluate H-hat code.
+
+This implementation is currently in early development and may not be usable. You can clone and check it out for yourself, though:
+
+```bash
+git clone https://github.com/hhat-lang/hhat_lang.git
+```
+
+There you can find this very code and also other language implementations.
+
+## Documentation
+
+Check out more at [H-hat's documentation](https://docs.hhat-lang.org).
diff --git a/rust/hhat_lang/Cargo.toml b/rust/hhat_lang/Cargo.toml
new file mode 100644
index 00000000..7cd87483
--- /dev/null
+++ b/rust/hhat_lang/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "hhat_lang"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
diff --git a/rust/hhat_lang/src/README.md b/rust/hhat_lang/src/README.md
new file mode 100644
index 00000000..e69de29b
diff --git a/rust/hhat_lang/src/hhat_core/Cargo.toml b/rust/hhat_lang/src/hhat_core/Cargo.toml
new file mode 100644
index 00000000..ebf55bb7
--- /dev/null
+++ b/rust/hhat_lang/src/hhat_core/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "hhat_core"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+
+[dependencies.uuid]
+version = "1.15.1"
+# Lets you generate random UUIDs
+features = [
+ "v4",
+]
diff --git a/rust/hhat_lang/src/hhat_core/src/README.md b/rust/hhat_lang/src/hhat_core/src/README.md
new file mode 100644
index 00000000..f638a473
--- /dev/null
+++ b/rust/hhat_lang/src/hhat_core/src/README.md
@@ -0,0 +1,29 @@
+# H-hat core features
+
+This crate serves to define H-hat's rule system and the language core features.
+
+## Refactoring phase
+
+For context during the code refactoring, what is actually being used and refactored:
+
+### Code that will be used with some rework, but mostly new work
+
+- [mem/core.rs](mem/core.rs) contains the structure and logic for memory blocks
+- [mem/manager.rs](mem/manager.rs) contains the manager for all the memory-related constructs, such as memory block, heap, stack, index, etc
+- [mem/defs.rs](mem/defs.rs) contains the constants for standard definitions for memory-based operations
+- [mem/heap.rs](mem/heap.rs) contains the heap-related code
+- [mem/index.rs](mem/index.rs) contains the index-relate code
+- [mem/data.rs](mem/data.rs) contains the data-related code
+- [utils.rs](utils.rs) contains some utilities constants and functions such as generating constant-size hashmaps and fullnames for the identifiers in the user program
+
+
+### Code that needs complete rework
+
+- [mem/stack.rs](mem/stack.rs) contains the stack-related code; must replace old code for the working one, namely mem/core.rs's `MemBlock` code with its errors/success enum
+
+
+### Stale code used for reference only
+
+Some codes can be used to build new and improved ones, such as:
+- [mem/type_container.rs](mem/type_container.rs) contains an attempt to build some data structures
+- [lib.rs](lib.rs) contains tests using mem/alloc.rs code must be rewritten to account for mem/core.rs code
diff --git a/rust/hhat_lang/src/hhat_core/src/data/core.rs b/rust/hhat_lang/src/hhat_core/src/data/core.rs
new file mode 100644
index 00000000..f8c10b13
--- /dev/null
+++ b/rust/hhat_lang/src/hhat_core/src/data/core.rs
@@ -0,0 +1,28 @@
+
+
+pub enum Item {
+ Atomic,
+ Symbol,
+ Literal,
+ CompositeSymbol,
+ CompositeLiteral,
+ CompositeMix,
+}
+
+pub struct WorkingDataItem {
+ data: Item,
+ value: String,
+ data_type: String,
+ is_quantum: bool,
+ suppress_type: bool,
+}
+
+
+pub trait WorkingData {
+ fn new();
+ fn push();
+ fn pop();
+ fn free();
+
+ fn is_quantum(&self) -> bool;
+}
diff --git a/rust/hhat_lang/src/hhat_core/src/data/mod.rs b/rust/hhat_lang/src/hhat_core/src/data/mod.rs
new file mode 100644
index 00000000..98160373
--- /dev/null
+++ b/rust/hhat_lang/src/hhat_core/src/data/mod.rs
@@ -0,0 +1 @@
+mod core;
diff --git a/rust/hhat_lang/src/hhat_core/src/instr/base.rs b/rust/hhat_lang/src/hhat_core/src/instr/base.rs
new file mode 100644
index 00000000..e69de29b
diff --git a/rust/hhat_lang/src/hhat_core/src/instr/classical_instr.rs b/rust/hhat_lang/src/hhat_core/src/instr/classical_instr.rs
new file mode 100644
index 00000000..e69de29b
diff --git a/rust/hhat_lang/src/hhat_core/src/instr/mod.rs b/rust/hhat_lang/src/hhat_core/src/instr/mod.rs
new file mode 100644
index 00000000..76b4e1ee
--- /dev/null
+++ b/rust/hhat_lang/src/hhat_core/src/instr/mod.rs
@@ -0,0 +1,3 @@
+pub mod base;
+pub mod classical_instr;
+pub mod quantum_instr;
diff --git a/rust/hhat_lang/src/hhat_core/src/instr/quantum_instr.rs b/rust/hhat_lang/src/hhat_core/src/instr/quantum_instr.rs
new file mode 100644
index 00000000..e69de29b
diff --git a/rust/hhat_lang/src/hhat_core/src/lib.rs b/rust/hhat_lang/src/hhat_core/src/lib.rs
new file mode 100644
index 00000000..72bacbbe
--- /dev/null
+++ b/rust/hhat_lang/src/hhat_core/src/lib.rs
@@ -0,0 +1,106 @@
+#![allow(dead_code, unused_attributes, unused_attributes, unused_variables)]
+
+mod instr;
+mod mem;
+mod utils;
+mod data;
+//----------------------
+// LET THE TESTS BEGIN
+//----------------------
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::mem::alloc::{free_memblock, AllocSuccess};
+ use crate::mem::defs::MAX_MEMBLOCK_SIZE;
+ use std::ptr::NonNull;
+
+ /// Create small memory blocks with [`mem::alloc::alloc_memblock`] and [`mem::base::MemBlock`]
+ #[test]
+ fn create_memblock_small() {
+ println!("Create memblock small:");
+
+ let align: usize = 8;
+ let block_size: usize = (1 << 4) as usize; // 16
+ let block_ptr1 = mem::alloc::alloc_memblock(block_size, align);
+ let block_ptr2 = mem::alloc::alloc_memblock(block_size, align);
+
+ let ptr1: NonNull = match block_ptr1 {
+ Ok(x) => {
+ println!(
+ " - allocated memory 1! {:} , {:}",
+ x.as_ptr() as u8,
+ x.addr()
+ );
+ x
+ },
+ Err(_) => {
+ return assert!(false);
+ }
+ };
+
+ let ptr2: NonNull = match block_ptr2 {
+ Ok(x) => {
+ println!(
+ " - allocated memory 2! {:}, {:}",
+ x.as_ptr() as u8,
+ x.addr()
+ );
+ x
+ },
+ Err(_) => return assert!(false),
+ };
+
+ let mut memblock: mem::base::MemBlock = match mem::base::MemBlock::new(block_size) {
+ Ok(x) => {
+ assert_eq!(x.get_size(), block_size);
+ {
+ println!(" - memblock ptr is equal to {:}", block_size + 16);
+ x
+ }
+ }
+ Err(_) => {
+ return assert!(false);
+ }
+ };
+
+ match free_memblock(block_size, align, ptr1) {
+ Ok(AllocSuccess::MemoryFreed) => assert!(true),
+ Ok(other) => assert!(false),
+ Err(_) => assert!(false),
+ };
+
+ match free_memblock(block_size, align, ptr2) {
+ Ok(AllocSuccess::MemoryFreed) => assert!(true),
+ Ok(other) => assert!(false),
+ Err(_) => assert!(false),
+ };
+
+ match memblock.free() {
+ Ok(AllocSuccess::MemoryFreed) => assert!(true),
+ Ok(other) => assert!(false),
+ Err(_) => assert!(false),
+ }
+ }
+
+ /// Create the largest memory blocks enabled with [`mem::base::MemBlock`]
+ #[test]
+ fn create_memblock_large() {
+ println!("Create memblock large:");
+
+ assert!(MAX_MEMBLOCK_SIZE.is_power_of_two());
+
+ let mut memblock: mem::base::MemBlock = match mem::base::MemBlock::new(MAX_MEMBLOCK_SIZE) {
+ Ok(x) => {
+ assert_eq!(x.get_size(), MAX_MEMBLOCK_SIZE);
+ println!("memblock with the expected size {:}", MAX_MEMBLOCK_SIZE);
+ x
+ }
+ Err(_) => {
+ return assert!(false);
+ }
+ };
+
+ let _ = memblock.free();
+ }
+}
diff --git a/rust/hhat_lang/src/hhat_core/src/mem/core.rs b/rust/hhat_lang/src/hhat_core/src/mem/core.rs
new file mode 100644
index 00000000..cd6b67df
--- /dev/null
+++ b/rust/hhat_lang/src/hhat_core/src/mem/core.rs
@@ -0,0 +1,242 @@
+use crate::mem::defs::{BlockSize, ALIGNMENT, MAX_MEMBLOCK_SIZE};
+use std::alloc::{alloc, dealloc, Layout};
+use std::mem::size_of_val;
+use std::ptr::{read, write, NonNull};
+
+/// A holder for memory block, its pointer in the [`std::alloc::GlobalAlloc`],
+/// the default alignment and its total size.
+///
+/// The `MemBlock` implementation is basically all unsafe since we are dealing with
+/// the raw memory directly here. Although it is being checked internally to make
+/// sure things don't go wild, unsafe reinforces the awareness the handler code must
+/// have when interacting with it.
+pub struct MemBlock {
+ ptr: NonNull,
+ size: BlockSize,
+ align: usize,
+ is_freed: bool,
+}
+
+impl MemBlock {
+ /// create a new memory block with a given size (smaller than [`MAX_MEMBLOCK_SIZE`]
+ /// and power of two), and alignment size (power of two).
+ pub unsafe fn new(size: BlockSize, align: usize) -> Result {
+ // size must not exceed the maximum permitted block size
+ if size > MAX_MEMBLOCK_SIZE {
+ return Err(MemAllocError::InvalidBlockSize);
+ }
+
+ // size must be power of two so a memory block can be properly allocated
+ if !size.is_power_of_two() {
+ return Err(MemAllocError::NotPowerOfTwo)
+ }
+
+ match Self::alloc_memblock(size, align) {
+ Ok(value) => Ok(MemBlock {
+ ptr: value,
+ size,
+ align,
+ is_freed: false,
+ }),
+ Err(value) => Err(value),
+ }
+
+ }
+
+ unsafe fn alloc_memblock(size: BlockSize, align: usize) -> Result, MemAllocError> {
+ let layout: Layout = match Layout::from_size_align(size, align) {
+ Ok(layout) => layout,
+ Err(_) => return Err(MemAllocError::LayoutError),
+ };
+ let ptr: *mut u8 = alloc(layout);
+
+ if ptr.is_null() {
+ return Err(MemAllocError::NullPointer)
+ }
+ Ok(NonNull::new_unchecked(ptr))
+ }
+
+ pub unsafe fn free(&mut self) -> Result {
+ if !self.is_freed {
+ let layout: Layout = match Layout::from_size_align(self.size, ALIGNMENT) {
+ Ok(layout) => layout,
+ Err(_) => return Err(MemAllocError::LayoutError),
+ };
+
+ dealloc(self.ptr.as_ptr(), layout);
+ self.is_freed = true;
+ Ok(MemAllocSuccess::MemoryFreed)
+ } else {
+ Err(MemAllocError::MemoryAlreadyFreed)
+ }
+ }
+
+ pub fn as_ptr(&self) -> *const u8 {
+ self.ptr.as_ptr()
+ }
+
+ /// Push data `T` to the memory block and returns its pointer position
+ pub unsafe fn push(&mut self, data: T) -> Result {
+ let offset: usize = size_of_val(&data);
+ let ptr: *mut T = self.as_ptr().add(offset) as *mut T;
+
+ if (ptr as usize) <= (self.as_ptr().add(self.size) as usize) {
+ write(ptr, data);
+ Ok(ptr as usize)
+ } else {
+ Err(MemAllocError::MemoryOverflow)
+ }
+ }
+
+ /// Pops the last item from the memory. Because [`MemBlock`] is just a struct
+ /// to hold data and its pointer at the [`std::alloc::GlobalAlloc`], it doesn't
+ /// know where the last item is. It's up to the API above to define it, such as
+ /// stack memory or a heap memory struct.
+ ///
+ /// It returns a tuple as the data, the data size, and the updated cursor pointer.
+ pub unsafe fn pop(
+ &mut self,
+ cursor_ptr: usize,
+ ) -> Result<(T, usize, usize), MemAllocError> {
+ // read the data from the allocated memory space given a cursor pointer
+ let data: T = read(cursor_ptr as *const T);
+
+ let data_size: usize = size_of_val(&data);
+ // get the new pointer position subtracted from the data memory space
+ let new_cursor_ptr: usize = (cursor_ptr as *const u8).sub(data_size) as usize;
+
+ // the pointer handler (that called this very function) now has an updated
+ // pointer to use as its new cursor, for instance.
+ Ok((data, data_size, new_cursor_ptr))
+ }
+
+ /// Take a look at the data at some pointer position in the memory. The API calling
+ /// it should handle the right pointer position (the last written data, for a stack
+ /// memory API, for instance). The pointer is not updated since it's just peeking
+ /// into the memory.
+ pub unsafe fn peek(&mut self, ptr: usize) -> T {
+ // let mem_ptr: *const T = self.as_ptr().add(ptr) as *const T;
+ // read(mem_ptr)
+ read(ptr as *const T)
+ }
+}
+
+impl Drop for MemBlock {
+ fn drop(&mut self) {
+ unsafe {
+ let _ = self.free();
+ }
+ }
+}
+
+#[derive(Debug)]
+pub enum MemAllocError {
+ EmptyMemory,
+ InvalidBlockSize,
+ InvalidAlignment,
+ LayoutError,
+ MemoryAlreadyFreed,
+ MemoryOverflow,
+ NotEnoughMemory,
+ NotPowerOfTwo,
+ NullPointer,
+}
+
+#[derive(Debug)]
+pub enum MemAllocSuccess {
+ MemoryFreed,
+ MemoryAlreadyFreed,
+ DataPushedToMemory,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ /// test memory block allocation, writing, reading and de-allocation
+ #[test]
+ fn test_simple_memblock_operations() {
+ unsafe {
+ println!("== simple memblock alloc ==");
+
+ let max_size = MAX_MEMBLOCK_SIZE;
+ assert_eq!(max_size, MAX_MEMBLOCK_SIZE);
+
+ let mut memblock = MemBlock::new(max_size, 8usize).unwrap();
+ println!(" - memblock");
+ println!(" - [x] ptr: {}", memblock.as_ptr() as usize);
+
+ let data_ptr = memblock.push(1u64).unwrap();
+ println!(" - [x] input data: {}", 1u64);
+ assert!(
+ memblock.as_ptr().add(size_of_val(&1u64)) <= memblock.as_ptr().add(memblock.size)
+ );
+ println!(" - [x] push data, received ptr: {:}", data_ptr);
+
+ let retrieved_data = memblock.peek::(data_ptr);
+ assert_eq!(retrieved_data, 1u64);
+ println!(" - [x] peek data: {:}", retrieved_data);
+
+ let (d, ds, p) = match memblock.pop::(data_ptr) {
+ Ok((x, y, z)) => (x, y, z),
+ Err(e) => panic!("{:?}", e),
+ };
+ assert_eq!(d, 1u64);
+ assert_eq!(ds, 8);
+ assert_eq!(p, memblock.as_ptr() as usize);
+ println!(" - [x] pop data:");
+ println!(" - - [x] retrieved data: {:}", d);
+ println!(" - - [x] retrieved data size: {:}", ds);
+ println!(" - - [x] retrieved new pointer: {:}", p);
+
+ memblock.free().unwrap();
+ println!(" - [x] memblock freed");
+
+ println!("=====================");
+ }
+ }
+
+ /// test many memory blocks allocation, writing, reading and de-allocation
+ #[test]
+ fn test_many_memblock_operations() {}
+
+ #[test]
+ fn test_struct_memblock_operations() {
+ unsafe {
+ #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
+ struct TestStruct {
+ x: u64,
+ y: u64
+ }
+
+ println!("=== complex memblock alloc ===");
+
+ let mut memblock = match MemBlock::new(MAX_MEMBLOCK_SIZE, 8usize) {
+ Ok(block) => block,
+ Err(err) => panic!("{:?}", err)
+ };
+ println!(" - memblock");
+ println!(" - [x] ptr: {}", memblock.as_ptr() as usize);
+
+ let data_struct = TestStruct{x:1u64, y:65535u64};
+ let data_ptr = memblock.push(data_struct.clone()).unwrap();
+ println!(" - [x] input data: {:?}", data_struct);
+ println!(" - [x] push data, received ptr: {:}", data_ptr);
+ let retrieved_data = memblock.peek::(data_ptr);
+ assert_eq!(retrieved_data, data_struct);
+ println!(" - [x] peek data: {:?}", retrieved_data);
+
+ let (d, ds, p) = match memblock.pop::(data_ptr) {
+ Ok((x, y, z)) => (x, y, z),
+ Err(e) => panic!("{:?}", e),
+ };
+ assert_eq!(d, data_struct);
+ assert_eq!(ds, 16);
+ assert_eq!(p, memblock.as_ptr() as usize);
+ println!(" - [x] pop data:");
+ println!(" - - [x] retrieved data: {:?}", d);
+ println!(" - - [x] retrieved data size: {:}", ds);
+ println!(" - - [x] retrieved new pointer: {:}", p);
+ }
+ }
+}
diff --git a/rust/hhat_lang/src/hhat_core/src/mem/data.rs b/rust/hhat_lang/src/hhat_core/src/mem/data.rs
new file mode 100644
index 00000000..e69de29b
diff --git a/rust/hhat_lang/src/hhat_core/src/mem/defs.rs b/rust/hhat_lang/src/hhat_core/src/mem/defs.rs
new file mode 100644
index 00000000..ca0f39eb
--- /dev/null
+++ b/rust/hhat_lang/src/hhat_core/src/mem/defs.rs
@@ -0,0 +1,3 @@
+pub type BlockSize = usize;
+pub const ALIGNMENT: usize = 8; // 64bit alignment
+pub const MAX_MEMBLOCK_SIZE: usize = (1 << 16) as usize; // 64kB
diff --git a/rust/hhat_lang/src/hhat_core/src/mem/heap.rs b/rust/hhat_lang/src/hhat_core/src/mem/heap.rs
new file mode 100644
index 00000000..e69de29b
diff --git a/rust/hhat_lang/src/hhat_core/src/mem/index.rs b/rust/hhat_lang/src/hhat_core/src/mem/index.rs
new file mode 100644
index 00000000..e69de29b
diff --git a/rust/hhat_lang/src/hhat_core/src/mem/manager.rs b/rust/hhat_lang/src/hhat_core/src/mem/manager.rs
new file mode 100644
index 00000000..e5d51dd6
--- /dev/null
+++ b/rust/hhat_lang/src/hhat_core/src/mem/manager.rs
@@ -0,0 +1,3 @@
+/// Create a manager for all the memory matters, namely:
+/// stack, heap, index, pid
+pub struct MemoryManager {}
diff --git a/rust/hhat_lang/src/hhat_core/src/mem/mod.rs b/rust/hhat_lang/src/hhat_core/src/mem/mod.rs
new file mode 100644
index 00000000..d6dfac35
--- /dev/null
+++ b/rust/hhat_lang/src/hhat_core/src/mem/mod.rs
@@ -0,0 +1,10 @@
+pub mod alloc;
+pub mod base;
+pub mod core;
+mod data;
+pub mod defs;
+pub mod heap;
+pub mod index;
+pub mod manager;
+pub mod stack;
+pub mod type_container;
diff --git a/rust/hhat_lang/src/hhat_core/src/mem/stack.rs b/rust/hhat_lang/src/hhat_core/src/mem/stack.rs
new file mode 100644
index 00000000..ef482547
--- /dev/null
+++ b/rust/hhat_lang/src/hhat_core/src/mem/stack.rs
@@ -0,0 +1,150 @@
+use std::ptr::write;
+
+use crate::mem::alloc::AllocError;
+use crate::mem::base::MemBlock;
+use crate::mem::defs::{ALIGNMENT, MAX_MEMBLOCK_SIZE};
+
+/// The actual stack memory container
+///
+/// It can hold several `StackPage`s according to what is needed.
+///
+/// `pages` contains the `StackPage` data, whereas `page_index` is
+/// responsible to hold information on which page to find some data
+pub struct StackMemory {
+ pages: Vec,
+ cur_page: isize,
+}
+
+impl StackMemory {
+ pub fn new(&mut self, max_size: usize) -> Self {
+ StackMemory {
+ pages: {
+ match max_size {
+ 0 => Vec::new(),
+ _ => {
+ let mut v: Vec = Vec::with_capacity(max_size);
+ // create a stack page with max memory size
+ let page: StackPage = match StackPage::new(MAX_MEMBLOCK_SIZE) {
+ Ok(x) => x,
+ Err(x) => panic!(""),
+ };
+ v.push(page);
+ v
+ }
+ }
+ },
+ cur_page: -1,
+ }
+ }
+
+ /// When `StackMemory` needs to expand memory, a new page must be created.
+ /// A page is a `StackPage`.
+ pub fn new_page(&mut self, size: usize) -> Result<(), StackError> {
+ match StackPage::new(size) {
+ Ok(page) => {
+ self.pages.push(page);
+ let cur_page = self.cur_page;
+ self.cur_page = cur_page + 1;
+ Ok(())
+ }
+ Err(err) => Err(err),
+ }
+ }
+
+ pub fn page_cursor(&mut self, page_num: isize) -> *const u16 {
+ self.pages[page_num as usize].cursor()
+ }
+
+ pub fn push(&mut self, data: T) {
+ unsafe {
+ todo!()
+ // figure out how to get the offset for `data`
+ // self.pages[-1].write(data, );
+ }
+ }
+
+ pub fn pop(&mut self) -> Option<()> {
+ todo!()
+ }
+}
+
+/// Stack page memory
+///
+/// To create a new `StackPage`, use `StackPage::new(size)` where `size` must be power
+/// of two, `usize` type and bounded to [`MAX_MEMBLOCK_SIZE`].
+///
+/// For now, the stack page can support pointers of size u16.
+pub struct StackPage {
+ memblock: MemBlock,
+ cursor: *const u16,
+ limit: *const u16,
+}
+
+impl StackPage {
+ pub fn new(size: usize) -> Result {
+ match MemBlock::new(size) {
+ Ok(memblock) => Ok(StackPage {
+ memblock,
+ cursor: MAX_MEMBLOCK_SIZE as *const u16,
+ limit: MAX_MEMBLOCK_SIZE as *const u16,
+ }),
+ Err(x) => match x {
+ AllocError::ConstraintsNotSatisfied => Err(StackError::LayoutError),
+ AllocError::InvalidAlignment => Err(StackError::InvalidAlignment),
+ AllocError::InvalidBlockSize => Err(StackError::InvalidBlockSize),
+ AllocError::MemoryAlreadyFreed => Err(StackError::MemoryAlreadyFreed),
+ AllocError::NotEnoughMemory => Err(StackError::NotEnoughMemory),
+ AllocError::NotPowerOfTwo => Err(StackError::SizeNotPowerOfTwo),
+ AllocError::NullPointer => Err(StackError::NullPointer),
+ },
+ }
+ }
+
+ /// Makes available a space in the Stack's memory block of size `alloc_size` and
+ /// returns a pointer to it.
+ pub fn alloc(&mut self, alloc_size: usize) -> Result<*const u16, StackError> {
+ let start_ptr = self.memblock.as_ptr() as usize;
+ let cursor_ptr = self.cursor as usize;
+
+ let next_ptr: usize = match cursor_ptr.checked_sub(alloc_size) {
+ Some(ptr) => ptr & ALIGNMENT, // align to word boundary
+ _ => return Err(StackError::NoNextPointer),
+ };
+
+ if next_ptr < start_ptr {
+ Err(StackError::NotEnoughMemory)
+ } else {
+ self.cursor = next_ptr as *const u16;
+ Ok(next_ptr as *const u16)
+ }
+ }
+
+ /// write data to the stack page(?)
+ pub unsafe fn write(&mut self, data: T, offset: usize) -> *const T {
+ let p = self.memblock.as_ptr().add(offset) as *mut T;
+ write(p, data);
+ p
+ }
+
+ /// To free the memory space given by the `ptr` pointer.
+ pub unsafe fn free(&mut self, ptr: *const u16) {
+ let _ = self.memblock.free();
+ }
+
+ pub fn cursor(&self) -> *const u16 {
+ self.cursor
+ }
+}
+
+/// Possible stack errors to communicate
+pub enum StackError {
+ InvalidBlockSize,
+ InvalidAlignment,
+ InvalidStackPage,
+ LayoutError,
+ MemoryAlreadyFreed,
+ NoNextPointer,
+ NotEnoughMemory,
+ NullPointer,
+ SizeNotPowerOfTwo,
+}
diff --git a/rust/hhat_lang/src/hhat_core/src/mem/type_container.rs b/rust/hhat_lang/src/hhat_core/src/mem/type_container.rs
new file mode 100644
index 00000000..3b447f58
--- /dev/null
+++ b/rust/hhat_lang/src/hhat_core/src/mem/type_container.rs
@@ -0,0 +1,58 @@
+use crate::utils::FullName;
+
+pub struct TypeContainer {
+ fullname: FullName,
+ type_ds: TypeDataStructure,
+}
+
+/// Define the type container structure
+pub trait TypeTemplate {
+ fn new() -> Self;
+ fn get_type() -> FullName;
+ fn ir_alloc(&mut self);
+}
+
+type TypeFullName = FullName;
+
+pub struct SingleType {
+ fullname: FullName,
+ datatype: TypeFullName,
+ data: (),
+}
+
+impl SingleType {
+ pub fn assign_data(&mut self, data: T) {}
+}
+
+/// Type data structure for composing types. It can be
+/// - `Single`: as `u16` or `bool`, they are simple, single-valued types.
+/// - `Struct`: similar to `struct` in C or Rust, hold members as a name + type.
+/// - `Enum`: similar to `enum` in Rust, can contain constant members or structs.
+/// - `Union`: similar to `union` in C or Rust, holds members as a name + type, but
+/// con only assign one of its members at once.
+pub enum TypeDataStructure {
+ Single,
+ Struct(StructType),
+ Enum { members: Vec },
+ Union { members: Vec },
+}
+
+
+enum ConstantMember {
+ Constant,
+}
+
+struct StructType {
+ members: Vec<(String, TypeFullName)>,
+}
+
+enum EnumMember {
+ Constant(ConstantMember),
+ Struct(StructType),
+}
+
+enum UnionMember {
+ Constant(ConstantMember),
+ Struct(StructType),
+ Enum(EnumMember),
+}
diff --git a/rust/hhat_lang/src/hhat_core/src/utils.rs b/rust/hhat_lang/src/hhat_core/src/utils.rs
new file mode 100644
index 00000000..daf1ec8c
--- /dev/null
+++ b/rust/hhat_lang/src/hhat_core/src/utils.rs
@@ -0,0 +1,105 @@
+// Utils for perfect hash function generation, naming scopes for
+// identifiers such as variables, types and functions
+
+use std::collections::HashSet;
+
+//---------------------------
+// NAME & NAMESPACE SECTION
+//---------------------------
+
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct FullName {
+ namespace: Vec,
+ name: String,
+}
+
+//--------------------------------------------
+// CONSTANTS FOR PERFECT HASH FUNCTION LOGIC
+//--------------------------------------------
+
+const PHF_A_LIMIT: u64 = 1_000_000;
+const PHF_R_LIMIT: u32 = 37;
+const PRIME_CONVERTER: u64 = 257;
+
+/// transform string into u64 according to the formula
+/// `value = value as u64 * PRIME_CONVERTER + char as u64`
+/// where `value` is iterated over each of the chars
+/// contained in the string
+pub fn str_to_int(s: &String) -> u64 {
+ let mut value: u64 = 0;
+
+ for ch in s.chars() {
+ value = value.wrapping_mul(PRIME_CONVERTER).wrapping_add(ch as u64);
+ }
+
+ value
+}
+
+/// Generates an u64 number to serve as the hash number for the given string
+pub fn get_hash(s: &String, a: u64, r: u32, n: u64) -> u64 {
+ let x: u64 = str_to_int(s);
+ let product: u64 = x.wrapping_mul(a);
+ let mixed: u64 = product ^ (product >> r);
+ mixed % n
+}
+
+/// Finds the perfect hash ([PHF wiki](https://en.wikipedia.org/wiki/Perfect_hash_function)
+/// and [PHF paper](https://cmph.sourceforge.net/papers/esa09.pdf)) parameters `a` and `r`
+/// for a given array of strings
+pub fn find_phf_params(strs: Vec) -> Option<(u64, u32)> {
+ let n: u64 = strs.len() as u64;
+
+ for a in 1..PHF_A_LIMIT {
+ for r in 0..PHF_R_LIMIT {
+ let mut seen: HashSet = HashSet::new();
+ let mut collision: bool = false;
+
+ for s in &strs {
+ let h: u64 = get_hash(s, a, r, n);
+
+ if !seen.insert(h) {
+ collision = true;
+ break;
+ }
+ }
+
+ if !collision {
+ return Some((a, r));
+ }
+ }
+ }
+ None
+}
+
+/// Generates the perfect hash results, returning tuple with a vector where the element
+/// is the string and the index is the actual result we want (as an u64 number), the
+/// `a` value as u64 number, used to retrieve the hash whenever needed, and the `r`
+/// value as u32 number, also used to retrieve the hash for the string
+pub fn gen_phf(
+ values: Vec,
+ vec_size: usize,
+) -> Option<(Vec