From 179152a278204158be447820a087d29b74cb8f4d Mon Sep 17 00:00:00 2001 From: Miguel M Date: Wed, 22 Feb 2023 16:58:37 +0000 Subject: [PATCH] vm working --- .github/workflows/CI.yml | 90 ++++++++ .gitignore | 72 ++++++ Cargo.lock | 425 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 14 ++ pyproject.toml | 14 ++ src/lib.rs | 72 ++++++ src/vm/mod.rs | 336 ++++++++++++++++++++++++++++ src/vm/parsing/ast.rs | 100 +++++++++ src/vm/parsing/grammar.pest | 20 ++ src/vm/parsing/mod.rs | 348 +++++++++++++++++++++++++++++ 10 files changed, 1491 insertions(+) create mode 100644 .github/workflows/CI.yml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 pyproject.toml create mode 100644 src/lib.rs create mode 100644 src/vm/mod.rs create mode 100644 src/vm/parsing/ast.rs create mode 100644 src/vm/parsing/grammar.pest create mode 100644 src/vm/parsing/mod.rs diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..d0670e0 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,90 @@ +on: + push: + branches: + - main + - master + pull_request: + workflow_dispatch: + +jobs: + linux: + runs-on: ubuntu-latest + strategy: + matrix: + target: [x86_64, x86, aarch64, armv7, s390x, ppc64le] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: --release --out dist --find-interpreter + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: dist + + windows: + runs-on: windows-latest + strategy: + matrix: + target: [x64, x86] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + architecture: ${{ matrix.target }} + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: --release --out dist --find-interpreter + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: dist + + macos: + runs-on: macos-latest + strategy: + matrix: + target: [x86_64, aarch64] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: --release --out dist --find-interpreter + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: dist + + release: + name: Release + runs-on: ubuntu-latest + if: "startsWith(github.ref, 'refs/tags/')" + needs: [linux, windows, macos] + steps: + - uses: actions/download-artifact@v3 + with: + name: wheels + - name: Publish to PyPI + uses: PyO3/maturin-action@v1 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + with: + command: upload + args: --skip-existing * diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af3ca5e --- /dev/null +++ b/.gitignore @@ -0,0 +1,72 @@ +/target + +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ +venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2ffb984 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,425 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brute_lower_bounds" +version = "0.1.0" +dependencies = [ + "pest", + "pest_derive", + "pyo3", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "indoc" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "pest" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac3922aac69a40733080f53c1ce7f91dcf57e1a5f6c52f421fadec7fbdc4b69" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06646e185566b5961b4058dd107e0a7f56e77c3f484549fb119867773c0f202" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f60b2ba541577e2a0c307c8f39d1439108120eb7903adeb6497fa880c59616" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06a3d8e8a46ab2738109347433cb7b96dffda2e4a218b03ef27090238886b147" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75439f995d07ddfad42b192dfcf3bc66a7ecfd8b4a1f5f6f046aa5c2c5d7677d" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839526a5c07a17ff44823679b68add4a58004de00512a95b6c1c98a6dcac0ee5" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd44cf207476c6a9760c4653559be4f206efafb924d3e4cbf2721475fc0d6cc5" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1f43d8e30460f36350d18631ccf85ded64c059829208fe680904c65bcd0a4c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae9980cab1db3fceee2f6c6f643d5d8de2997c58ee8d25fb0cc8a9e9e7348e5" + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "unindent" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9720086 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "brute_lower_bounds" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "adversary" +crate-type = ["cdylib"] + +[dependencies] +pest = "2.5.5" +pest_derive = "2.5.5" +pyo3 = { version = "0.18.0", features = ["extension-module"] } diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e850234 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[build-system] +requires = ["maturin>=0.14,<0.15"] +build-backend = "maturin" + +[project] +name = "brute_lower_bounds" +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] + + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c6b52bf --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,72 @@ +extern crate pest; +#[macro_use] +extern crate pest_derive; + +mod vm; + +use pyo3::prelude::*; + +#[pyclass] +struct Prover {} + +#[pymethods] +impl Prover { + #[new] + fn py_new( + a_set: String, + b_set: String, + relationship: String, + conjecture: String, + ) -> PyResult { + let relationship = match vm::parsing::parse_relation(&relationship) { + Ok(relationship) => relationship, + Err(msg) => { + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "When parsing `relationship`:\n{}", + msg + ))); + } + }; + + let a_set = match vm::parsing::parse_relation(&a_set) { + Ok(a_set) => a_set, + Err(msg) => { + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "When parsing `a_set`:\n{}", + msg + ))); + } + }; + + let b_set = match vm::parsing::parse_relation(&b_set) { + Ok(b_set) => b_set, + Err(msg) => { + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "When parsing `b_set`:\n{}", + msg + ))); + } + }; + + let conjecture = match vm::parsing::parse_arithmetic(&conjecture) { + Ok(conjecture) => conjecture, + Err(msg) => { + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "When parsing `conjecture`:\n{}", + msg + ))); + } + }; + + todo!(); + Ok(Prover {}) + } +} + +/// A python module to brute force lower bounds per Ambainis & Co.'s adversarial +/// method. +#[pymodule] +fn adversary(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_class::()?; + Ok(()) +} diff --git a/src/vm/mod.rs b/src/vm/mod.rs new file mode 100644 index 0000000..4aa7afa --- /dev/null +++ b/src/vm/mod.rs @@ -0,0 +1,336 @@ +pub mod parsing; + +use parsing::ast; + +type Bytecode = (OpCode, usize); + +#[derive(Debug)] +pub struct VmCode { + code: Vec, +} + +#[derive(Debug)] +pub struct Vm<'code> { + code: &'code VmCode, + registers: Registers, +} + +#[derive(Debug)] +pub struct Registers { + x: u128, + y: u128, + n: u32, + p: u32, + k: u32, + xpop: u32, + ypop: u32, + npop: u32, + ppop: u32, + kpop: u32, +} + +#[derive(Debug)] +pub enum VmOutput { + Boolean(bool), + Arithmetic(f64), +} + +#[derive(Debug)] +enum OpCode { + UnaryBooleanOperator(ast::UnaryBooleanOperator), + BinaryArithmeticOperator(ast::BinaryArithmeticOperator), + ComparisonOperator(ast::ComparisonOperator), + UnaryArithmeticOperator(ast::UnaryArithmeticOperator), + BinaryBooleanOperator(ast::BinaryBooleanOperator), + Variable(ast::Variable), + Literal(usize), +} + +enum Expression { + Boolean(ast::BooleanExpression), + Arithmetic(ast::ArithmeticExpression), +} + +impl Registers { + pub fn load(x: u128, y: u128, n: u32, p: u32, k: u32) -> Self { + Self { + x, + y, + n, + p, + k, + xpop: x.count_ones(), + ypop: y.count_ones(), + npop: n.count_ones(), + ppop: p.count_ones(), + kpop: k.count_ones(), + } + } +} + +impl<'code> Vm<'code> { + pub fn compile_boolean(expression: ast::BooleanExpression) -> VmCode { + let mut code = Vec::::new(); + compile_expression(Expression::Boolean(expression), &mut code); + VmCode { code } + } + + pub fn compile_arithmetic(expression: ast::ArithmeticExpression) -> VmCode { + let mut code = Vec::::new(); + compile_expression(Expression::Arithmetic(expression), &mut code); + VmCode { code } + } + + pub fn load(code: &'code VmCode, registers: Registers) -> Self { + Vm { code, registers } + } + + pub fn run(&self) -> VmOutput { + // Alias for convenience + let code = &self.code.code; + let registers = &self.registers; + + struct Resolver<'f> { + f: &'f dyn Fn(&Resolver, usize) -> VmOutput, + } + + let resolver = Resolver { + f: &|resolver: &Resolver, op_index: usize| -> VmOutput { + let (opcode, offset) = &code[op_index]; + match opcode { + OpCode::UnaryBooleanOperator(op) => match op { + ast::UnaryBooleanOperator::Not => { + let value = (resolver.f)(resolver, op_index + 1); + match value { + VmOutput::Arithmetic(_) => panic!("Bad bytecode! Unary operator is followed by arithmetic resolution."), + VmOutput::Boolean(value) => VmOutput::Boolean(!value), + } + } + }, + OpCode::ComparisonOperator(op) => { + let left_operand = match (resolver.f)(resolver, op_index + 1) { + VmOutput::Arithmetic(value) => value, + VmOutput::Boolean(_) => panic!( + "Bad bytecode! Left operand of arithmetic operator is boolean." + ), + }; + let right_operand = match (resolver.f)(resolver, op_index + offset) { + VmOutput::Arithmetic(value) => value, + VmOutput::Boolean(_) => panic!( + "Bad bytecode! Right operand of arithmetic operator is boolean." + ), + }; + + match op { + ast::ComparisonOperator::GreaterOrEqual => { + VmOutput::Boolean(left_operand >= right_operand) + } + ast::ComparisonOperator::LessOrEqual => { + VmOutput::Boolean(left_operand <= right_operand) + } + ast::ComparisonOperator::GreaterThan => { + VmOutput::Boolean(left_operand > right_operand) + } + ast::ComparisonOperator::LessThan => { + VmOutput::Boolean(left_operand < right_operand) + } + ast::ComparisonOperator::NotEqual => { + VmOutput::Boolean(left_operand != right_operand) + } + ast::ComparisonOperator::Equal => { + VmOutput::Boolean(left_operand == right_operand) + } + } + } + OpCode::BinaryArithmeticOperator(op) => { + let left_operand = match (resolver.f)(resolver, op_index + 1) { + VmOutput::Arithmetic(value) => value, + VmOutput::Boolean(_) => panic!( + "Bad bytecode! Left operand of arithmetic operator is boolean." + ), + }; + let right_operand = match (resolver.f)(resolver, op_index + offset) { + VmOutput::Arithmetic(value) => value, + VmOutput::Boolean(_) => panic!( + "Bad bytecode! Right operand of arithmetic operator is boolean." + ), + }; + + match op { + ast::BinaryArithmeticOperator::Times => { + VmOutput::Arithmetic(left_operand * right_operand) + } + ast::BinaryArithmeticOperator::Divide => { + VmOutput::Arithmetic(left_operand / right_operand) + } + ast::BinaryArithmeticOperator::Plus => { + VmOutput::Arithmetic(left_operand + right_operand) + } + ast::BinaryArithmeticOperator::Minus => { + VmOutput::Arithmetic(left_operand - right_operand) + } + ast::BinaryArithmeticOperator::Xor => VmOutput::Arithmetic( + ((left_operand as u128) ^ (right_operand as u128)) as f64, + ), + } + } + OpCode::UnaryArithmeticOperator(op) => { + let value = match (resolver.f)(resolver, op_index + 1) { + VmOutput::Arithmetic(value) => value, + VmOutput::Boolean(_) => panic!("Bad bytecode! Arithmetic unary operator followed by boolean resolution."), + }; + match op { + ast::UnaryArithmeticOperator::Negative => VmOutput::Arithmetic(-value), + ast::UnaryArithmeticOperator::Ham => { + VmOutput::Arithmetic((value as u128).count_ones() as f64) + } + } + } + OpCode::BinaryBooleanOperator(op) => { + let left_operand = match (resolver.f)(resolver, op_index + 1) { + VmOutput::Boolean(value) => value, + VmOutput::Arithmetic(_) => panic!( + "Bad bytecode! Left operand of boolean operator is arithmetic." + ), + }; + let right_operand = match (resolver.f)(resolver, op_index + offset) { + VmOutput::Boolean(value) => value, + VmOutput::Arithmetic(_) => panic!( + "Bad bytecode! Right operand of boolean operator is arithmetic." + ), + }; + + VmOutput::Boolean(match op { + ast::BinaryBooleanOperator::And => left_operand & right_operand, + ast::BinaryBooleanOperator::Or => left_operand | right_operand, + ast::BinaryBooleanOperator::Xor => left_operand ^ right_operand, + }) + } + OpCode::Variable(var) => VmOutput::Arithmetic(match var { + ast::Variable::X => registers.x, + ast::Variable::Y => registers.y, + ast::Variable::N => registers.n as u128, + ast::Variable::P => registers.p as u128, + ast::Variable::K => registers.k as u128, + } as f64), + OpCode::Literal(lit) => VmOutput::Arithmetic(*lit as f64), + } + }, + }; + + (resolver.f)(&resolver, 0) + } +} + +/// Returns how many code-points (Bytecode units) were emitted +fn compile_expression(expression: Expression, code: &mut Vec) -> usize { + match expression { + Expression::Boolean(expression) => match expression { + ast::BooleanExpression::BinaryBooleanConjunction(expression) => { + let expression = *expression; + code.push((OpCode::BinaryBooleanOperator(expression.operator), 0)); + let index = code.len() - 1; + let left_operand_size = + compile_expression(Expression::Boolean(expression.left_operand), code); + let right_operand_size = + compile_expression(Expression::Boolean(expression.right_operand), code); + code[index].1 = left_operand_size + 1; + return 1 + left_operand_size + right_operand_size; + } + ast::BooleanExpression::UnaryBooleanConjunction(expression) => { + let expression = *expression; + code.push((OpCode::UnaryBooleanOperator(expression.operator), 0)); + let operand_size = + compile_expression(Expression::Boolean(expression.operand), code); + return 1 + operand_size; + } + ast::BooleanExpression::ComparisonConjunction(expression) => { + code.push((OpCode::ComparisonOperator(expression.operator), 0)); + let index = code.len() - 1; + let left_operand_size = match expression.left_operand { + ast::ArithmeticOperand::Literal(literal) => { + code.push((OpCode::Literal(literal), 0)); + 1_usize + } + ast::ArithmeticOperand::Expression(expression) => { + compile_expression(Expression::Arithmetic(*expression), code) + } + }; + let right_operand_size = match expression.right_operand { + ast::ArithmeticOperand::Literal(literal) => { + code.push((OpCode::Literal(literal), 0)); + 1_usize + } + ast::ArithmeticOperand::Expression(expression) => { + compile_expression(Expression::Arithmetic(*expression), code) + } + }; + code[index].1 = left_operand_size + 1; + return 1 + left_operand_size + right_operand_size; + } + }, + Expression::Arithmetic(expression) => match expression { + ast::ArithmeticExpression::Variable(variable) => { + code.push((OpCode::Variable(variable), 0)); + return 1; + } + ast::ArithmeticExpression::UnaryArithmeticConjunction(expression) => { + let expression = *expression; + code.push((OpCode::UnaryArithmeticOperator(expression.operator), 0)); + let operand_size = match expression.operand { + ast::ArithmeticOperand::Literal(literal) => { + code.push((OpCode::Literal(literal), 0)); + 1_usize + } + ast::ArithmeticOperand::Expression(expression) => { + compile_expression(Expression::Arithmetic(*expression), code) + } + }; + return 1 + operand_size; + } + ast::ArithmeticExpression::BinaryArithmeticConjunction(expression) => { + code.push((OpCode::BinaryArithmeticOperator(expression.operator), 0)); + let index = code.len() - 1; + let left_operand_size = match expression.left_operand { + ast::ArithmeticOperand::Literal(literal) => { + code.push((OpCode::Literal(literal), 0)); + 1_usize + } + ast::ArithmeticOperand::Expression(expression) => { + compile_expression(Expression::Arithmetic(*expression), code) + } + }; + let right_operand_size = match expression.right_operand { + ast::ArithmeticOperand::Literal(literal) => { + code.push((OpCode::Literal(literal), 0)); + 1_usize + } + ast::ArithmeticOperand::Expression(expression) => { + compile_expression(Expression::Arithmetic(*expression), code) + } + }; + code[index].1 = left_operand_size + 1; + return 1 + left_operand_size + right_operand_size; + } + }, + } +} + +#[test] +fn boolean_compilation_test() { + let expression = + parsing::parse_relation("and and < x 1 != 1 y < ham x k").expect("Valid AST"); + println!("{:?}", Vm::compile_boolean(expression)); +} + +#[test] +fn run_test() { + let expression = parsing::parse_relation("(= (ham (^ x y)) 1)"); + if let Err(e) = expression { + println!("{}", e); + panic!(); + } + let code = Vm::compile_boolean(expression.unwrap()); + let vm = Vm::load(&code, Registers::load(0b_1001, 0b_1111, 10, 2, 3)); + println!("{:?}", vm.run()); +} diff --git a/src/vm/parsing/ast.rs b/src/vm/parsing/ast.rs new file mode 100644 index 0000000..c6d4f46 --- /dev/null +++ b/src/vm/parsing/ast.rs @@ -0,0 +1,100 @@ +use std::fmt::Debug; + +#[derive(Debug)] +pub enum UnaryBooleanOperator { + Not, +} + +#[derive(Debug)] +pub enum BinaryArithmeticOperator { + Times, + Divide, + Plus, + Minus, + Xor, +} + +#[derive(Debug)] +pub enum ComparisonOperator { + GreaterOrEqual, + LessOrEqual, + GreaterThan, + LessThan, + NotEqual, + Equal, +} + +#[derive(Debug)] +pub enum UnaryArithmeticOperator { + Negative, + Ham, +} + +#[derive(Debug)] +pub enum BinaryBooleanOperator { + And, + Or, + Xor, +} + +#[derive(Debug)] +pub enum Variable { + X, + Y, + N, + P, + K, +} + +#[derive(Debug)] +pub enum BooleanExpression { + BinaryBooleanConjunction(Box), + UnaryBooleanConjunction(Box), + ComparisonConjunction(Box), +} + +#[derive(Debug)] +pub struct BinaryBooleanConjunction { + pub operator: BinaryBooleanOperator, + pub left_operand: BooleanExpression, + pub right_operand: BooleanExpression, +} + +#[derive(Debug)] +pub struct UnaryBooleanConjunction { + pub operator: UnaryBooleanOperator, + pub operand: BooleanExpression, +} + +#[derive(Debug)] +pub struct ComparisonConjunction { + pub operator: ComparisonOperator, + pub left_operand: ArithmeticOperand, + pub right_operand: ArithmeticOperand, +} + +#[derive(Debug)] +pub struct BinaryArithmeticConjunction { + pub operator: BinaryArithmeticOperator, + pub left_operand: ArithmeticOperand, + pub right_operand: ArithmeticOperand, +} + +#[derive(Debug)] +pub enum ArithmeticOperand { + Literal(usize), + Expression(Box), +} + +#[derive(Debug)] +pub enum ArithmeticExpression { + Variable(Variable), + UnaryArithmeticConjunction(Box), + BinaryArithmeticConjunction(Box), +} + +#[derive(Debug)] +pub struct UnaryArithmeticConjunction { + pub operator: UnaryArithmeticOperator, + pub operand: ArithmeticOperand, +} \ No newline at end of file diff --git a/src/vm/parsing/grammar.pest b/src/vm/parsing/grammar.pest new file mode 100644 index 0000000..2c5025e --- /dev/null +++ b/src/vm/parsing/grammar.pest @@ -0,0 +1,20 @@ +relation = { SOI ~ boolean_expression ~ EOI } +conjecture = { SOI ~ arithmetic_expression ~ EOI } +boolean_expression = { "(" ~ (binary_boolean_conjunction | unary_boolean_conjunction | comparison_expression) ~ ")" | + binary_boolean_conjunction | unary_boolean_conjunction | comparison_expression} +binary_boolean_conjunction = { binary_boolean_operator ~ boolean_expression ~ boolean_expression } +binary_boolean_operator = { "and" | "or" | "xor" } +unary_boolean_conjunction = { boolean_unary_operator ~ boolean_expression } +boolean_unary_operator = { "not" } +comparison_expression = { comparison_operator ~ arithmetic_operand ~ arithmetic_operand } +comparison_operator = { ">=" | "<=" | "!=" | ">" | "<" | "=" } +arithmetic_operand = { arithmetic_expression | number_literal } +arithmetic_expression = { "(" ~ (binary_arithmetic_conjunction | unary_arithmetic_conjunction | variable) ~ ")" | + binary_arithmetic_conjunction | unary_arithmetic_conjunction | variable } +binary_arithmetic_conjunction = { binary_arithmetic_operator ~ arithmetic_operand ~ arithmetic_operand } +binary_arithmetic_operator = { "*" | "/" | "+" | "-" | "^" } +unary_arithmetic_conjunction = { unary_arithmetic_operator ~ arithmetic_operand } +unary_arithmetic_operator = { "neg" | "ham" } +variable = { "x" | "y" | "n" | "p" | "k" } +number_literal = { ('0'..'9')+ } +WHITESPACE = _{ " " } \ No newline at end of file diff --git a/src/vm/parsing/mod.rs b/src/vm/parsing/mod.rs new file mode 100644 index 0000000..95b34df --- /dev/null +++ b/src/vm/parsing/mod.rs @@ -0,0 +1,348 @@ +pub mod ast; + +use self::ast::*; +use pest::Parser; + +#[derive(Parser)] +#[grammar = "vm/parsing/grammar.pest"] +struct ExpressionParser; + +pub fn parse_relation(expression: &str) -> Result { + let parse_result = ExpressionParser::parse(Rule::relation, expression); + + if let Err(e) = parse_result { + return Err(parse_error_to_human(expression, e)); + } + + let main_expr = parse_result + .unwrap() + .next() + .unwrap() + .into_inner() + .next() + .unwrap(); + let main_expr = match main_expr.as_rule() { + Rule::boolean_expression => parse_boolean_expression(main_expr), + _ => unreachable!(), + }; + + Ok(main_expr) +} + +pub fn parse_arithmetic(expression: &str) -> Result { + let parse_result = ExpressionParser::parse(Rule::conjecture, expression); + + if let Err(e) = parse_result { + return Err(parse_error_to_human(expression, e)); + } + + let main_expr = parse_result + .unwrap() + .next() + .unwrap() + .into_inner() + .next() + .unwrap(); + let main_expr = match main_expr.as_rule() { + Rule::arithmetic_expression => parse_arithmetic_expression(main_expr), + _ => unreachable!(), + }; + + Ok(main_expr) +} + +fn parse_error_to_human(expression: &str, error: pest::error::Error) -> String { + let mut error_msg = String::new(); + error_msg.push_str("Invalid expression:\n"); + match error.line_col { + pest::error::LineColLocation::Pos((line, col)) => { + with_caret(error.line(), line, col, "In ", &mut error_msg); + error_msg.push('\n'); + } + pest::error::LineColLocation::Span((sline, scol), (eline, ecol)) => { + let (start_line, end_line) = { + let mut lines = expression.lines(); + (lines.nth(sline).unwrap(), lines.nth(eline - sline).unwrap()) + }; + with_caret(start_line, sline, scol, "Starting at ", &mut error_msg); + with_caret(end_line, eline, ecol, "until ", &mut error_msg); + error_msg.push('\n'); + } + } + match error.variant { + pest::error::ErrorVariant::ParsingError { + positives, + negatives, + } => { + match positives.len() { + 0 => {} + 1 => { + error_msg.push_str("Expected "); + error_msg.push_str(rule_as_text(&positives[0])); + error_msg.push('.'); + } + 2 => { + error_msg.push_str("Expected either "); + error_msg.push_str(rule_as_text(&positives[0])); + error_msg.push_str("or "); + error_msg.push_str(rule_as_text(&positives[1])); + error_msg.push('.'); + } + _ => { + error_msg.push_str("Expected one of "); + for rule in &positives[..positives.len() - 1] { + error_msg.push_str(rule_as_text(rule)); + error_msg.push_str(", "); + } + error_msg.push_str("or "); + error_msg.push_str(rule_as_text(&positives.last().unwrap())); + error_msg.push('.'); + } + } + match negatives.len() { + 0 => {} + 1 => { + error_msg.push_str("Did not expect "); + error_msg.push_str(rule_as_text(&negatives[0])); + error_msg.push('.'); + } + _ => { + error_msg.push_str("Did not expect any of "); + for rule in &negatives[..negatives.len() - 1] { + error_msg.push_str(rule_as_text(rule)); + error_msg.push_str(", "); + } + error_msg.push_str("nor "); + error_msg.push_str(rule_as_text(&negatives.last().unwrap())); + error_msg.push('.'); + } + } + } + pest::error::ErrorVariant::CustomError { message } => { + error_msg.push_str(&message); + } + } + return error_msg; +} + +fn parse_boolean_expression(rule: pest::iterators::Pair) -> BooleanExpression { + let inner = rule.into_inner().next().unwrap(); + match inner.as_rule() { + Rule::binary_boolean_conjunction => BooleanExpression::BinaryBooleanConjunction(Box::new( + parse_binary_boolean_conjunction(inner), + )), + Rule::unary_boolean_conjunction => BooleanExpression::UnaryBooleanConjunction(Box::new( + parse_unary_boolean_conjunction(inner), + )), + Rule::comparison_expression => { + BooleanExpression::ComparisonConjunction(Box::new(parse_comparison_expression(inner))) + } + _ => unreachable!(), + } +} + +fn parse_comparison_expression(rule: pest::iterators::Pair) -> ComparisonConjunction { + let mut inner = rule.into_inner(); + let operator = parse_comparison_operator(inner.next().unwrap()); + let left_operand = parse_arithmetic_operand(inner.next().unwrap()); + let right_operand = parse_arithmetic_operand(inner.next().unwrap()); + let conjunction = ComparisonConjunction { + operator, + left_operand, + right_operand, + }; + conjunction +} + +fn parse_comparison_operator(rule: pest::iterators::Pair) -> ComparisonOperator { + match rule.as_str().trim() { + ">=" => ComparisonOperator::GreaterOrEqual, + "<=" => ComparisonOperator::LessOrEqual, + ">" => ComparisonOperator::GreaterThan, + "<" => ComparisonOperator::LessThan, + "=" => ComparisonOperator::Equal, + "!=" => ComparisonOperator::NotEqual, + _ => unreachable!(), + } +} + +fn parse_binary_arithmetic_conjunction( + rule: pest::iterators::Pair, +) -> BinaryArithmeticConjunction { + let mut inner = rule.into_inner(); + let operator = parse_binary_arithmetic_operator(inner.next().unwrap()); + let left_operand = parse_arithmetic_operand(inner.next().unwrap()); + let right_operand = parse_arithmetic_operand(inner.next().unwrap()); + let conjunction = BinaryArithmeticConjunction { + operator, + left_operand, + right_operand, + }; + conjunction +} + +fn parse_arithmetic_operand(rule: pest::iterators::Pair) -> ArithmeticOperand { + let inner = rule.into_inner().next().unwrap(); + match inner.as_rule() { + Rule::number_literal => { + let inner = inner.as_str().trim().parse::().unwrap(); + ArithmeticOperand::Literal(inner) + } + Rule::arithmetic_expression => { + ArithmeticOperand::Expression(Box::new(parse_arithmetic_expression(inner))) + } + _ => unreachable!(), + } +} + +fn parse_arithmetic_expression(rule: pest::iterators::Pair) -> ArithmeticExpression { + let inner = rule.into_inner().next().unwrap(); + match inner.as_rule() { + Rule::variable => ArithmeticExpression::Variable(parse_variable(inner)), + Rule::unary_arithmetic_conjunction => ArithmeticExpression::UnaryArithmeticConjunction( + Box::new(parse_unary_arithmetic_conjunction(inner)), + ), + Rule::binary_arithmetic_conjunction => ArithmeticExpression::BinaryArithmeticConjunction( + Box::new(parse_binary_arithmetic_conjunction(inner)), + ), + _ => unreachable!(), + } +} + +fn parse_unary_arithmetic_conjunction( + rule: pest::iterators::Pair, +) -> UnaryArithmeticConjunction { + let mut inner = rule.into_inner(); + let operator = parse_unary_arithmetic_operator(inner.next().unwrap()); + let operand = parse_arithmetic_operand(inner.next().unwrap()); + let conjunction = UnaryArithmeticConjunction { operator, operand }; + conjunction +} + +fn parse_unary_arithmetic_operator( + rule: pest::iterators::Pair, +) -> ast::UnaryArithmeticOperator { + match rule.as_str().trim() { + "neg" => ast::UnaryArithmeticOperator::Negative, + "ham" => ast::UnaryArithmeticOperator::Ham, + _ => unreachable!(), + } +} + +fn parse_variable(rule: pest::iterators::Pair) -> ast::Variable { + match rule.as_str().trim() { + "x" => ast::Variable::X, + "y" => ast::Variable::Y, + "n" => ast::Variable::N, + "p" => ast::Variable::P, + "k" => ast::Variable::K, + _ => unreachable!(), + } +} + +fn parse_binary_arithmetic_operator(rule: pest::iterators::Pair) -> BinaryArithmeticOperator { + match rule.as_str().trim() { + "*" => BinaryArithmeticOperator::Times, + "/" => BinaryArithmeticOperator::Divide, + "+" => BinaryArithmeticOperator::Plus, + "-" => BinaryArithmeticOperator::Minus, + "^" => BinaryArithmeticOperator::Xor, + _ => unreachable!(), + } +} + +fn parse_unary_boolean_conjunction(rule: pest::iterators::Pair) -> UnaryBooleanConjunction { + let mut inner = rule.into_inner(); + let operator = parse_unary_boolean_operator(inner.next().unwrap()); + let operand = parse_boolean_expression(inner.next().unwrap()); + let conjunction = UnaryBooleanConjunction { operator, operand }; + conjunction +} + +fn parse_unary_boolean_operator(rule: pest::iterators::Pair) -> UnaryBooleanOperator { + match rule.as_str().trim() { + "not" => UnaryBooleanOperator::Not, + _ => unreachable!(), + } +} + +fn parse_binary_boolean_conjunction(rule: pest::iterators::Pair) -> BinaryBooleanConjunction { + let mut inner = rule.into_inner(); + let operator = parse_binary_boolean_operator(inner.next().unwrap()); + let left_operand = parse_boolean_expression(inner.next().unwrap()); + let right_operand = parse_boolean_expression(inner.next().unwrap()); + let conjunction = ast::BinaryBooleanConjunction { + operator, + left_operand, + right_operand, + }; + conjunction +} + +fn parse_binary_boolean_operator(rule: pest::iterators::Pair) -> ast::BinaryBooleanOperator { + match rule.as_str().trim() { + "and" => ast::BinaryBooleanOperator::And, + "or" => ast::BinaryBooleanOperator::Or, + "xor" => ast::BinaryBooleanOperator::Xor, + _ => unreachable!(), + } +} + +fn with_caret(line: &str, line_number: usize, column: usize, preamble: &str, into: &mut String) { + into.push_str(&format!("{}l.{}: {}\n", preamble, line_number, line)); + let padding = preamble.len() + 2 + (line_number % 10) + 2; + for _ in 0..(column - 1 + padding) { + into.push(' '); + } + into.push('^'); +} + +fn rule_as_text(rule: &Rule) -> &'static str { + match rule { + Rule::EOI => "end of input", + Rule::WHITESPACE => "whitespace", + Rule::relation => "relation (boolean) expression", + Rule::conjecture => "conjecture (arithmetic) expression", + Rule::boolean_expression => "boolean expression", + Rule::binary_boolean_conjunction => "binary boolean relation", + Rule::binary_boolean_operator => "binary boolean operator", + Rule::unary_boolean_conjunction => "unary boolean expression", + Rule::boolean_unary_operator => "unary boolean operator", + Rule::binary_arithmetic_conjunction => "binary arithmetic expression", + Rule::binary_arithmetic_operator => "binary arithmetic operator", + Rule::arithmetic_operand => "arithmetic value", + Rule::number_literal => "number literal", + Rule::arithmetic_expression => "arithmetic expression", + Rule::variable => "x, y, n, or p", + Rule::unary_arithmetic_conjunction => "unary arithmetic expression", + Rule::unary_arithmetic_operator => "unary arithmetic operator", + Rule::comparison_expression => "arithmetic comparison", + Rule::comparison_operator => "arithmetic comparison operator", + } +} + +#[test] +fn parse_test() { + match parse_relation("< ham x 3") { + Err(e) => { + println!("{}", e); + panic!(); + } + Ok(ast) => { + println!("{:?}", ast) + } + } +} + +#[test] +fn parse_weird_test() { + match parse_relation("and and < x 1 != 1 y < ham x k") { + Err(e) => { + println!("{}", e); + panic!(); + } + Ok(ast) => { + println!("{:?}", ast) + } + } +} \ No newline at end of file