├── VERSION
├── MANIFEST.in
├── cairo_kernel
├── __init__.py
├── __main__.py
├── syntax_highlighting.py
├── install.py
├── kernel.py
└── repl.py
├── CHANGELOG.md
├── .gitignore
├── release.sh
├── tests
├── test_kernel_install.py
└── repl_test.py
├── .github
└── workflows
│ └── test.yml
├── LICENSE.txt
├── README.md
├── setup.py
└── notebooks
├── cairo_check_comments_work.ipynb
├── cairo_test_functions.ipynb
├── Cairo test multiple commands in one cell.ipynb
├── Cairo example.ipynb
└── Array sum tutorial.ipynb
/VERSION:
--------------------------------------------------------------------------------
1 | 0.0.7
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include VERSION
--------------------------------------------------------------------------------
/cairo_kernel/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### 0.0.7
2 |
3 | * Bumped up cairo-lang version number to 0.7.1
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **venv
2 | **__pycache__
3 | **.ipynb_checkpoints
4 | *.egg-info
5 | **dist
6 | **build
7 | MANIFEST
--------------------------------------------------------------------------------
/cairo_kernel/__main__.py:
--------------------------------------------------------------------------------
1 | from ipykernel.kernelapp import IPKernelApp
2 | from .kernel import CairoKernel
3 |
4 | IPKernelApp.launch_instance(kernel_class=CairoKernel)
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | # git commands
2 | git checkout master
3 | git pull
4 | git stash
5 |
6 | # tag commands
7 | git tag `cat VERSION`
8 | git push origin --tags
9 |
10 | # upload the library to pypi
11 | python setup.py sdist
12 | twine upload dist/*
--------------------------------------------------------------------------------
/tests/test_kernel_install.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | try:
3 | from jupyter_client.kernelspec import find_kernel_specs
4 | except ImportError:
5 | try:
6 | from IPython.kernel.kernelspec import find_kernel_specs
7 | except ImportError:
8 | print("Please install either Jupyter to IPython before continuing")
9 |
10 | def test_cairo_kernelspec():
11 | print(find_kernel_specs()['cairo'])
12 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test Package
2 |
3 | on: [push]
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - uses: actions/setup-python@v2
11 | with:
12 | python-version: 3.8.10
13 | - name: Run pytest
14 | run: |
15 | pip install -U setuptools
16 | pip install -U wheel
17 | pip install .
18 | pytest
--------------------------------------------------------------------------------
/tests/repl_test.py:
--------------------------------------------------------------------------------
1 | from cairo_kernel.repl import Repl
2 | import pytest
3 |
4 | my_repl = Repl()
5 |
6 | func = """
7 | func add2(x : felt) -> (x : felt):
8 | return (x=x+2)
9 | end
10 | """
11 |
12 | multiple_imports = """
13 | from starkware.cairo.common.uint256 import (
14 | Uint256, uint256_add, uint256_sub, uint256_lt, uint256_check
15 | )
16 | """
17 |
18 | import_struct = """
19 | struct Location:
20 | member row : felt
21 | member col : felt
22 | end"""
23 |
24 |
25 | @pytest.mark.parametrize("func", [func, multiple_imports, import_struct])
26 | def test_repl_run(func):
27 | my_repl.run(func)
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 | Copyright (c) 2020 Ankit CHiPLUNKAR
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 | The above copyright notice and this permission notice shall be included in all
10 | copies or substantial portions of the Software.
11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17 | SOFTWARE.
--------------------------------------------------------------------------------
/cairo_kernel/syntax_highlighting.py:
--------------------------------------------------------------------------------
1 | from pygments import token
2 | from pygments.lexer import RegexLexer, words
3 |
4 | KEYWORDS = [
5 | "func",
6 | "struct",
7 | "namespace",
8 | "end",
9 | "call",
10 | "ret",
11 | "jmp",
12 | "if",
13 | "let",
14 | "const",
15 | "import",
16 | "from",
17 | "as",
18 | "abs",
19 | "rel",
20 | "static_assert",
21 | "local",
22 | "tempvar",
23 | "felt",
24 | "return",
25 | "assert",
26 | "member",
27 | "cast",
28 | "else",
29 | "alloc_locals",
30 | "with",
31 | "with_attr",
32 | "nondet",
33 | ]
34 |
35 |
36 | class CairoLexer(RegexLexer):
37 | name = "cairo"
38 |
39 | tokens = {
40 | "root": [
41 | (words(KEYWORDS, prefix=r"\b", suffix=r"\b"), token.Keyword),
42 | (words(("SIZEOF_LOCALS", "SIZE"), prefix=r"\b", suffix=r"\b"), token.Literal),
43 | (r"%builtins|%lang", token.Keyword),
44 | (words(("ap", "fp"), prefix=r"\b", suffix=r"\b"), token.Name.Entity),
45 | (r"!=|->", token.Operator),
46 | (r"[+\-*/&]", token.Operator),
47 | (r"[:;,.=\[\]\(\)\{\}]", token.Punctuation),
48 | (r"-?[0-9]+", token.Number),
49 | (r"[a-zA-Z_][a-zA-Z_0-9]*", token.Text),
50 | (r"#.*", token.Comment),
51 | (r"%\{(.|\n)*?%\}", token.Text),
52 | (r"%\[(.|\n)*?%\]", token.Text),
53 | (r"@\w+", token.Keyword),
54 | (r"<[a-zA-Z0-9 _\-]+>", token.Comment),
55 | (r" ", token.Text),
56 | ]
57 | }
58 |
--------------------------------------------------------------------------------
/cairo_kernel/install.py:
--------------------------------------------------------------------------------
1 | """Script that installs the kernel"""
2 |
3 | import os
4 | import sys
5 | import json
6 | import getopt
7 | from shutil import copyfile
8 |
9 | try:
10 | from jupyter_client.kernelspec import install_kernel_spec
11 | except ImportError:
12 | try:
13 | from IPython.kernel.kernelspec import install_kernel_spec
14 | except ImportError:
15 | print("Please install either Jupyter to IPython before continuing")
16 | from IPython.utils.tempdir import TemporaryDirectory
17 |
18 | kernel_json = {
19 | "argv": [sys.executable,
20 | "-m", "cairo_kernel", "-f", "{connection_file}"],
21 | "display_name": "Cairo",
22 | "language": "text",
23 | "name": "Cairo",
24 | }
25 |
26 |
27 | def install_my_kernel_spec(user=True, prefix=None):
28 | user = '--user' in sys.argv or not _is_root()
29 | with TemporaryDirectory() as td:
30 | os.chmod(td, 0o755) # Starts off as 700, not user readable
31 |
32 | with open(os.path.join(td, 'kernel.json'), 'w') as f:
33 | json.dump(kernel_json, f, sort_keys=True)
34 |
35 | try:
36 | install_kernel_spec(td, kernel_json['name'], user=user,
37 | replace=True, prefix=prefix)
38 | except OSError:
39 | install_kernel_spec(td, kernel_json['name'], user=not user,
40 | replace=True, prefix=prefix)
41 |
42 | def main(argv=()):
43 | prefix = None
44 | user = not _is_root()
45 |
46 | opts, _ = getopt.getopt(argv[1:], '', ['user', 'prefix='])
47 | for k, v in opts:
48 | if k == '--user':
49 | user = True
50 | elif k == '--prefix':
51 | prefix = v
52 | user = False
53 |
54 | install_my_kernel_spec(user=user, prefix=prefix)
55 |
56 |
57 | def _is_root():
58 | try:
59 | return os.geteuid() == 0
60 | except AttributeError:
61 | return False # assume not an admin on non-Unix platforms
62 |
63 |
64 | if __name__ == '__main__':
65 | main(argv=sys.argv)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | cairo_jupyter
2 | ===========
3 |
4 | ``cairo_jupyter`` hosts notebooks and code for cairo-kernel its a simple Jupyter kernel for Cairo a smart contract programing language for STARKs.
5 |
6 | Hosted on binder: [](https://mybinder.org/v2/gh/ankitchiplunkar/cairo-jupyter/HEAD)
7 |
8 | Project managenemt: [Kanban](https://ankitchiplunkar.notion.site/Cairo-kernel-starkware-py-5f64ee70cfde4578969c430bf1a14531)
9 |
10 | Installation
11 | ------------
12 | 1. Install virtualenv
13 |
14 | ```
15 | virtualenv -p python3.8 venv
16 | source venv/bin/activate
17 | ```
18 |
19 | 2. To install ``cairo_kernel``:
20 |
21 | ```
22 | # Upgrade to latest version of setuptools and wheel
23 | pip install -U setuptools
24 | pip install -U wheel
25 | pip install cairo-kernel
26 | ```
27 |
28 | Using the Kernel
29 | ---------------------
30 |
31 | [Cairo example](https://github.com/ankitchiplunkar/cairo-jupyter/blob/master/notebooks/Cairo%20example.ipynb) is a working example of the notebook.
32 |
33 |
34 | **Notebook**: The *New* menu in the notebook should show an option for an Cairo notebook.
35 |
36 | **Console frontends**: To use it with the console frontends, add ``--kernel cairo`` to
37 | their command line arguments.
38 |
39 | Running the kernel on notebook
40 | --------------------------------
41 |
42 | 1. Start the notebook
43 |
44 | ```
45 | jupyter notebook
46 | ```
47 |
48 | 2. Start cairo kernel via UI
49 |
50 | 
51 |
52 |
53 |
54 | Development
55 | -------------------------------
56 |
57 | 1. Clone & enter the repo.
58 |
59 | ```
60 | git clone https://github.com/ankitchiplunkar/cairo-jupyter.git
61 | ```
62 |
63 | 2. Install required libraries.
64 |
65 | ```
66 | pip install -r requirements.txt
67 | ```
68 |
69 | 3. Install the cairo-jupyter library locally:
70 | ```
71 | pip install -e .
72 | python -m cairo_kernel.install
73 | ```
74 |
75 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import setuptools
4 | from distutils.command.install import install
5 | from os import path
6 |
7 | # managing version
8 | with open("VERSION", 'r') as f:
9 | VERSION = f.read()
10 |
11 | # managing readme
12 | this_directory = path.abspath(path.dirname(__file__))
13 | with open(path.join(this_directory, 'README.md'), 'r') as f:
14 | readme = f.read()
15 |
16 | class InstallWithKernelspec(install):
17 | def run(self):
18 | # Regular installation
19 | install.run(self)
20 |
21 | # Kernel installation
22 | if "NO_KERNEL_INSTALL" in os.environ:
23 | # If the NO_KERNEL_INSTALL env variable is set then skip the kernel installation.
24 | return
25 | else:
26 | print("Reached kernel install")
27 | from cairo_kernel import install as kernel_install
28 | kernel_install.main(argv=sys.argv)
29 |
30 | setuptools.setup(
31 | name='cairo_kernel',
32 | version=VERSION,
33 | packages=['cairo_kernel'],
34 | cmdclass={'install': InstallWithKernelspec},
35 | description='Jupyter kernel for Cairo language',
36 | long_description=readme,
37 | long_description_content_type='text/markdown',
38 | author='Ankit Chiplunkar, Arie Benhamou',
39 | author_email='ankitchiplunkar@gmail.com, benhamou.arie@gmail.com',
40 | python_requires='>=3.6, <3.10',
41 | keywords=['ethereum', 'starkware'],
42 | setup_requires=[
43 | "jupyter_client==6.1.12",
44 | "ipykernel==6.4.2",
45 | ],
46 | install_requires=[
47 | "pygments==2.11.2",
48 | "contextvars==2.4",
49 | "cairo-lang==0.7.1",
50 | "jupyter==1.0.0",
51 | "jupyter_client==6.1.12",
52 | "ipykernel==6.4.2",
53 | "pytest==6.2.5",
54 | ],
55 | classifiers=[
56 | 'Development Status :: 3 - Alpha',
57 | 'Intended Audience :: Developers',
58 | 'Natural Language :: English',
59 | 'Programming Language :: Python :: 3',
60 | 'Programming Language :: Python :: 3.6',
61 | ]
62 | )
--------------------------------------------------------------------------------
/notebooks/cairo_check_comments_work.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "id": "d70927cf",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "# These comments should be discarded\n",
11 | "\n",
12 | "# The empty line below used to return an error\n",
13 | "\n",
14 | "tempvar x = 10\n",
15 | "\n",
16 | "# The empty line above used to return an error"
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "execution_count": 2,
22 | "id": "d6196e17",
23 | "metadata": {},
24 | "outputs": [
25 | {
26 | "name": "stdout",
27 | "output_type": "stream",
28 | "text": [
29 | "10"
30 | ]
31 | }
32 | ],
33 | "source": [
34 | "x"
35 | ]
36 | },
37 | {
38 | "cell_type": "code",
39 | "execution_count": 3,
40 | "id": "2effbbdd",
41 | "metadata": {},
42 | "outputs": [],
43 | "source": [
44 | "from starkware.cairo.common.serialize import serialize_word"
45 | ]
46 | },
47 | {
48 | "cell_type": "code",
49 | "execution_count": 4,
50 | "id": "b6e816e6",
51 | "metadata": {},
52 | "outputs": [],
53 | "source": [
54 | "func main{output_ptr : felt*}():\n",
55 | " #Prints '1234'\n",
56 | " serialize_word(1234)\n",
57 | " #Prints '4321'\n",
58 | " serialize_word(4321)\n",
59 | " return ()\n",
60 | "end"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": 6,
66 | "id": "a9172102",
67 | "metadata": {},
68 | "outputs": [
69 | {
70 | "name": "stdout",
71 | "output_type": "stream",
72 | "text": [
73 | "32"
74 | ]
75 | }
76 | ],
77 | "source": [
78 | "# Function still doesn't return the right output, but adding comments inside doesn't make it bug anymore \n",
79 | "main"
80 | ]
81 | }
82 | ],
83 | "metadata": {
84 | "kernelspec": {
85 | "display_name": "Cairo",
86 | "language": "text",
87 | "name": "cairo"
88 | },
89 | "language_info": {
90 | "file_extension": ".cairo",
91 | "mimetype": "text/plain",
92 | "name": "Cairo"
93 | }
94 | },
95 | "nbformat": 4,
96 | "nbformat_minor": 5
97 | }
98 |
--------------------------------------------------------------------------------
/cairo_kernel/kernel.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import traceback
3 |
4 | from ipykernel.kernelbase import Kernel
5 | from prompt_toolkit.lexers import PygmentsLexer
6 | from .syntax_highlighting import CairoLexer
7 | from starkware.cairo.lang.compiler.error_handling import LocationError
8 | from .repl import Repl
9 |
10 |
11 | class CairoKernel(Kernel):
12 | implementation = "Cairo"
13 | implementation_version = "0.0.1"
14 | language = "cairo"
15 | language_version = "0.4.2"
16 | language_info = {
17 | "name": "Cairo",
18 | "mimetype": "text/plain",
19 | "file_extension": ".cairo",
20 | # TODO(add lexer)"
21 | # "pygments_lexer": PygmentsLexer(CairoLexer),
22 | }
23 | banner = "Cairo Kernel - scale dApps using STARKs"
24 | repl = Repl()
25 |
26 | def do_execute(
27 | self, code, silent, store_history=True, user_expressions=None, allow_stdin=False
28 | ):
29 | if not silent:
30 | try:
31 | value = self.repl.run(code)
32 | stream_content = {"name": "stdout", "text": str(value) if value else value}
33 | self.send_response(self.iopub_socket, "stream", stream_content)
34 | return {
35 | "status": "ok",
36 | # The base class increments the execution count
37 | "execution_count": self.execution_count,
38 | "payload": [],
39 | "user_expressions": {},
40 | }
41 |
42 | except LocationError as exc:
43 | error_msg = str(exc)
44 | except Exception:
45 | error_msg = str(traceback.print_exc())
46 |
47 | stream_content = {"name": "stdout", "text": error_msg}
48 | self.send_response(self.iopub_socket, "stream", stream_content)
49 | return {
50 | "status": "error",
51 | #TODO(arie): set right parameteres for status = error
52 | # "ename": "my_ename",
53 | # "evalue": "my_evalue",
54 | # "traceback": error_msg
55 | }
56 |
57 |
58 |
59 | if __name__ == "__main__":
60 | from ipykernel.kernelapp import IPKernelApp
61 | IPKernelApp.launch_instance(kernel_class=CairoKernel)
62 |
--------------------------------------------------------------------------------
/notebooks/cairo_test_functions.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "id": "0718c460",
7 | "metadata": {
8 | "scrolled": false
9 | },
10 | "outputs": [],
11 | "source": [
12 | "%builtins output"
13 | ]
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": 2,
18 | "id": "40236556",
19 | "metadata": {
20 | "scrolled": true
21 | },
22 | "outputs": [],
23 | "source": [
24 | "func add1(x : felt) -> (x : felt): \n",
25 | " return (x=x+1) \n",
26 | " end"
27 | ]
28 | },
29 | {
30 | "cell_type": "code",
31 | "execution_count": 3,
32 | "id": "f9b17f12",
33 | "metadata": {},
34 | "outputs": [],
35 | "source": [
36 | "let (a) = add1(10)"
37 | ]
38 | },
39 | {
40 | "cell_type": "code",
41 | "execution_count": 4,
42 | "id": "a0886540",
43 | "metadata": {},
44 | "outputs": [
45 | {
46 | "name": "stdout",
47 | "output_type": "stream",
48 | "text": [
49 | "11"
50 | ]
51 | }
52 | ],
53 | "source": [
54 | "a"
55 | ]
56 | },
57 | {
58 | "cell_type": "code",
59 | "execution_count": 5,
60 | "id": "9219009c",
61 | "metadata": {},
62 | "outputs": [],
63 | "source": [
64 | "from starkware.cairo.common.serialize import serialize_word"
65 | ]
66 | },
67 | {
68 | "cell_type": "code",
69 | "execution_count": null,
70 | "id": "82db1a61",
71 | "metadata": {},
72 | "outputs": [
73 | {
74 | "name": "stdout",
75 | "output_type": "stream",
76 | "text": [
77 | ":1:1: While trying to retrieve the implicit argument 'output_ptr' in:\n",
78 | "serialize_word(1234)\n",
79 | "^******************^\n",
80 | "/home/arie/cairo_venv/lib/python3.8/site-packages/starkware/cairo/common/serialize.cairo:2:21: Unknown identifier 'output_ptr'.\n",
81 | "func serialize_word{output_ptr : felt*}(word):\n",
82 | " ^****************^"
83 | ]
84 | }
85 | ],
86 | "source": [
87 | "serialize_word(1234)"
88 | ]
89 | },
90 | {
91 | "cell_type": "code",
92 | "execution_count": null,
93 | "id": "437ea328",
94 | "metadata": {},
95 | "outputs": [],
96 | "source": []
97 | }
98 | ],
99 | "metadata": {
100 | "kernelspec": {
101 | "display_name": "Cairo",
102 | "language": "text",
103 | "name": "cairo"
104 | },
105 | "language_info": {
106 | "file_extension": ".cairo",
107 | "mimetype": "text/plain",
108 | "name": "Cairo"
109 | }
110 | },
111 | "nbformat": 4,
112 | "nbformat_minor": 5
113 | }
114 |
--------------------------------------------------------------------------------
/notebooks/Cairo test multiple commands in one cell.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "id": "3e2ea0b0",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "from starkware.cairo.common.alloc import alloc\n",
11 | "from starkware.cairo.common.serialize import serialize_word"
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": 2,
17 | "id": "9318d1cd",
18 | "metadata": {},
19 | "outputs": [
20 | {
21 | "name": "stdout",
22 | "output_type": "stream",
23 | "text": [
24 | "2:0"
25 | ]
26 | }
27 | ],
28 | "source": [
29 | "output_ptr"
30 | ]
31 | },
32 | {
33 | "cell_type": "code",
34 | "execution_count": null,
35 | "id": "020ef30c",
36 | "metadata": {},
37 | "outputs": [
38 | {
39 | "name": "stdout",
40 | "output_type": "stream",
41 | "text": [
42 | ":1:1: Unknown value for memory cell at address 2:0.\n",
43 | "[output_ptr]\n",
44 | "^**********^"
45 | ]
46 | }
47 | ],
48 | "source": [
49 | "[output_ptr]"
50 | ]
51 | },
52 | {
53 | "cell_type": "code",
54 | "execution_count": 4,
55 | "id": "af269190",
56 | "metadata": {},
57 | "outputs": [],
58 | "source": [
59 | "func main{output_ptr : felt*}():\n",
60 | " tempvar x = 10\n",
61 | " tempvar y = x + x\n",
62 | " tempvar z = y * y + x\n",
63 | " serialize_word(x)\n",
64 | " serialize_word(y)\n",
65 | " serialize_word(z)\n",
66 | " return ()\n",
67 | "end"
68 | ]
69 | },
70 | {
71 | "cell_type": "code",
72 | "execution_count": 5,
73 | "id": "40236556",
74 | "metadata": {
75 | "scrolled": true
76 | },
77 | "outputs": [],
78 | "source": [
79 | "func main2{output_ptr : felt*}():\n",
80 | " tempvar x = 10\n",
81 | " tempvar y = x + x\n",
82 | " tempvar z = y * y + x\n",
83 | " serialize_word(x)\n",
84 | " serialize_word(y)\n",
85 | " serialize_word(z)\n",
86 | " return ()\n",
87 | "end\n",
88 | "# This runs properly (see cell below)\n",
89 | "main()"
90 | ]
91 | },
92 | {
93 | "cell_type": "code",
94 | "execution_count": 6,
95 | "id": "de52b009",
96 | "metadata": {},
97 | "outputs": [
98 | {
99 | "name": "stdout",
100 | "output_type": "stream",
101 | "text": [
102 | "2:3"
103 | ]
104 | }
105 | ],
106 | "source": [
107 | "output_ptr"
108 | ]
109 | },
110 | {
111 | "cell_type": "code",
112 | "execution_count": null,
113 | "id": "9e57d407",
114 | "metadata": {},
115 | "outputs": [],
116 | "source": []
117 | }
118 | ],
119 | "metadata": {
120 | "kernelspec": {
121 | "display_name": "Cairo",
122 | "language": "text",
123 | "name": "cairo"
124 | },
125 | "language_info": {
126 | "file_extension": ".cairo",
127 | "mimetype": "text/plain",
128 | "name": "Cairo"
129 | }
130 | },
131 | "nbformat": 4,
132 | "nbformat_minor": 5
133 | }
134 |
--------------------------------------------------------------------------------
/notebooks/Cairo example.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "f9a59b66",
7 | "metadata": {},
8 | "outputs": [
9 | {
10 | "name": "stdout",
11 | "output_type": "stream",
12 | "text": [
13 | ":1:1: Redefinition of builtins directive.\n",
14 | "%builtins output\n",
15 | "^**************^"
16 | ]
17 | }
18 | ],
19 | "source": [
20 | "%builtins output"
21 | ]
22 | },
23 | {
24 | "cell_type": "code",
25 | "execution_count": 3,
26 | "id": "35d56058",
27 | "metadata": {},
28 | "outputs": [],
29 | "source": [
30 | "tempvar x = 10"
31 | ]
32 | },
33 | {
34 | "cell_type": "code",
35 | "execution_count": 4,
36 | "id": "4bdd7a0c",
37 | "metadata": {},
38 | "outputs": [],
39 | "source": [
40 | "tempvar y = x + x"
41 | ]
42 | },
43 | {
44 | "cell_type": "code",
45 | "execution_count": 5,
46 | "id": "681b35e6",
47 | "metadata": {},
48 | "outputs": [
49 | {
50 | "name": "stdout",
51 | "output_type": "stream",
52 | "text": [
53 | "20"
54 | ]
55 | }
56 | ],
57 | "source": [
58 | "y"
59 | ]
60 | },
61 | {
62 | "cell_type": "code",
63 | "execution_count": 6,
64 | "id": "3027c4e9",
65 | "metadata": {},
66 | "outputs": [],
67 | "source": [
68 | "tempvar z = x*x + y"
69 | ]
70 | },
71 | {
72 | "cell_type": "code",
73 | "execution_count": 7,
74 | "id": "f525a1fb",
75 | "metadata": {},
76 | "outputs": [
77 | {
78 | "name": "stdout",
79 | "output_type": "stream",
80 | "text": [
81 | "120"
82 | ]
83 | }
84 | ],
85 | "source": [
86 | "z"
87 | ]
88 | },
89 | {
90 | "cell_type": "code",
91 | "execution_count": null,
92 | "id": "f4a403af",
93 | "metadata": {},
94 | "outputs": [
95 | {
96 | "name": "stdout",
97 | "output_type": "stream",
98 | "text": [
99 | "None"
100 | ]
101 | }
102 | ],
103 | "source": [
104 | "# test"
105 | ]
106 | },
107 | {
108 | "cell_type": "code",
109 | "execution_count": 9,
110 | "id": "87fdaf70",
111 | "metadata": {},
112 | "outputs": [],
113 | "source": [
114 | "from starkware.cairo.common.serialize import serialize_word"
115 | ]
116 | },
117 | {
118 | "cell_type": "code",
119 | "execution_count": 10,
120 | "id": "db884d3a",
121 | "metadata": {},
122 | "outputs": [],
123 | "source": [
124 | "serialize_word(5)"
125 | ]
126 | },
127 | {
128 | "cell_type": "code",
129 | "execution_count": 13,
130 | "id": "d7eeabfa",
131 | "metadata": {},
132 | "outputs": [],
133 | "source": [
134 | "func add1{output_ptr : felt*}(x : felt) -> (x : felt): \n",
135 | " return (x=x+1) \n",
136 | " end\n"
137 | ]
138 | },
139 | {
140 | "cell_type": "code",
141 | "execution_count": 14,
142 | "id": "d04c77bb",
143 | "metadata": {},
144 | "outputs": [],
145 | "source": [
146 | "let (a) = add1(10)"
147 | ]
148 | },
149 | {
150 | "cell_type": "code",
151 | "execution_count": 15,
152 | "id": "297fa6a8",
153 | "metadata": {},
154 | "outputs": [
155 | {
156 | "name": "stdout",
157 | "output_type": "stream",
158 | "text": [
159 | "11"
160 | ]
161 | }
162 | ],
163 | "source": [
164 | "a"
165 | ]
166 | },
167 | {
168 | "cell_type": "code",
169 | "execution_count": null,
170 | "id": "38b7244c",
171 | "metadata": {},
172 | "outputs": [],
173 | "source": []
174 | }
175 | ],
176 | "metadata": {
177 | "kernelspec": {
178 | "display_name": "Cairo",
179 | "language": "text",
180 | "name": "cairo"
181 | },
182 | "language_info": {
183 | "file_extension": ".cairo",
184 | "mimetype": "text/plain",
185 | "name": "Cairo"
186 | }
187 | },
188 | "nbformat": 4,
189 | "nbformat_minor": 5
190 | }
191 |
--------------------------------------------------------------------------------
/notebooks/Array sum tutorial.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "fdc60609",
6 | "metadata": {},
7 | "source": [
8 | "# Array sum tutorial\n",
9 | "This is a tutorial in the to demonstrate the functioning of cairo and write a siple recursive function to sum arrays\n",
10 | "\n",
11 | "Cairo is not a high-level language. It’s a low-level language with some powerful syntactic sugar to allow writing maintainable code. The advantage is that the Cairo language allows you to write very efficient code "
12 | ]
13 | },
14 | {
15 | "cell_type": "markdown",
16 | "id": "30da1ac2",
17 | "metadata": {},
18 | "source": [
19 | "### The `array_sum` function"
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": 1,
25 | "id": "975865bd",
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "func array_sum(arr : felt*, size) -> (sum):\n",
30 | " if size == 0:\n",
31 | " return (sum=0)\n",
32 | " end\n",
33 | "\n",
34 | " # size is not zero.\n",
35 | " let (sum_of_rest) = array_sum(arr=arr + 1, size=size - 1)\n",
36 | " return (sum=[arr] + sum_of_rest)\n",
37 | "end"
38 | ]
39 | },
40 | {
41 | "cell_type": "markdown",
42 | "id": "e2bbe167",
43 | "metadata": {},
44 | "source": [
45 | "`func array_sum(arr : felt*, size) -> (sum)`: defines a function named array_sum which takes two arguments: arr and size. arr points to an array of size elements. The type of arr is `felt*` which is a pointer (for more information about felt, see below). The function declares that it returns one value called sum. The scope of the function ends with the word end (although end doesn’t mean that the function returns – you must add the return statement explicitly).\n",
46 | "\n",
47 | "You may have noticed that we’ve used recursion in the code above rather than the loop structure you may have expected. The main reason for this is that the Cairo memory is immutable – once you write the value of a memory cell, this cell cannot change in the future. Immutability of cairo is also why, **we cannot rerun the same function again in the kernel**, because cairo will throw a `Redefinition` error."
48 | ]
49 | },
50 | {
51 | "cell_type": "markdown",
52 | "id": "afbf49da",
53 | "metadata": {},
54 | "source": [
55 | "### Importing variables\n",
56 | "\n",
57 | "The line `from starkware.cairo.common.serialize import serialize_word` instructs the compiler to compile the file `starkware/cairo/common/serialize.cairo`, and to expose the identifier serialize_word."
58 | ]
59 | },
60 | {
61 | "cell_type": "code",
62 | "execution_count": 2,
63 | "id": "d1cb5ae5",
64 | "metadata": {},
65 | "outputs": [],
66 | "source": [
67 | "from starkware.cairo.common.serialize import serialize_word"
68 | ]
69 | },
70 | {
71 | "cell_type": "markdown",
72 | "id": "e53e1897",
73 | "metadata": {},
74 | "source": [
75 | "We use the standard library function alloc() to allocate a new memory segment. In practice the exact location of the allocated memory will be determined only when the program terminates, which allows us to avoid specifying the size of the allocation"
76 | ]
77 | },
78 | {
79 | "cell_type": "code",
80 | "execution_count": 3,
81 | "id": "2a2e595e",
82 | "metadata": {},
83 | "outputs": [],
84 | "source": [
85 | "from starkware.cairo.common.alloc import alloc"
86 | ]
87 | },
88 | {
89 | "cell_type": "markdown",
90 | "id": "6563d1b7",
91 | "metadata": {},
92 | "source": [
93 | "### The `main` function"
94 | ]
95 | },
96 | {
97 | "cell_type": "code",
98 | "execution_count": 4,
99 | "id": "7b39b494",
100 | "metadata": {},
101 | "outputs": [],
102 | "source": [
103 | "func main{output_ptr : felt*}():\n",
104 | " const ARRAY_SIZE = 3\n",
105 | "\n",
106 | " # Allocate an array.\n",
107 | " let (ptr) = alloc()\n",
108 | "\n",
109 | " # Populate some values in the array.\n",
110 | " assert [ptr] = 9\n",
111 | " assert [ptr + 1] = 16\n",
112 | " assert [ptr + 2] = 25\n",
113 | "\n",
114 | " # Call array_sum to compute the sum of the elements.\n",
115 | " let (sum) = array_sum(arr=ptr, size=ARRAY_SIZE)\n",
116 | "\n",
117 | " # Write the sum to the program output.\n",
118 | " serialize_word(sum)\n",
119 | "\n",
120 | " return ()\n",
121 | "end"
122 | ]
123 | },
124 | {
125 | "cell_type": "markdown",
126 | "id": "3cdb6ddb",
127 | "metadata": {},
128 | "source": [
129 | "Now, let’s write a `main()` function that will use `array_sum()`. To do this, we will need to allocate space for the array. The syntax `{output_ptr : felt*}` declares an “implicit argument”, which means that behind the scenes, it adds both a corresponding argument and return value. More information about implicit arguments can be found in [here](https://www.cairo-lang.org/docs/how_cairo_works/builtins.html#implicit-arguments).\n",
130 | "\n",
131 | "To write a to the output, we can use the library function `serialize_word(x)`. serialize_word gets one argument (the value we want to write) and one implicit argument output_ptr. It writes x to the memory cell pointed by output_ptr and returns output_ptr + 1. Now the implicit argument mechanism kicks in: in the first call to serialize_word() the Cairo compiler passes the value of output_ptr as the implicit argument. In the second call it uses the value returned by the first call."
132 | ]
133 | },
134 | {
135 | "cell_type": "markdown",
136 | "id": "dd8bbadf",
137 | "metadata": {},
138 | "source": [
139 | "### The `main` function\n",
140 | "\n",
141 | "Running the main function is as simple as caling `main()`"
142 | ]
143 | },
144 | {
145 | "cell_type": "code",
146 | "execution_count": 5,
147 | "id": "2ff7bd4c",
148 | "metadata": {},
149 | "outputs": [],
150 | "source": [
151 | "main()"
152 | ]
153 | },
154 | {
155 | "cell_type": "markdown",
156 | "id": "1bc2f368",
157 | "metadata": {},
158 | "source": [
159 | "To read the result of `main` function we print the value at second last location of `output_ptr`"
160 | ]
161 | },
162 | {
163 | "cell_type": "code",
164 | "execution_count": 6,
165 | "id": "e6f52caa",
166 | "metadata": {},
167 | "outputs": [
168 | {
169 | "name": "stdout",
170 | "output_type": "stream",
171 | "text": [
172 | "2:1"
173 | ]
174 | }
175 | ],
176 | "source": [
177 | "output_ptr"
178 | ]
179 | },
180 | {
181 | "cell_type": "code",
182 | "execution_count": 7,
183 | "id": "aae24575",
184 | "metadata": {},
185 | "outputs": [
186 | {
187 | "name": "stdout",
188 | "output_type": "stream",
189 | "text": [
190 | "50"
191 | ]
192 | }
193 | ],
194 | "source": [
195 | "[output_ptr-1]"
196 | ]
197 | }
198 | ],
199 | "metadata": {
200 | "kernelspec": {
201 | "display_name": "Cairo",
202 | "language": "text",
203 | "name": "cairo"
204 | },
205 | "language_info": {
206 | "file_extension": ".cairo",
207 | "mimetype": "text/plain",
208 | "name": "Cairo"
209 | }
210 | },
211 | "nbformat": 4,
212 | "nbformat_minor": 5
213 | }
214 |
--------------------------------------------------------------------------------
/cairo_kernel/repl.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from typing import List, Union
3 |
4 | from starkware.cairo.lang.compiler.assembler import assemble
5 | from starkware.cairo.lang.compiler.ast.cairo_types import TypePointer, TypeStruct, TypeFelt
6 | from starkware.cairo.lang.compiler.ast.code_elements import (
7 | CodeElement,
8 | CodeElementFunction,
9 | CodeElementImport,
10 | CodeElementScoped,
11 | )
12 |
13 | from starkware.cairo.lang.compiler.ast.expr import Expression
14 | from starkware.cairo.lang.compiler.ast.expr_func_call import ExprFuncCall
15 | from starkware.cairo.lang.compiler.ast.module import CairoModule
16 | from starkware.cairo.lang.compiler.ast.node import AstNode
17 | from starkware.cairo.lang.compiler.cairo_compile import get_module_reader
18 | from starkware.cairo.lang.compiler.constants import MAIN_SCOPE
19 | from starkware.cairo.lang.compiler.error_handling import LocationError
20 | from starkware.cairo.lang.compiler.expression_evaluator import ExpressionEvaluator
21 | from starkware.cairo.lang.compiler.identifier_definition import (
22 | FutureIdentifierDefinition,
23 | ReferenceDefinition,
24 | )
25 | from starkware.cairo.lang.compiler.identifier_manager import IdentifierManager
26 | from starkware.cairo.lang.compiler.import_loader import (
27 | DirectDependenciesCollector,
28 | ImportsCollector,
29 | )
30 | from starkware.cairo.lang.compiler.instruction import Register
31 | from starkware.cairo.lang.compiler.parser import ParserError, parse
32 | from starkware.cairo.lang.compiler.preprocessor.identifier_collector import IdentifierCollector
33 | from starkware.cairo.lang.compiler.preprocessor.preprocessor import Preprocessor, ReferenceState
34 | from starkware.cairo.lang.compiler.preprocessor.struct_collector import StructCollector
35 | from starkware.cairo.lang.compiler.preprocessor.unique_labels import UniqueLabelCreator
36 | from starkware.cairo.lang.compiler.program import StrippedProgram
37 | from starkware.cairo.lang.compiler.scoped_name import ScopedName
38 | from .syntax_highlighting import CairoLexer
39 | from starkware.cairo.lang.vm.cairo_runner import CairoRunner
40 |
41 | PRIME = 2 ** 251 + 17 * 2 ** 192 + 1
42 |
43 | INPUT_FILENAME = ""
44 | CAIRO_BUILTIN_SCOPE = ScopedName.from_string("starkware.cairo.common.cairo_builtins")
45 |
46 |
47 | class Repl:
48 | def __init__(self):
49 | self.identifiers = IdentifierManager()
50 | self.struct_collector = StructCollector(identifiers=self.identifiers)
51 | builtins = ["output", "pedersen", "range_check", "ecdsa"]
52 | # TODO(lior, 01/10/2021): Get prime as an argument.
53 | self.preprocessor = Preprocessor(prime=PRIME, builtins=builtins, identifiers=self.identifiers)
54 | self.identifier_collector = IdentifierCollector()
55 | self.unique_label_creator = UniqueLabelCreator()
56 | # TODO(lior, 01/10/2021): Get path as an argument.
57 | path = ["src"]
58 | self.module_reader = get_module_reader(path)
59 |
60 | layout = "small"
61 | program = StrippedProgram(prime=PRIME, data=[], builtins=builtins, main=0)
62 | self.runner = CairoRunner(program=program, layout=layout, proof_mode=False)
63 | self.runner.initialize_segments()
64 | self.runner.initialize_main_entrypoint()
65 | self.runner.initialize_vm(hint_locals={})
66 |
67 | for offset, builtin in enumerate(builtins, -2 - len(builtins)):
68 | builtin_name = MAIN_SCOPE + f"{builtin}_ptr"
69 | self.preprocessor.add_future_definition(
70 | name=builtin_name,
71 | future_definition=FutureIdentifierDefinition(identifier_type=ReferenceDefinition),
72 | )
73 | if builtin == "output":
74 | self.preprocessor.add_simple_reference(
75 | name=builtin_name,
76 | reg=Register.FP,
77 | cairo_type=TypePointer(
78 | pointee=TypeFelt(),
79 | ),
80 | offset=offset,
81 | location=None,
82 | )
83 | else:
84 | self.preprocessor.add_simple_reference(
85 | name=builtin_name,
86 | reg=Register.FP,
87 | cairo_type=TypePointer(
88 | pointee=TypeStruct(
89 | scope=CAIRO_BUILTIN_SCOPE + "HashBuiltin",
90 | is_fully_resolved=True,
91 | ),
92 | ),
93 | offset=offset,
94 | location=None,
95 | )
96 | self.preprocessor.reference_states[builtin_name] = ReferenceState.ALLOW_IMPLICIT
97 |
98 | def parse(self, code: str) -> Union[CodeElement, Expression]:
99 | try:
100 | first_parse = parse(
101 | filename=INPUT_FILENAME,
102 | code=code,
103 | code_type="expr",
104 | expected_type=Expression,
105 | )
106 | if isinstance(first_parse, ExprFuncCall):
107 | return parse(
108 | filename=INPUT_FILENAME,
109 | code=code,
110 | code_type="code_element",
111 | expected_type=CodeElement,
112 | )
113 | else:
114 | return first_parse
115 | except ParserError:
116 | print("reached parse error")
117 | return parse(
118 | filename=INPUT_FILENAME,
119 | code=code,
120 | code_type="code_element",
121 | expected_type=CodeElement,
122 | )
123 | finally:
124 | print("reached here")
125 |
126 | def preprocess_parser(self, code: str):
127 |
128 | code = code.split('\n')
129 | # Comments will be ignored
130 | code = [line.split('#')[0] for line in code]
131 |
132 | # Supports extra new lines
133 | code = [line for line in code if line != '']
134 | return '\n'.join(code)
135 |
136 | def is_func_def(self, code:str):
137 | return code.strip().startswith('func ')
138 |
139 | # List of instructions that generate and end statement
140 | triggers_end = ['if']
141 |
142 | def func_def_ends_index(self, list_instructions):
143 | count = 0
144 | i = 0
145 | while i <= len(list_instructions):
146 | if list_instructions[i].strip().startswith('end'):
147 | if count == 0:
148 | return i
149 | else:
150 | count -= 1
151 | if list_instructions[i].strip().startswith(tuple(self.triggers_end)):
152 | count += 1
153 | i += 1
154 |
155 | def is_struct_def(self, code:str):
156 | return code.strip().startswith('struct ')
157 |
158 | def struct_def_ends_index(self, list_instructions):
159 | i = 0
160 | while i <= len(list_instructions):
161 | if list_instructions[i].strip().startswith('end'):
162 | return i
163 | i += 1
164 |
165 | def is_multiple_imports(self, code:str):
166 | return code.strip().endswith('import (')
167 |
168 | def multiple_imports_end_index(self, list_instructions):
169 | i = 0
170 | while i <= len(list_instructions):
171 | if ')' in list_instructions[i]:
172 | return i
173 | else:
174 | i += 1
175 |
176 | def get_instructions(self, code:str):
177 | list_instructions = code.split('\n')
178 | output_instructions = []
179 | i = 0
180 | while i < len(list_instructions):
181 | current = list_instructions[i]
182 |
183 | if not self.is_func_def(current) \
184 | and not self.is_multiple_imports(current)\
185 | and not self.is_struct_def(current):
186 | output_instructions.append(current)
187 | i += 1
188 | else:
189 | if self.is_func_def(current):
190 | end_index = self.func_def_ends_index(list_instructions[i:])
191 | elif self.is_struct_def(current):
192 | end_index = self.struct_def_ends_index(list_instructions[i:])
193 | else:
194 | end_index = self.multiple_imports_end_index(list_instructions[i:])
195 | list_instructions = [instruction.strip() for instruction in list_instructions]
196 | output_instructions.append('\n'.join(list_instructions[i: i + end_index + 1]))
197 | i += end_index + 1
198 |
199 | return output_instructions
200 |
201 | def run(self, code: str):
202 | code = self.preprocess_parser(code)
203 | list_instructions = self.get_instructions(code)
204 | for instruction in list_instructions:
205 | obj = self.parse(instruction)
206 |
207 | if isinstance(obj, CodeElement):
208 | self.exec(obj)
209 | elif isinstance(obj, Expression):
210 | value = self.eval(obj)
211 | return value
212 | else:
213 | raise NotImplementedError(f"Unsupported type: {type(obj).__name__}")
214 |
215 | def exec(self, code_element: CodeElement):
216 | # If the code element is an import statement or a function, we don't need to run
217 | # the corresponding generated instructions.
218 | skip_execution = isinstance(code_element, (CodeElementImport, CodeElementFunction))
219 |
220 | # Replace code_element with a scoped version.
221 | code_element = CodeElementScoped(scope=MAIN_SCOPE, code_elements=[code_element])
222 |
223 | # Find used modules (due to imports).
224 | direct_dependencies_collector = DirectDependenciesCollector()
225 | direct_dependencies_collector.visit(code_element)
226 |
227 | import_collector = ImportsCollector(self.module_reader.read)
228 | for pkg_name, location in direct_dependencies_collector.packages:
229 | import_collector.collect(pkg_name, location=location)
230 |
231 | # Create a list of code elements.
232 | elements: List[AstNode] = []
233 | for module_name, ast in import_collector.collected_data.items():
234 | scope = ScopedName.from_string(module_name)
235 | elements.append(CairoModule(cairo_file=ast, module_name=scope))
236 | elements.append(code_element)
237 |
238 | elements = list(map(self.unique_label_creator.visit, elements))
239 |
240 | # Collect identifiers and update the preprocessor with the new identifiers.
241 | for element in elements:
242 | self.identifier_collector.visit(element)
243 |
244 | new_identifiers = self.identifier_collector.identifiers
245 |
246 | # Filter out identifiers that were already defined.
247 | new_identifiers = new_identifiers.exclude(self.identifiers)
248 | self.preprocessor.update_identifiers(new_identifiers)
249 |
250 | # Store the flow_tracking data before calling the preprocessor.
251 | old_flow_tracking = self.preprocessor.flow_tracking.data
252 |
253 | for element in elements:
254 | self.struct_collector.visit(element)
255 |
256 | # Invoke the preprocessor.
257 | for element in elements:
258 | self.preprocessor.visit(element)
259 |
260 | self.preprocessor.resolve_labels()
261 |
262 | preprocessed_program = self.preprocessor.get_program()
263 | # TODO(lior, 01/10/2021): Consider assembling only the new instructions that were added.
264 | program = assemble(
265 | preprocessed_program=preprocessed_program, main_scope=MAIN_SCOPE, add_debug_info=True
266 | )
267 | for i, value in enumerate(program.data):
268 | self.runner.memory[self.runner.program_base + i] = value
269 | self.runner.vm.load_hints(program=program, program_base=self.runner.program_base)
270 |
271 | new_pc = len(program.data)
272 | try:
273 | if skip_execution:
274 | self.preprocessor.flow_tracking.data = old_flow_tracking
275 | else:
276 | self.runner.run_until_pc(self.runner.program_base + new_pc)
277 | finally:
278 | # Move pc to the end, even if there has been a runtime error.
279 | self.runner.vm.run_context.pc = self.runner.program_base + new_pc
280 |
281 | def eval(self, expr: Expression):
282 | with self.preprocessor.scoped(new_scope=MAIN_SCOPE, parent=None):
283 | expr = self.preprocessor.simplify_expr_as_felt(expr)
284 | return ExpressionEvaluator(
285 | prime=PRIME,
286 | ap=self.runner.vm.run_context.ap,
287 | fp=self.runner.vm.run_context.fp,
288 | # TODO(lior, 01/10/2021): Replace memory with a proxy that handles auto-deduction.
289 | memory=self.runner.vm.run_context.memory,
290 | identifiers=self.identifiers,
291 | ).eval(expr)
292 |
293 | def is_repl_line_complete(self, code: str) -> bool:
294 | """
295 | Returns True if the given code is complete and does not require additional code lines.
296 | For example: "1 + 2" is complete, while "func foo():" is not.
297 | """
298 | if len(code.strip()) == 0:
299 | return True
300 | try:
301 | self.parse(code=code)
302 | except LocationError as exc:
303 | if "Unexpected end-of-input" in str(exc):
304 | return False
305 | return True
306 |
--------------------------------------------------------------------------------