Skip to content

Commit

Permalink
feat: introduce the pyxide programming language (#97)
Browse files Browse the repository at this point in the history
* feat: introduce the pyxide programming language

* add pyxide language spec
  • Loading branch information
sansyrox authored Sep 26, 2023
1 parent 6de2764 commit ac9716a
Show file tree
Hide file tree
Showing 13 changed files with 137 additions and 49 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ async def handle_on_click(e):
}
</style>

<pyml>
<pyxide>
<store>
<parent hello='world'>
<span onclick={handle_on_click}>
{[ mocked_request() for i in range(4)]}
</span>
</parent>
</store>
</pyml>
</pyxide>


<script>
Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,15 @@ async def handle_on_click(e):
}
</style>

<pyml>
<pyxide>
<store>
<parent hello='world'>
<span onclick={handle_on_click}>
{[ mocked_request() for i in range(4)]}
</span>
</parent>
</store>
</pyml>
</pyxide>


<script>
Expand Down
88 changes: 88 additions & 0 deletions docs/pyxide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Pyxide Language Specification

Pyxide blends Python with a JSX-like syntax, offering seamless integration of both scripting and markup.

## Table of Contents
- Structure
- Python Functionality
- Styling
- Pyxide Markup
- JavaScript Integration
- Importing CSS and JS Libraries
- Structure

A Pyxide file contains:

1.Python imports, functions, and library integrations.
2. Styling via the <style> block or CSS imports.
3. Pyxide markup inside the <pyxide> block.
4. (Optional) JavaScript in the <script> block or JS library imports.

### Python Functionality

Declare Python functions and import modules as in regular Python.

```
from .parent import parent
from .store import store
def mocked_request():
return "fetched on the server"
async def handle_on_click(e):
# async code here
```

### Styling

Define styles with the <style> block, or import them from external files.

```
<style>
body {
background-color: red;
}
</style>
```

### CSS Imports:
```
import 'path/to/style.css'
```

### Components:

```
<store>
<parent hello='world'>
...
</parent>
</store>
```
### Event Handling and Looping

```
<span onclick={handle_on_click}>
{[ mocked_request() for i in range(4)]}
</span>
```

JavaScript Integration
Include custom JS in the <script> section.

```
<script>
// custom JS here
</script>
```

### Importing CSS and JS Libraries

Pyxide supports the importing of external CSS stylesheets and JS libraries directly from the Python code.

More documentation coming soon.


12 changes: 6 additions & 6 deletions documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,30 +48,30 @@ V
For each file, create a function named fx_<file_name>
|
V
create_component function transforms pyml, css, js, and client_side_python into a Component or a Node
create_component function transforms pyxide, css, js, and client_side_python into a Component or a Node
|
V
client_side_python transpiles into js
|
V
ComponentParser parses pyml, css, js, creating a Component or a Node, duplicating global and local variables
ComponentParser parses pyxide, css, js, creating a Component or a Node, duplicating global and local variables
|
V
ComponentParser parses pyml and constructs a tree out of html, returns a Component
ComponentParser parses pyxide and constructs a tree out of html, returns a Component
|
V
End
```

The build directory hosts all the Python files born out of the conversion process.

Each file features a function named `fx_<file_name>`, corresponding to the file's component name. The `fx_<file_name>` function invokes the `create_component` function (defined in `starfyre/__init__.py`). This function transforms the `pyml`, `css`, `js`, and `client_side_python` strings into a Component or a Node in our Tree.
Each file features a function named `fx_<file_name>`, corresponding to the file's component name. The `fx_<file_name>` function invokes the `create_component` function (defined in `starfyre/__init__.py`). This function transforms the `pyxide`, `css`, `js`, and `client_side_python` strings into a Component or a Node in our Tree.

Firstly, `client_side_python` undergoes transpilation into `js` (Refer to line 13 of `starfyre/__init__.py`).

Secondly, the `ComponentParser` steps in, parsing the `pyml`, `css`, `js` strings and transforming them into a Component or a Node in our Tree. Additionally, it also takes care of duplicating the global and local variables of the Python file, which is crucial for copying the imports and the variables associated with the component.
Secondly, the `ComponentParser` steps in, parsing the `pyxide`, `css`, `js` strings and transforming them into a Component or a Node in our Tree. Additionally, it also takes care of duplicating the global and local variables of the Python file, which is crucial for copying the imports and the variables associated with the component.

The `ComponentParser` further parses the `pyml`, constructs a tree out of the provided `html`, and returns a `Component` (`starfyre/component.py`), the root of the tree.
The `ComponentParser` further parses the `pyxide`, constructs a tree out of the provided `html`, and returns a `Component` (`starfyre/component.py`), the root of the tree.

## Insight into the Build Directory
```
Expand Down
12 changes: 6 additions & 6 deletions starfyre/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .transpiler import transpile


def create_component(pyml="", css="", js="", client_side_python="", component_name=""):
def create_component(pyxide="", css="", js="", client_side_python="", component_name=""):
if client_side_python:
new_js = transpile(client_side_python) + js
js = new_js
Expand All @@ -17,12 +17,12 @@ def create_component(pyml="", css="", js="", client_side_python="", component_na
global_variables = inspect.currentframe().f_back.f_back.f_globals.copy()

parser = ComponentParser(local_variables, global_variables, css, js, component_name)
pyml = pyml.strip("\n").strip()
parser.feed(pyml)
pyxide = pyxide.strip("\n").strip()
parser.feed(pyxide)
parser.close()
pyml_root = parser.get_root()
pyxide_root = parser.get_root()

if pyml_root is None:
if pyxide_root is None:
return Component(
tag="div",
props={},
Expand All @@ -34,7 +34,7 @@ def create_component(pyml="", css="", js="", client_side_python="", component_na
original_name="div",
)

return pyml_root
return pyxide_root


__all__ = [
Expand Down
32 changes: 16 additions & 16 deletions starfyre/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def remove_empty_lines_from_end(lines):
current_line_type = "python"
python_lines = []
css_lines = []
pyml_lines = []
pyxide_lines = []
js_lines = []
client_side_python = []

Expand Down Expand Up @@ -109,8 +109,8 @@ def remove_empty_lines_from_end(lines):
if line.startswith("<style"):
current_line_type = "css"
continue
elif line.startswith("<pyml"):
current_line_type = "pyml"
elif line.startswith("<pyxide"):
current_line_type = "pyxide"
continue
elif line.startswith("<script"):
current_line_type = "js"
Expand All @@ -126,7 +126,7 @@ def remove_empty_lines_from_end(lines):
continue
elif (
"</style>" in line
or "</pyml>" in line
or "</pyxide>" in line
or "</script>" in line
or "--" in line
):
Expand All @@ -137,8 +137,8 @@ def remove_empty_lines_from_end(lines):
python_lines.append(line)
elif current_line_type == "css":
css_lines.append(line)
elif current_line_type == "pyml":
pyml_lines.append(line)
elif current_line_type == "pyxide":
pyxide_lines.append(line)
elif current_line_type == "js":
js_lines.append(line)
elif current_line_type == "client":
Expand All @@ -147,17 +147,17 @@ def remove_empty_lines_from_end(lines):
return (
remove_empty_lines_from_end(python_lines),
remove_empty_lines_from_end(css_lines),
remove_empty_lines_from_end(pyml_lines),
remove_empty_lines_from_end(pyxide_lines),
remove_empty_lines_from_end(js_lines),
remove_empty_lines_from_end(client_side_python),
)


def python_transpiled_string(
pyml_lines, css_lines, js_lines, client_side_python, file_name
pyxide_lines, css_lines, js_lines, client_side_python, file_name
):
file_name = file_name.replace(".py", "").split("/")[-1]
pyml_lines = "".join(pyml_lines)
pyxide_lines = "".join(pyxide_lines)
css_lines = "".join(css_lines)
js_lines = "".join(js_lines)
client_side_python = "".join(client_side_python)
Expand All @@ -176,7 +176,7 @@ def python_transpiled_string(
def fx_{root_name}():
# not nesting the code to preserve the frames
component = create_component("""
{pyml_lines}
{pyxide_lines}
""", css="""
{css_lines}
""", js="""
Expand All @@ -196,7 +196,7 @@ def fx_{root_name}():
def fx_{root_name}():
component = create_component("""
{pyml_lines}
{pyxide_lines}
""", css="""
{css_lines}
""", js="""
Expand All @@ -216,7 +216,7 @@ def fx_{root_name}():
def transpile_to_python(
python_lines,
css_lines,
pyml_lines,
pyxide_lines,
js_lines,
client_side_python,
output_file_name,
Expand All @@ -226,13 +226,13 @@ def transpile_to_python(
Transpiles a fyre file into a python file.
This function is responsible for:
- parsing the fyre file into python, css, pyml, js and client side python
- parsing the fyre file into python, css, pyxide, js and client side python
"""
final_python_lines = ["".join(python_lines)]

main_content = python_transpiled_string(
pyml_lines, css_lines, js_lines, client_side_python, output_file_name
pyxide_lines, css_lines, js_lines, client_side_python, output_file_name
)

final_python_lines.append(main_content)
Expand Down Expand Up @@ -270,13 +270,13 @@ def compile(entry_file_name):

for fyre_file in fyre_files:
python_file_name = fyre_file.replace(".fyre", ".py")
python_lines, css_lines, pyml_lines, js_lines, client_side_python = parse(
python_lines, css_lines, pyxide_lines, js_lines, client_side_python = parse(
fyre_file_name=project_dir / fyre_file, project_dir=project_dir
)
transpile_to_python(
python_lines,
css_lines,
pyml_lines,
pyxide_lines,
js_lines,
client_side_python,
python_file_name,
Expand Down
10 changes: 5 additions & 5 deletions starfyre/js/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ observers = {}; // uuid , list[observers]

const domIdMap = {};

function addDomIdToMap(domId, pyml) {
domIdMap[domId] = pyml;
function addDomIdToMap(domId, pyxide) {
domIdMap[domId] = pyxide;
}

function getPymlFromDomId(domId) {
function getPyxideFromDomId(domId) {
return domIdMap[domId];
}

function render(domId) {
const element = document.getElementById(domId);
const pyml = getPymlFromDomId(domId);
element.innerHTML = `${eval(pyml)}`;
const pyxide = getPyxideFromDomId(domId);
element.innerHTML = `${eval(pyxide)}`;
}

function create_signal(initial_state) {
Expand Down
4 changes: 2 additions & 2 deletions starfyre/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def handle_starttag(self, tag, attrs):
# if the tag is not found in the generic tags and custom components
if tag not in self.generic_tags and tag not in self.components:
raise UnknownTagError(
f'Unknown tag: "{tag}". Please review line {self.lineno} in your "{self.component_name}" component in the pyml code.'
f'Unknown tag: "{tag}". Please review line {self.lineno} in your "{self.component_name}" component in the pyxide code.'
)

if self.root_node is None:
Expand Down Expand Up @@ -274,7 +274,7 @@ def handle_endtag(self, tag):
# if the tag is not found in the generic tags and custom components
if tag not in self.generic_tags and tag not in self.components:
raise UnknownTagError(
f'Unknown tag: "{tag}". Please review line {self.lineno} in your "{self.component_name}" component in the pyml code.'
f'Unknown tag: "{tag}". Please review line {self.lineno} in your "{self.component_name}" component in the pyxide code.'
)

endtag_node = self.stack.pop()
Expand Down
4 changes: 2 additions & 2 deletions test_application/components/parent.fyre
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def ssr_request():
else:
return "No response"

<pyml>
<pyxide>
<span>

<div>
Expand All @@ -36,4 +36,4 @@ def ssr_request():
</div>

</span>
</pyml>
</pyxide>
4 changes: 2 additions & 2 deletions test_application/pages/__init__.fyre
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ async def handle_on_click(e):
}
</style>

<pyml>
<pyxide>
<store>

<div>
Expand All @@ -41,7 +41,7 @@ async def handle_on_click(e):
</parent>
</div>
</store>
</pyml>
</pyxide>


<script>
Expand Down
Loading

0 comments on commit ac9716a

Please sign in to comment.