Skip to content

Commit 97806ca

Browse files
committed
v0.1.2: trait update
1 parent c2055f9 commit 97806ca

File tree

9 files changed

+369
-25
lines changed

9 files changed

+369
-25
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "hrun"
3-
version = "0.1.1"
3+
version = "0.1.2"
44
edition = "2021"
55

66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

README.md

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
![Contributors](https://badgen.net/github/contributors/AWeirdDev/hrun)
77
![Release](https://badgen.net/github/release/AWeirdDev/hrun)
88

9-
**`H`** is a simple runtime designed to be fast and memory-safe.
9+
**`H`** is a simple runtime designed to be fast and memory-safe. Available on PyPI with the name [`hrun-rt`](https://pypi.org/project/hrun-rt) ("rt" - runtime).
1010

1111
You may find it useful for:
1212
- Writing simple scripts
1313
- Learning AST
1414
- Running unsafe code (e.g., from AI models)
1515

16+
> **New!** — Now with functions, thanks to the `HFunction` trait update.
17+
1618
First, create a new H runtime.
1719

1820
```python
@@ -80,3 +82,81 @@ h.run(code)
8082
print(h.get("c"))
8183
# Console output: Yes!
8284
```
85+
86+
***
87+
88+
# Documentation
89+
The following components are already **available** as docstrings in Python:
90+
91+
- `Expr`
92+
- `Statement`
93+
- `H` (The runtime)
94+
95+
## <kbd>type</kbd> `Value`
96+
```python
97+
type Value = str | int | float | bool | None | list
98+
```
99+
A value. (Discoverable as `PyValue` in `src/lib.rs`, `Value` in `crates/h/src/lib.rs`)
100+
101+
It is recommended to cast a value to another if you're certain about a type at runtime.
102+
103+
## <kbd>type</kbd> `Identifier`
104+
```python
105+
type Identifier = str | int
106+
```
107+
An identifier. Could be a string or an integer; both have advantages & disadvantages.
108+
109+
## ✨ Declaring functions
110+
Functions are available since `v0.1.2`.
111+
112+
First, create a Python function:
113+
114+
```python
115+
# We need cast() from typing because we're *certain*
116+
# what the type would be
117+
from typing import cast
118+
119+
from hrun import Value
120+
121+
def add(items: Value) -> Value:
122+
# Since the H runtime side would be calling
123+
# this like add([A, B]), items: list[int]
124+
items = cast(list[int], items)
125+
a = items[0]
126+
b = items[1]
127+
128+
return a + b
129+
```
130+
131+
Then, define a simple code structure:
132+
133+
```python
134+
from hrun import Statement, Expr
135+
136+
code = [
137+
Statement.fn("add", add), # Name this function as "add"
138+
Statement.let("a", Expr.literal(1)),
139+
Statement.let("b", Expr.literal(2)),
140+
Statement.let(
141+
"result",
142+
Expr.call(
143+
"add",
144+
Expr.vector([
145+
Expr.ident("a"),
146+
Expr.ident("b")
147+
])
148+
)
149+
)
150+
]
151+
```
152+
153+
Finally, run it with the `H` runtime:
154+
155+
```python
156+
from hrun import H
157+
158+
h = H()
159+
h.run(code)
160+
161+
print(h.get("result")) # Output: 3
162+
```

crates/h/src/lib.rs

Lines changed: 127 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,77 @@
1-
use std::sync::Arc;
1+
use std::{ fmt::Debug, sync::Arc };
22

33
use dashmap::DashMap;
44

5-
#[derive(Debug, Clone, PartialEq)]
5+
pub trait HFunction: HFunctionClone + Debug + Send + Sync {
6+
fn run(&self, args: Arc<Value>) -> Value;
7+
}
8+
9+
pub trait HFunctionClone {
10+
fn clone_box(&self) -> Box<dyn HFunction>;
11+
}
12+
13+
impl<T> HFunctionClone for T where T: 'static + HFunction + Clone {
14+
fn clone_box(&self) -> Box<dyn HFunction> {
15+
Box::new(self.clone())
16+
}
17+
}
18+
19+
impl Clone for Box<dyn HFunction> {
20+
fn clone(&self) -> Self {
21+
self.clone_box()
22+
}
23+
}
24+
25+
pub type BoxedHFunction = Box<dyn HFunction + 'static>;
26+
27+
#[derive(Debug, Clone)]
628
pub enum Value {
729
Null,
830
Boolean(bool),
931
String(String),
1032
Number(Number),
33+
Vector(Vec<Arc<Value>>),
34+
Function(BoxedHFunction),
35+
}
36+
37+
impl PartialEq for Value {
38+
fn eq(&self, other: &Self) -> bool {
39+
match self {
40+
Self::Function(_) => panic!("Cannot perform PartialEq for Value::Fn"),
41+
Self::Boolean(b) =>
42+
b.eq(
43+
other
44+
.as_bool()
45+
.unwrap_or_else(|| panic!("Expected other item to be Value::Boolean"))
46+
),
47+
Self::Null => other.is_null(),
48+
Self::Number(n) =>
49+
n.eq(
50+
other
51+
.as_number()
52+
.unwrap_or_else(|| panic!("Expected other item to be Value::Number"))
53+
),
54+
Self::String(s) =>
55+
s.eq(
56+
other
57+
.as_string()
58+
.unwrap_or_else(|| panic!("Expected other item to be Value::String"))
59+
),
60+
Self::Vector(v) =>
61+
v.eq(
62+
other
63+
.as_vector()
64+
.unwrap_or_else(|| panic!("Expected other item to be Value::Vector"))
65+
),
66+
}
67+
}
1168
}
1269

1370
impl Value {
71+
pub fn is_null(&self) -> bool {
72+
matches!(self, Self::Null)
73+
}
74+
1475
pub fn is_number(&self) -> bool {
1576
matches!(self, Self::Number(_))
1677
}
@@ -44,6 +105,28 @@ impl Value {
44105
}
45106
}
46107

108+
pub fn is_function(&self) -> bool {
109+
matches!(self, Self::Function(_))
110+
}
111+
112+
pub fn as_function(&self) -> Option<&BoxedHFunction> {
113+
match self {
114+
Self::Function(f) => Some(f),
115+
_ => None,
116+
}
117+
}
118+
119+
pub fn is_vector(&self) -> bool {
120+
matches!(self, Self::Vector(_))
121+
}
122+
123+
pub fn as_vector(&self) -> Option<&Vec<Arc<Self>>> {
124+
match self {
125+
Self::Vector(v) => Some(v),
126+
_ => None,
127+
}
128+
}
129+
47130
pub const fn null() -> Self {
48131
Self::Null
49132
}
@@ -128,6 +211,8 @@ pub enum Expr {
128211
Not(Box<Expr>),
129212
GreaterThan(Box<Expr>, Box<Expr>),
130213
LessThan(Box<Expr>, Box<Expr>),
214+
Call(Identifier, Box<Expr>),
215+
Vector(Vec<Expr>),
131216
}
132217

133218
impl Expr {
@@ -152,6 +237,7 @@ impl Expr {
152237
}
153238

154239
/// Creates a not expr
240+
#[allow(clippy::should_implement_trait)]
155241
pub fn not(item: Self) -> Self {
156242
Self::Not(Box::new(item))
157243
}
@@ -165,6 +251,16 @@ impl Expr {
165251
pub fn less_than(a: Self, b: Self) -> Self {
166252
Self::LessThan(Box::new(a), Box::new(b))
167253
}
254+
255+
/// Calls a function
256+
pub fn call(ident: Identifier, args: Self) -> Self {
257+
Self::Call(ident, Box::new(args))
258+
}
259+
260+
/// Creates a vector (i.e. `[expr_1, expr_2, expr_3, ...]`)
261+
pub fn vector(items: Vec<Self>) -> Self {
262+
Self::Vector(items)
263+
}
168264
}
169265

170266
#[derive(Debug, Clone)]
@@ -212,13 +308,20 @@ pub enum Statement {
212308
then: Vec<Statement>,
213309
otherwise: Vec<Statement>, // are you expecting "else"?? nahhh
214310
},
311+
Fn(Identifier, BoxedHFunction),
215312
}
216313

217314
#[derive(Debug)]
218315
pub struct Machine {
219316
pub vars: DashMap<Identifier, Arc<Value>>,
220317
}
221318

319+
impl Default for Machine {
320+
fn default() -> Self {
321+
Self::new()
322+
}
323+
}
324+
222325
impl Machine {
223326
pub fn new() -> Self {
224327
Self {
@@ -231,7 +334,10 @@ impl Machine {
231334
}
232335

233336
pub fn get(&self, ident: &Identifier) -> Arc<Value> {
234-
self.vars.get(ident).expect(&format!("Value cannot be found: {:?}", ident)).clone()
337+
self.vars
338+
.get(ident)
339+
.unwrap_or_else(|| panic!("Value cannot be found: {:?}", ident))
340+
.clone()
235341
}
236342
}
237343

@@ -252,6 +358,9 @@ pub fn deduce(machine: &Machine, statements: Vec<Statement>) {
252358
deduce(machine, otherwise);
253359
}
254360
}
361+
Statement::Fn(ident, function) => {
362+
machine.set(ident, Arc::new(Value::Function(function)));
363+
}
255364
}
256365
}
257366
}
@@ -327,6 +436,21 @@ pub fn deduce_expr(machine: &Machine, expr: Expr) -> Arc<Value> {
327436
_ => Arc::new(Value::boolean(false)),
328437
}
329438
}
439+
Expr::Call(ident, args) => {
440+
let va = deduce_expr(machine, *args);
441+
let function = machine.get(&ident);
442+
let function = function.as_function().unwrap();
443+
444+
let result = function.run(va);
445+
Arc::new(result)
446+
}
447+
Expr::Vector(mut v) => {
448+
let items = v
449+
.drain(..)
450+
.map(|item| deduce_expr(machine, item))
451+
.collect::<Vec<_>>();
452+
Arc::new(Value::Vector(items))
453+
}
330454
}
331455
}
332456

python/hrun/core.py

Whitespace-only changes.

python/hrun/hrun.pyi

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List, Literal
1+
from typing import Callable, List, Literal
22
from .types import Identifier, Value
33

44

@@ -98,6 +98,26 @@ class Expr:
9898
b (Expr): An expression. Must be a number.
9999
"""
100100

101+
@staticmethod
102+
def call(ident: Identifier, args: Expr) -> "Expr":
103+
"""Creates an expression that calls a function.
104+
105+
Args:
106+
ident (Identifier): The identifier.
107+
args (Expr): Arguments (parameters).
108+
"""
109+
110+
@staticmethod
111+
def vector(items: List[Expr]) -> "Expr":
112+
"""Creates a vector.
113+
114+
In other words, this is like a bracket which creates a list of expressions,
115+
like so: `[expr_1, expr_2, expr_3, ...]`
116+
117+
Args:
118+
items (list[Expr]): Expression items.
119+
"""
120+
101121

102122
class Statement:
103123
"""Represents a statement."""
@@ -125,3 +145,6 @@ class Statement:
125145
then (list[Statement]): The `if [...] then...` branch.
126146
otheriwse (list[Statement]): The `...else` branch.
127147
"""
148+
149+
@staticmethod
150+
def fn(ident: Identifier, item: Callable[[Value], Value]): ...

python/hrun/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from typing import Union
22

33
Identifier = Union[str, int]
4-
Value = Union[str, int, float, bool, None]
4+
Value = Union[str, int, float, bool, None, list]

0 commit comments

Comments
 (0)