Skip to content

Commit

Permalink
interactive: App (#1759)
Browse files Browse the repository at this point in the history
Using Textual Python, I have created a GUI/app.This app allows you to
write/paste3 xDSL IR into the "Input" TextArea, which is mirrored in the
"Output" TextArea.

This app is still under construction, this is the first step towards
building the full tool.

---------

Co-authored-by: Sasha Lopoukhine <[email protected]>
  • Loading branch information
dshaaban01 and superlopuh authored Nov 8, 2023
1 parent 704707a commit 2e6656a
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 1 deletion.
7 changes: 7 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,12 @@
"${file}"
]
},
{
"name": "interactive",
"type": "python",
"request": "launch",
"module": "xdsl.interactive.app",
"justMyCode": false,
},
]
}
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ dependencies = [
dynamic = ["version"]

[project.optional-dependencies]
extras = ["riscemu==2.2.5", "wgpu==0.11.0"]
extras = ["riscemu==2.2.5", "wgpu==0.11.0", "textual==0.40.0", "pyclip==0.7"]

[project.urls]
Homepage = "https://xdsl.dev/"
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ ruff==0.1.4
asv<0.7
isort==5.12.0
nbconvert>=7.7.2,<8.0.0
textual-dev==1.2.1
pytest-asyncio==0.21.1
# pyright version has to be fixed with `==`. The CI parses this file
# and installs the according version for typechecking.
pyright==1.1.334
Expand Down
68 changes: 68 additions & 0 deletions tests/interactive/test_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import pytest

from xdsl.builder import ImplicitBuilder
from xdsl.dialects import arith, func
from xdsl.dialects.builtin import IndexType, IntegerAttr, ModuleOp
from xdsl.interactive.app import InputApp
from xdsl.ir import Block, Region
from xdsl.utils.exceptions import ParseError


@pytest.mark.asyncio()
async def test_input():
"""Test pressing keys has the desired result."""
app = InputApp()
async with app.run_test() as pilot: # pyright: ignore
# Test no input
assert app.output_text_area.text == "No input"
assert app.current_module is None

# Test inccorect input
app.input_text_area.insert("dkjfd")
await pilot.pause()
assert (
app.output_text_area.text
== "(Span[5:6](text=''), 'Operation builtin.unregistered does not have a custom format.')"
)
assert isinstance(app.current_module, ParseError)
assert (
str(app.current_module)
== "(Span[5:6](text=''), 'Operation builtin.unregistered does not have a custom format.')"
)

# Test corect input
app.input_text_area.clear()
app.input_text_area.insert(
"""
func.func @hello(%n : index) -> index {
%two = arith.constant 2 : index
%res = arith.muli %n, %two : index
func.return %res : index
}
"""
)
await pilot.pause()
assert (
app.output_text_area.text
== """builtin.module {
func.func @hello(%n : index) -> index {
%two = arith.constant 2 : index
%res = arith.muli %n, %two : index
func.return %res : index
}
}
"""
)

index = IndexType()

expected_module = ModuleOp(Region([Block()]))
with ImplicitBuilder(expected_module.body):
function = func.FuncOp("hello", ((index,), (index,)))
with ImplicitBuilder(function.body) as (n,):
two = arith.Constant(IntegerAttr(2, index)).result
res = arith.Muli(n, two)
func.Return(res)

assert isinstance(app.current_module, ModuleOp)
assert app.current_module.is_structurally_equivalent(expected_module)
Empty file added xdsl/interactive/__init__.py
Empty file.
139 changes: 139 additions & 0 deletions xdsl/interactive/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"""
An interactive command-line tool to explore compilation pipeline construction.
Execute `xdsl-gui` in your terminal to run it.
Run `terminal -m xdsl.interactive.app:InputApp --def` to run in development mode. Please be sure to install `textual-dev` to run this command.
"""

from io import StringIO

from rich.style import Style
from textual import events, on
from textual.app import App, ComposeResult
from textual.containers import Horizontal, Vertical
from textual.reactive import reactive
from textual.widgets import Footer, TextArea
from textual.widgets.text_area import TextAreaTheme

from xdsl.dialects.builtin import ModuleOp
from xdsl.ir import MLContext
from xdsl.parser import Parser
from xdsl.printer import Printer
from xdsl.tools.command_line_tool import get_all_dialects


class OutputTextArea(TextArea):
"""Used to prevent users from being able to change/alter the Output TextArea"""

async def _on_key(self, event: events.Key) -> None:
event.prevent_default()


class InputApp(App[None]):
"""
Interactive application for constructing compiler pipelines.
"""

CSS_PATH = "app.tcss"

BINDINGS = [
("d", "toggle_dark", "Toggle dark mode"),
("q", "quit_app", "Quit"),
]

# defines a theme for the Input/Output TextArea's
_DEFAULT_THEME = TextAreaTheme(
name="my_theme_design",
base_style=Style(bgcolor="white"),
syntax_styles={
"string": Style(color="red"),
"comment": Style(color="magenta"),
},
)

current_module: reactive[ModuleOp | Exception | None] = reactive(None)
"""
Reactive variable used to save the current state of the modified Input TextArea (i.e. is the Output TextArea)
"""

input_text_area = TextArea(id="input")
output_text_area = OutputTextArea(id="output")

def compose(self) -> ComposeResult:
"""
Creates the required widgets, events, etc.
Get the list of xDSL passes, add them to an array in "Selection" format (so it can be added to a Selection List)
and sort the list in alphabetical order.
"""

with Horizontal(id="input_output"):
with Vertical(id="input_container"):
yield self.input_text_area
with Vertical(id="output_container"):
yield self.output_text_area
yield Footer()

@on(TextArea.Changed, "#input")
def update_current_module(self) -> None:
"""
Function called when the Input TextArea is cahnged. This function parses the Input IR and updates
the current_module reactive variable.
"""
input_text = self.input_text_area.text
try:
ctx = MLContext(True)
for dialect in get_all_dialects():
ctx.load_dialect(dialect)
parser = Parser(ctx, input_text)
module = parser.parse_module()
self.current_module = module
except Exception as e:
self.current_module = e

def watch_current_module(self):
"""
Function called when the current_module reactive variable is updated. This function updates
the Output TextArea.
"""
match self.current_module:
case None:
output_text = "No input"
case Exception() as e:
output_stream = StringIO()
Printer(output_stream).print(e)
output_text = output_stream.getvalue()
case ModuleOp():
output_stream = StringIO()
Printer(output_stream).print(self.current_module)
output_text = output_stream.getvalue()

self.output_text_area.load_text(output_text)

def on_mount(self) -> None:
"""Configure widgets in this application before it is first shown."""

# register's the theme for the Input/Output TextArea's
self.input_text_area.register_theme(InputApp._DEFAULT_THEME)
self.output_text_area.register_theme(InputApp._DEFAULT_THEME)
self.input_text_area.theme = "my_theme_design"
self.output_text_area.theme = "my_theme_design"

self.query_one("#input_container").border_title = "Input xDSL IR"
self.query_one("#output_container").border_title = "Output xDSL IR"

def action_toggle_dark(self) -> None:
"""An action to toggle dark mode."""
self.dark = not self.dark

def action_quit_app(self) -> None:
"""An action to quit the app."""
self.exit()


def main():
return InputApp().run()


if __name__ == "__main__":
main()
16 changes: 16 additions & 0 deletions xdsl/interactive/app.tcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

# Vertical(TextArea)
#input_container {
margin: 1;
border: heavy $warning-lighten-1;
border-title-color: $primary;
border-title-align: center;
}

# Vertical(OutputTextArea)
#output_container {
margin: 1;
border: heavy $warning-lighten-1;
border-title-color: $primary;
border-title-align: center;
}

0 comments on commit 2e6656a

Please sign in to comment.