therl 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- therl-0.1.0/PKG-INFO +148 -0
- therl-0.1.0/README.md +137 -0
- therl-0.1.0/pyproject.toml +21 -0
- therl-0.1.0/setup.cfg +4 -0
- therl-0.1.0/src/therl/__init__.py +0 -0
- therl-0.1.0/src/therl/__main__.py +4 -0
- therl-0.1.0/src/therl/consts.py +13 -0
- therl-0.1.0/src/therl/functions.py +40 -0
- therl-0.1.0/src/therl/instructions.py +205 -0
- therl-0.1.0/src/therl/main.py +81 -0
- therl-0.1.0/src/therl/runtime.py +4 -0
- therl-0.1.0/src/therl/types.py +3 -0
- therl-0.1.0/src/therl/utils.py +83 -0
- therl-0.1.0/src/therl/variable.py +24 -0
- therl-0.1.0/src/therl.egg-info/PKG-INFO +148 -0
- therl-0.1.0/src/therl.egg-info/SOURCES.txt +17 -0
- therl-0.1.0/src/therl.egg-info/dependency_links.txt +1 -0
- therl-0.1.0/src/therl.egg-info/entry_points.txt +2 -0
- therl-0.1.0/src/therl.egg-info/top_level.txt +1 -0
therl-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: therl
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A human readable programming language
|
|
5
|
+
Author-email: CyzmiX <itzmedigamingx@gmail.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.13
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Therl
|
|
13
|
+
|
|
14
|
+
> **T**ruly **H**uman **R**eadaby **L**anguage
|
|
15
|
+
> _(Yes, the acronym is a little cursed. I know.)_
|
|
16
|
+
|
|
17
|
+
Therl is a human-readable, instruction-based programming language written entirely in pure Python. Its goal is to make code read almost like plain English while remaining predictable and easy to parse.
|
|
18
|
+
|
|
19
|
+
```therl
|
|
20
|
+
set greeting to "hello"
|
|
21
|
+
|
|
22
|
+
func greet <name>
|
|
23
|
+
|
|
24
|
+
say greeting + " " + name + "!"
|
|
25
|
+
|
|
26
|
+
eof
|
|
27
|
+
|
|
28
|
+
func main
|
|
29
|
+
|
|
30
|
+
run greet with <name> as "Jeff"
|
|
31
|
+
|
|
32
|
+
set numbers to [1..10]
|
|
33
|
+
|
|
34
|
+
say numbers
|
|
35
|
+
|
|
36
|
+
eof
|
|
37
|
+
|
|
38
|
+
run main
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Output:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
hello Jeff!
|
|
45
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Features
|
|
51
|
+
|
|
52
|
+
- 📖 Human-readable syntax
|
|
53
|
+
- 📝 Instruction-based language design
|
|
54
|
+
- 🐍 Written entirely in pure Python
|
|
55
|
+
- 🔄 Strictly dynamically typed variables
|
|
56
|
+
- 📦 Built-in lists
|
|
57
|
+
- 🔢 Integer, float, string and boolean types
|
|
58
|
+
- 🧩 Functions with named parameters
|
|
59
|
+
- 📢 Simple `say` statement for output
|
|
60
|
+
- ✏️ Mutable list elements
|
|
61
|
+
- 🚀 Easy to extend
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Example
|
|
66
|
+
|
|
67
|
+
```therl
|
|
68
|
+
set greeting to "hello"
|
|
69
|
+
|
|
70
|
+
func greet <name>
|
|
71
|
+
|
|
72
|
+
say greeting + " " + name + "!"
|
|
73
|
+
|
|
74
|
+
eof
|
|
75
|
+
|
|
76
|
+
func main
|
|
77
|
+
|
|
78
|
+
say "Welcome!"
|
|
79
|
+
|
|
80
|
+
run greet with <name> as "Jeff"
|
|
81
|
+
|
|
82
|
+
set ids to [1..10]
|
|
83
|
+
|
|
84
|
+
say ids
|
|
85
|
+
|
|
86
|
+
set ids at 0 to 1000
|
|
87
|
+
|
|
88
|
+
say ids
|
|
89
|
+
|
|
90
|
+
add 10 to ids
|
|
91
|
+
|
|
92
|
+
say ids
|
|
93
|
+
|
|
94
|
+
eof
|
|
95
|
+
|
|
96
|
+
run main
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Output:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
Welcome!
|
|
103
|
+
hello Jeff!
|
|
104
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
105
|
+
[1000, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
106
|
+
[1000, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
# Project Status
|
|
112
|
+
|
|
113
|
+
⚠️ Therl is currently in active development.
|
|
114
|
+
|
|
115
|
+
Expect missing features, breaking changes, and plenty of experimentation as the language evolves.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
# Roadmap
|
|
120
|
+
|
|
121
|
+
- [ ] Arithmetic expressions
|
|
122
|
+
- [ ] Conditional statements
|
|
123
|
+
- [ ] Loops
|
|
124
|
+
- [ ] Dictionaries / Maps
|
|
125
|
+
- [ ] Modules
|
|
126
|
+
- [ ] Classes
|
|
127
|
+
- [ ] Error reporting with line numbers
|
|
128
|
+
- [ ] Standard library
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
# Why?
|
|
133
|
+
|
|
134
|
+
Because making programming languages is fun.
|
|
135
|
+
|
|
136
|
+
And because writing
|
|
137
|
+
|
|
138
|
+
```therl
|
|
139
|
+
set score to 100
|
|
140
|
+
|
|
141
|
+
add 10 to score
|
|
142
|
+
|
|
143
|
+
say score
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
just feels oddly satisfying.
|
|
147
|
+
|
|
148
|
+
---
|
therl-0.1.0/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Therl
|
|
2
|
+
|
|
3
|
+
> **T**ruly **H**uman **R**eadaby **L**anguage
|
|
4
|
+
> _(Yes, the acronym is a little cursed. I know.)_
|
|
5
|
+
|
|
6
|
+
Therl is a human-readable, instruction-based programming language written entirely in pure Python. Its goal is to make code read almost like plain English while remaining predictable and easy to parse.
|
|
7
|
+
|
|
8
|
+
```therl
|
|
9
|
+
set greeting to "hello"
|
|
10
|
+
|
|
11
|
+
func greet <name>
|
|
12
|
+
|
|
13
|
+
say greeting + " " + name + "!"
|
|
14
|
+
|
|
15
|
+
eof
|
|
16
|
+
|
|
17
|
+
func main
|
|
18
|
+
|
|
19
|
+
run greet with <name> as "Jeff"
|
|
20
|
+
|
|
21
|
+
set numbers to [1..10]
|
|
22
|
+
|
|
23
|
+
say numbers
|
|
24
|
+
|
|
25
|
+
eof
|
|
26
|
+
|
|
27
|
+
run main
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Output:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
hello Jeff!
|
|
34
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
- 📖 Human-readable syntax
|
|
42
|
+
- 📝 Instruction-based language design
|
|
43
|
+
- 🐍 Written entirely in pure Python
|
|
44
|
+
- 🔄 Strictly dynamically typed variables
|
|
45
|
+
- 📦 Built-in lists
|
|
46
|
+
- 🔢 Integer, float, string and boolean types
|
|
47
|
+
- 🧩 Functions with named parameters
|
|
48
|
+
- 📢 Simple `say` statement for output
|
|
49
|
+
- ✏️ Mutable list elements
|
|
50
|
+
- 🚀 Easy to extend
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Example
|
|
55
|
+
|
|
56
|
+
```therl
|
|
57
|
+
set greeting to "hello"
|
|
58
|
+
|
|
59
|
+
func greet <name>
|
|
60
|
+
|
|
61
|
+
say greeting + " " + name + "!"
|
|
62
|
+
|
|
63
|
+
eof
|
|
64
|
+
|
|
65
|
+
func main
|
|
66
|
+
|
|
67
|
+
say "Welcome!"
|
|
68
|
+
|
|
69
|
+
run greet with <name> as "Jeff"
|
|
70
|
+
|
|
71
|
+
set ids to [1..10]
|
|
72
|
+
|
|
73
|
+
say ids
|
|
74
|
+
|
|
75
|
+
set ids at 0 to 1000
|
|
76
|
+
|
|
77
|
+
say ids
|
|
78
|
+
|
|
79
|
+
add 10 to ids
|
|
80
|
+
|
|
81
|
+
say ids
|
|
82
|
+
|
|
83
|
+
eof
|
|
84
|
+
|
|
85
|
+
run main
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Output:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
Welcome!
|
|
92
|
+
hello Jeff!
|
|
93
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
94
|
+
[1000, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
95
|
+
[1000, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
# Project Status
|
|
101
|
+
|
|
102
|
+
⚠️ Therl is currently in active development.
|
|
103
|
+
|
|
104
|
+
Expect missing features, breaking changes, and plenty of experimentation as the language evolves.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
# Roadmap
|
|
109
|
+
|
|
110
|
+
- [ ] Arithmetic expressions
|
|
111
|
+
- [ ] Conditional statements
|
|
112
|
+
- [ ] Loops
|
|
113
|
+
- [ ] Dictionaries / Maps
|
|
114
|
+
- [ ] Modules
|
|
115
|
+
- [ ] Classes
|
|
116
|
+
- [ ] Error reporting with line numbers
|
|
117
|
+
- [ ] Standard library
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
# Why?
|
|
122
|
+
|
|
123
|
+
Because making programming languages is fun.
|
|
124
|
+
|
|
125
|
+
And because writing
|
|
126
|
+
|
|
127
|
+
```therl
|
|
128
|
+
set score to 100
|
|
129
|
+
|
|
130
|
+
add 10 to score
|
|
131
|
+
|
|
132
|
+
say score
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
just feels oddly satisfying.
|
|
136
|
+
|
|
137
|
+
---
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "therl"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A human readable programming language"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name="CyzmiX", email="itzmedigamingx@gmail.com" },
|
|
11
|
+
]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.13"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
run-my-tool = "therl.app:main"
|
therl-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from .instructions import ADD, CAST, RUN, SAY, SET
|
|
4
|
+
|
|
5
|
+
OPERATORS = ["+", "-", "/", "*", "**"]
|
|
6
|
+
INSTRUCTIONS_KEYWORDS = sorted(
|
|
7
|
+
["add", "set", "say", "cast", "func", "eof", "run"],
|
|
8
|
+
key=len,
|
|
9
|
+
reverse=True,
|
|
10
|
+
)
|
|
11
|
+
INSTRUCTION_TO_FUNC = {"say": SAY, "set": SET, "cast": CAST, "add": ADD, "run": RUN}
|
|
12
|
+
pattern = f"({'|'.join(map(re.escape, INSTRUCTIONS_KEYWORDS))})"
|
|
13
|
+
params_pattern = f"({'|'.join(map(re.escape, ['<', '>']))})"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from .consts import INSTRUCTION_TO_FUNC
|
|
4
|
+
from .types import VARIABLES_TYPE
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Function:
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
name: str,
|
|
11
|
+
params: list[str],
|
|
12
|
+
instructions: list[str] | None = None,
|
|
13
|
+
) -> None:
|
|
14
|
+
self.instructions = instructions
|
|
15
|
+
self.name = name
|
|
16
|
+
self.params = params
|
|
17
|
+
|
|
18
|
+
def __name__(self):
|
|
19
|
+
return "function"
|
|
20
|
+
|
|
21
|
+
def run(self, params: VARIABLES_TYPE | None = None):
|
|
22
|
+
import therl.runtime
|
|
23
|
+
|
|
24
|
+
if self.instructions is None:
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
OLD_VARIABLES = therl.runtime.VARIABLES
|
|
28
|
+
|
|
29
|
+
if params:
|
|
30
|
+
therl.runtime.VARIABLES = therl.runtime.VARIABLES | params
|
|
31
|
+
|
|
32
|
+
for instruction in self.instructions:
|
|
33
|
+
action = instruction[0]
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
INSTRUCTION_TO_FUNC[action](instruction[1])
|
|
37
|
+
therl.runtime.VARIABLES = OLD_VARIABLES
|
|
38
|
+
except (KeyError, IndexError):
|
|
39
|
+
print("invalid instruction:", str(instruction))
|
|
40
|
+
sys.exit(1)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from .types import VARIABLES_TYPE
|
|
5
|
+
from .utils import _decode_value
|
|
6
|
+
from .variable import Variable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def SET(string: str):
|
|
10
|
+
from .runtime import VARIABLES
|
|
11
|
+
|
|
12
|
+
check_slices = string.split(" ")
|
|
13
|
+
|
|
14
|
+
if check_slices[1] == "to":
|
|
15
|
+
slices = string.split(" ", maxsplit=2)
|
|
16
|
+
|
|
17
|
+
name = slices[0]
|
|
18
|
+
value = slices[2]
|
|
19
|
+
alr_exits = VARIABLES.get(name)
|
|
20
|
+
|
|
21
|
+
if alr_exits is not None:
|
|
22
|
+
alr_exits.set(new_value=_decode_value(value))
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
VARIABLES[name] = Variable(name=name, value=_decode_value(value))
|
|
26
|
+
|
|
27
|
+
elif check_slices[1] == "at":
|
|
28
|
+
slices = string.split(" ", maxsplit=2)
|
|
29
|
+
|
|
30
|
+
name = slices[0]
|
|
31
|
+
alr_exits = VARIABLES.get(name)
|
|
32
|
+
if alr_exits is None:
|
|
33
|
+
print(f"Cannot modify unexisisting array named {name}")
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
|
|
36
|
+
if alr_exits.type != list and alr_exits.type != str:
|
|
37
|
+
print(
|
|
38
|
+
f"Cannot perform index based modification on {alr_exits.type.__name__}, must list or str"
|
|
39
|
+
)
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
|
|
42
|
+
index_and_value = [i.strip() for i in slices[2].split("to")]
|
|
43
|
+
try:
|
|
44
|
+
index = index_and_value[0]
|
|
45
|
+
if VARIABLES.get(index) is not None:
|
|
46
|
+
if VARIABLES.get(index).type != int:
|
|
47
|
+
print(
|
|
48
|
+
f"Invalid index type, expected int found {VARIABLES.get(index).type.__name__}"
|
|
49
|
+
)
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
index = VARIABLES.get(index).value
|
|
52
|
+
else:
|
|
53
|
+
index = int(index)
|
|
54
|
+
except ValueError:
|
|
55
|
+
print("Invalid index type, expected int found str")
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
value = index_and_value[1]
|
|
58
|
+
try:
|
|
59
|
+
VARIABLES[name].value[index] = (
|
|
60
|
+
_decode_value(value) if alr_exits.type == list else str(value)
|
|
61
|
+
)
|
|
62
|
+
except IndexError:
|
|
63
|
+
print("Index out of range!")
|
|
64
|
+
sys.exit(1)
|
|
65
|
+
|
|
66
|
+
else:
|
|
67
|
+
print("invalid syntax expected 'to' in variable assignment")
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def SAY(string: str):
|
|
72
|
+
from .runtime import VARIABLES
|
|
73
|
+
|
|
74
|
+
alr_exits = VARIABLES.get(string.strip())
|
|
75
|
+
|
|
76
|
+
if "at" in string:
|
|
77
|
+
var_and_index = [s.strip() for s in string.split("at")]
|
|
78
|
+
name = var_and_index[0]
|
|
79
|
+
|
|
80
|
+
if VARIABLES.get(name) is None:
|
|
81
|
+
print(f"Variable with name {name} doesnt exist")
|
|
82
|
+
sys.exit(1)
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
index = var_and_index[1]
|
|
86
|
+
if VARIABLES.get(index) is not None:
|
|
87
|
+
if VARIABLES.get(index).type != int:
|
|
88
|
+
print(
|
|
89
|
+
f"Invalid index type, expected int found {VARIABLES.get(index).type.__name__}"
|
|
90
|
+
)
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
index = VARIABLES.get(index).value
|
|
93
|
+
else:
|
|
94
|
+
index = int(index)
|
|
95
|
+
except ValueError:
|
|
96
|
+
print("Invalid index type, expected int found str")
|
|
97
|
+
sys.exit(1)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
print(VARIABLES[name].value[index])
|
|
101
|
+
return
|
|
102
|
+
except IndexError:
|
|
103
|
+
print("Index out of range")
|
|
104
|
+
sys.exit(1)
|
|
105
|
+
|
|
106
|
+
if alr_exits is not None:
|
|
107
|
+
print(alr_exits.value)
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
print(_decode_value(string))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def CAST(string: str):
|
|
114
|
+
from .runtime import VARIABLES
|
|
115
|
+
|
|
116
|
+
slices = [s.strip() for s in string.split("to") if s]
|
|
117
|
+
name = slices[0]
|
|
118
|
+
new_type = slices[1]
|
|
119
|
+
alr_exits = VARIABLES.get(name)
|
|
120
|
+
|
|
121
|
+
if alr_exits is None:
|
|
122
|
+
print(f"Variable with name {name} doesnt exist")
|
|
123
|
+
sys.exit(1)
|
|
124
|
+
|
|
125
|
+
if new_type == "int":
|
|
126
|
+
alr_exits.cast(int(alr_exits.value))
|
|
127
|
+
|
|
128
|
+
elif new_type == "float":
|
|
129
|
+
alr_exits.cast(float(alr_exits.value))
|
|
130
|
+
|
|
131
|
+
elif new_type == "str":
|
|
132
|
+
alr_exits.cast(str(alr_exits.value))
|
|
133
|
+
|
|
134
|
+
elif new_type == "array":
|
|
135
|
+
alr_exits.cast(list(alr_exits.value))
|
|
136
|
+
|
|
137
|
+
else:
|
|
138
|
+
print(f"Invalid type {new_type}")
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def ADD(string: str):
|
|
143
|
+
from .runtime import VARIABLES
|
|
144
|
+
|
|
145
|
+
slices = [s.strip() for s in string.split("to") if s]
|
|
146
|
+
name = slices[1]
|
|
147
|
+
new_value = _decode_value(slices[0])
|
|
148
|
+
alr_exits = VARIABLES.get(name)
|
|
149
|
+
|
|
150
|
+
if alr_exits is None:
|
|
151
|
+
print(f"Variable with name {name} doesnt exist")
|
|
152
|
+
sys.exit(1)
|
|
153
|
+
|
|
154
|
+
if alr_exits.type != list:
|
|
155
|
+
print(f"Variable must be an array not a {alr_exits.type.__name__}")
|
|
156
|
+
sys.exit(1)
|
|
157
|
+
|
|
158
|
+
alr_exits.value.append(new_value)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def RUN(string: str):
|
|
162
|
+
from .functions import Function
|
|
163
|
+
from .runtime import VARIABLES
|
|
164
|
+
|
|
165
|
+
slices = [_.strip() for _ in string.split(" ") if _]
|
|
166
|
+
|
|
167
|
+
func_name = slices[0]
|
|
168
|
+
|
|
169
|
+
if VARIABLES.get(func_name) is None:
|
|
170
|
+
print(f"Function named {func_name} doesnt exist!")
|
|
171
|
+
sys.exit(1)
|
|
172
|
+
|
|
173
|
+
if VARIABLES.get(func_name).type != Function:
|
|
174
|
+
print(
|
|
175
|
+
f"Cant run variable of type {VARIABLES.get(func_name).type.__name__}, must be a function!"
|
|
176
|
+
)
|
|
177
|
+
sys.exit(1)
|
|
178
|
+
|
|
179
|
+
if len(slices) == 1:
|
|
180
|
+
VARIABLES[func_name].value.run()
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
if slices[1] == "with":
|
|
184
|
+
params: VARIABLES_TYPE = {}
|
|
185
|
+
params_slices = [_.strip() for _ in " ".join(slices[2:]).split("and")]
|
|
186
|
+
|
|
187
|
+
for param in params_slices:
|
|
188
|
+
s = [_.strip() for _ in re.split(re.escape(" "), param) if _]
|
|
189
|
+
if s[1] != "as":
|
|
190
|
+
print(f"Expected as in parameter assignment, found {s[1]}")
|
|
191
|
+
sys.exit()
|
|
192
|
+
name = s[0].strip().replace("<", "").replace(">", "")
|
|
193
|
+
|
|
194
|
+
if name not in VARIABLES.get(func_name).value.params:
|
|
195
|
+
print(f"Unknown parameter {name}")
|
|
196
|
+
sys.exit(1)
|
|
197
|
+
|
|
198
|
+
value = s[2].strip()
|
|
199
|
+
|
|
200
|
+
if VARIABLES.get(value) is not None:
|
|
201
|
+
params[name] = VARIABLES.get(value)
|
|
202
|
+
else:
|
|
203
|
+
params[name] = Variable(name=name, value=_decode_value(value))
|
|
204
|
+
|
|
205
|
+
VARIABLES[func_name].value.run(params=params)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from therl.consts import INSTRUCTION_TO_FUNC, params_pattern, pattern
|
|
5
|
+
from therl.functions import Function
|
|
6
|
+
from therl.runtime import VARIABLES
|
|
7
|
+
from therl.variable import Variable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
try:
|
|
12
|
+
if sys.argv[1].split(".")[-1] != "therl":
|
|
13
|
+
print("Invalid file type, file must be .therl")
|
|
14
|
+
sys.exit(1)
|
|
15
|
+
|
|
16
|
+
with open(sys.argv[1], "r") as file:
|
|
17
|
+
instructions = [line.strip() for line in file if line.strip()]
|
|
18
|
+
except IndexError:
|
|
19
|
+
print("no file provided!")
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
except FileNotFoundError:
|
|
22
|
+
print("file doesnt exist!")
|
|
23
|
+
sys.exit(1)
|
|
24
|
+
|
|
25
|
+
inside_function = False
|
|
26
|
+
cur_func_instuctions = []
|
|
27
|
+
cur_func_name = ""
|
|
28
|
+
cur_func_params = set()
|
|
29
|
+
|
|
30
|
+
for instruction in instructions:
|
|
31
|
+
tokens = [t.strip() for t in re.split(pattern, instruction, maxsplit=2) if t]
|
|
32
|
+
|
|
33
|
+
action = tokens[0]
|
|
34
|
+
|
|
35
|
+
arg = "".join(tokens[1:])
|
|
36
|
+
|
|
37
|
+
if action == "eof":
|
|
38
|
+
inside_function = False
|
|
39
|
+
|
|
40
|
+
VARIABLES[cur_func_name] = Variable(
|
|
41
|
+
name=cur_func_name,
|
|
42
|
+
value=Function(
|
|
43
|
+
name=cur_func_name,
|
|
44
|
+
instructions=cur_func_instuctions,
|
|
45
|
+
params=list(cur_func_params),
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
cur_func_name = ""
|
|
49
|
+
cur_func_instuctions = []
|
|
50
|
+
cur_func_params.clear()
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
if inside_function:
|
|
54
|
+
cur_func_instuctions.append(tokens)
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
if action == "func":
|
|
58
|
+
name_and_params = [l.strip() for l in arg.split(" ") if l]
|
|
59
|
+
cur_func_name = name_and_params[0]
|
|
60
|
+
|
|
61
|
+
if VARIABLES.get(cur_func_name) is not None:
|
|
62
|
+
print(
|
|
63
|
+
f"Cant name function to a variable of the same name, '{cur_func_name}' is already defined!"
|
|
64
|
+
)
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
inside_function = True
|
|
68
|
+
params_split = [
|
|
69
|
+
t.strip()
|
|
70
|
+
for t in re.split(params_pattern, "".join(name_and_params[1:]))
|
|
71
|
+
if t
|
|
72
|
+
]
|
|
73
|
+
for i, param in enumerate(params_split):
|
|
74
|
+
try:
|
|
75
|
+
if param == "<" and params_split[i + 2] == ">":
|
|
76
|
+
cur_func_params.add(params_split[i + 1])
|
|
77
|
+
except IndexError:
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
if not inside_function:
|
|
81
|
+
INSTRUCTION_TO_FUNC[action](arg)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import therl.consts
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _decode_value(value: str) -> Any:
|
|
8
|
+
from therl.runtime import VARIABLES
|
|
9
|
+
|
|
10
|
+
if value[0] == "[" and value[-1] == "]":
|
|
11
|
+
return _decode_array(value)
|
|
12
|
+
|
|
13
|
+
used_operator = not set(value.split(" ")).isdisjoint(therl.consts.OPERATORS)
|
|
14
|
+
|
|
15
|
+
if used_operator:
|
|
16
|
+
try:
|
|
17
|
+
return eval(
|
|
18
|
+
value,
|
|
19
|
+
None,
|
|
20
|
+
{
|
|
21
|
+
var[0]: var[1].value for var in VARIABLES.items()
|
|
22
|
+
}, # will it be unsafe in this case ?
|
|
23
|
+
)
|
|
24
|
+
except NameError as e:
|
|
25
|
+
print(f"variable nammed {e.name} doesnt exist!")
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
if value.strip()[0] == value.strip()[-1] == '"':
|
|
29
|
+
return str(value[1:-1])
|
|
30
|
+
|
|
31
|
+
if value.strip().lower() == "true":
|
|
32
|
+
return True
|
|
33
|
+
if value.strip().lower() == "false":
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
# Try Integer
|
|
37
|
+
try:
|
|
38
|
+
return int(value)
|
|
39
|
+
except ValueError:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
# Try Float
|
|
43
|
+
try:
|
|
44
|
+
return float(value)
|
|
45
|
+
except ValueError:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
if VARIABLES.get(value) is not None:
|
|
49
|
+
return VARIABLES.get(value).value
|
|
50
|
+
|
|
51
|
+
print("Invalid data value: ", value.strip())
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _decode_array(array_str: str) -> list:
|
|
56
|
+
clean_str = array_str[1:-1] # remove [ ]
|
|
57
|
+
|
|
58
|
+
if clean_str.strip() == "":
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
if ".." in clean_str:
|
|
62
|
+
try:
|
|
63
|
+
s_and_f = clean_str.split("..")
|
|
64
|
+
start = int(s_and_f[0])
|
|
65
|
+
|
|
66
|
+
finish = int(s_and_f[-1])
|
|
67
|
+
|
|
68
|
+
return list(range(start, finish))
|
|
69
|
+
except ValueError:
|
|
70
|
+
print("Array sequence must be made with two ints!")
|
|
71
|
+
sys.exit(1)
|
|
72
|
+
|
|
73
|
+
array = []
|
|
74
|
+
|
|
75
|
+
for val in clean_str.split(","):
|
|
76
|
+
v = _decode_value(val.strip())
|
|
77
|
+
if len(array) > 0 and type(array[0]) != type(v):
|
|
78
|
+
print("Cannot mix and match data types in array!")
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
|
|
81
|
+
array.append(v)
|
|
82
|
+
|
|
83
|
+
return array
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Any, Generic, TypeVar
|
|
3
|
+
|
|
4
|
+
T = TypeVar("T")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Variable(Generic[T]):
|
|
8
|
+
def __init__(self, name: str, value: T) -> None:
|
|
9
|
+
self.name = name
|
|
10
|
+
self.type: type[T] = type(value)
|
|
11
|
+
self.value: T = value
|
|
12
|
+
|
|
13
|
+
def set(self, new_value: Any):
|
|
14
|
+
if type(new_value) is not self.type:
|
|
15
|
+
print(
|
|
16
|
+
f"Error variable types doesnt match! from {self.type.__name__} to {type(new_value).__name__}"
|
|
17
|
+
)
|
|
18
|
+
sys.exit(1)
|
|
19
|
+
|
|
20
|
+
self.value = new_value
|
|
21
|
+
|
|
22
|
+
def cast(self, new_value: T):
|
|
23
|
+
self.type: type[T] = type(new_value)
|
|
24
|
+
self.value: T = new_value
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: therl
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A human readable programming language
|
|
5
|
+
Author-email: CyzmiX <itzmedigamingx@gmail.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.13
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Therl
|
|
13
|
+
|
|
14
|
+
> **T**ruly **H**uman **R**eadaby **L**anguage
|
|
15
|
+
> _(Yes, the acronym is a little cursed. I know.)_
|
|
16
|
+
|
|
17
|
+
Therl is a human-readable, instruction-based programming language written entirely in pure Python. Its goal is to make code read almost like plain English while remaining predictable and easy to parse.
|
|
18
|
+
|
|
19
|
+
```therl
|
|
20
|
+
set greeting to "hello"
|
|
21
|
+
|
|
22
|
+
func greet <name>
|
|
23
|
+
|
|
24
|
+
say greeting + " " + name + "!"
|
|
25
|
+
|
|
26
|
+
eof
|
|
27
|
+
|
|
28
|
+
func main
|
|
29
|
+
|
|
30
|
+
run greet with <name> as "Jeff"
|
|
31
|
+
|
|
32
|
+
set numbers to [1..10]
|
|
33
|
+
|
|
34
|
+
say numbers
|
|
35
|
+
|
|
36
|
+
eof
|
|
37
|
+
|
|
38
|
+
run main
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Output:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
hello Jeff!
|
|
45
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Features
|
|
51
|
+
|
|
52
|
+
- 📖 Human-readable syntax
|
|
53
|
+
- 📝 Instruction-based language design
|
|
54
|
+
- 🐍 Written entirely in pure Python
|
|
55
|
+
- 🔄 Strictly dynamically typed variables
|
|
56
|
+
- 📦 Built-in lists
|
|
57
|
+
- 🔢 Integer, float, string and boolean types
|
|
58
|
+
- 🧩 Functions with named parameters
|
|
59
|
+
- 📢 Simple `say` statement for output
|
|
60
|
+
- ✏️ Mutable list elements
|
|
61
|
+
- 🚀 Easy to extend
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Example
|
|
66
|
+
|
|
67
|
+
```therl
|
|
68
|
+
set greeting to "hello"
|
|
69
|
+
|
|
70
|
+
func greet <name>
|
|
71
|
+
|
|
72
|
+
say greeting + " " + name + "!"
|
|
73
|
+
|
|
74
|
+
eof
|
|
75
|
+
|
|
76
|
+
func main
|
|
77
|
+
|
|
78
|
+
say "Welcome!"
|
|
79
|
+
|
|
80
|
+
run greet with <name> as "Jeff"
|
|
81
|
+
|
|
82
|
+
set ids to [1..10]
|
|
83
|
+
|
|
84
|
+
say ids
|
|
85
|
+
|
|
86
|
+
set ids at 0 to 1000
|
|
87
|
+
|
|
88
|
+
say ids
|
|
89
|
+
|
|
90
|
+
add 10 to ids
|
|
91
|
+
|
|
92
|
+
say ids
|
|
93
|
+
|
|
94
|
+
eof
|
|
95
|
+
|
|
96
|
+
run main
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Output:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
Welcome!
|
|
103
|
+
hello Jeff!
|
|
104
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
105
|
+
[1000, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
106
|
+
[1000, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
# Project Status
|
|
112
|
+
|
|
113
|
+
⚠️ Therl is currently in active development.
|
|
114
|
+
|
|
115
|
+
Expect missing features, breaking changes, and plenty of experimentation as the language evolves.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
# Roadmap
|
|
120
|
+
|
|
121
|
+
- [ ] Arithmetic expressions
|
|
122
|
+
- [ ] Conditional statements
|
|
123
|
+
- [ ] Loops
|
|
124
|
+
- [ ] Dictionaries / Maps
|
|
125
|
+
- [ ] Modules
|
|
126
|
+
- [ ] Classes
|
|
127
|
+
- [ ] Error reporting with line numbers
|
|
128
|
+
- [ ] Standard library
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
# Why?
|
|
133
|
+
|
|
134
|
+
Because making programming languages is fun.
|
|
135
|
+
|
|
136
|
+
And because writing
|
|
137
|
+
|
|
138
|
+
```therl
|
|
139
|
+
set score to 100
|
|
140
|
+
|
|
141
|
+
add 10 to score
|
|
142
|
+
|
|
143
|
+
say score
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
just feels oddly satisfying.
|
|
147
|
+
|
|
148
|
+
---
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/therl/__init__.py
|
|
4
|
+
src/therl/__main__.py
|
|
5
|
+
src/therl/consts.py
|
|
6
|
+
src/therl/functions.py
|
|
7
|
+
src/therl/instructions.py
|
|
8
|
+
src/therl/main.py
|
|
9
|
+
src/therl/runtime.py
|
|
10
|
+
src/therl/types.py
|
|
11
|
+
src/therl/utils.py
|
|
12
|
+
src/therl/variable.py
|
|
13
|
+
src/therl.egg-info/PKG-INFO
|
|
14
|
+
src/therl.egg-info/SOURCES.txt
|
|
15
|
+
src/therl.egg-info/dependency_links.txt
|
|
16
|
+
src/therl.egg-info/entry_points.txt
|
|
17
|
+
src/therl.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
therl
|