shell-lite 0.3.3__py3-none-any.whl → 0.3.5__py3-none-any.whl
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.
- shell_lite/__init__.py +1 -0
- shell_lite/ast_nodes.py +15 -110
- shell_lite/cli.py +10 -0
- shell_lite/compiler.py +2 -189
- shell_lite/formatter.py +75 -0
- shell_lite/interpreter.py +35 -538
- shell_lite/js_compiler.py +3 -79
- shell_lite/lexer.py +29 -107
- shell_lite/main.py +120 -75
- shell_lite/parser.py +17 -510
- shell_lite/runtime.py +1 -76
- shell_lite-0.3.5.dist-info/LICENSE +21 -0
- shell_lite-0.3.5.dist-info/METADATA +40 -0
- shell_lite-0.3.5.dist-info/RECORD +17 -0
- {shell_lite-0.3.3.dist-info → shell_lite-0.3.5.dist-info}/WHEEL +1 -1
- shell_lite-0.3.3.dist-info/METADATA +0 -77
- shell_lite-0.3.3.dist-info/RECORD +0 -14
- {shell_lite-0.3.3.dist-info → shell_lite-0.3.5.dist-info}/entry_points.txt +0 -0
- {shell_lite-0.3.3.dist-info → shell_lite-0.3.5.dist-info}/top_level.txt +0 -0
shell_lite/main.py
CHANGED
|
@@ -8,7 +8,8 @@ import subprocess
|
|
|
8
8
|
from .lexer import Lexer
|
|
9
9
|
from .parser import Parser
|
|
10
10
|
from .interpreter import Interpreter
|
|
11
|
-
|
|
11
|
+
from .ast_nodes import *
|
|
12
|
+
import json
|
|
12
13
|
def execute_source(source: str, interpreter: Interpreter):
|
|
13
14
|
lines = source.split('\n')
|
|
14
15
|
try:
|
|
@@ -16,7 +17,6 @@ def execute_source(source: str, interpreter: Interpreter):
|
|
|
16
17
|
tokens = lexer.tokenize()
|
|
17
18
|
parser = Parser(tokens)
|
|
18
19
|
statements = parser.parse()
|
|
19
|
-
|
|
20
20
|
for stmt in statements:
|
|
21
21
|
interpreter.visit(stmt)
|
|
22
22
|
except Exception as e:
|
|
@@ -25,7 +25,6 @@ def execute_source(source: str, interpreter: Interpreter):
|
|
|
25
25
|
if 0 <= e.line-1 < len(lines):
|
|
26
26
|
print(f" {lines[e.line-1].strip()}")
|
|
27
27
|
print(f"Exception: {e}")
|
|
28
|
-
|
|
29
28
|
def run_file(filename: str):
|
|
30
29
|
if not os.path.exists(filename):
|
|
31
30
|
print(f"Error: File '{filename}' not found.")
|
|
@@ -34,7 +33,6 @@ def run_file(filename: str):
|
|
|
34
33
|
source = f.read()
|
|
35
34
|
interpreter = Interpreter()
|
|
36
35
|
execute_source(source, interpreter)
|
|
37
|
-
|
|
38
36
|
def run_repl():
|
|
39
37
|
interpreter = Interpreter()
|
|
40
38
|
print("\n" + "="*40)
|
|
@@ -43,19 +41,14 @@ def run_repl():
|
|
|
43
41
|
print("Version: v0.03.3 | Made by Shrey Naithani")
|
|
44
42
|
print("Commands: Type 'exit' to quit, 'help' for examples.")
|
|
45
43
|
print("Note: Terminal commands (like 'shl install') must be run in CMD/PowerShell, not here.")
|
|
46
|
-
|
|
47
44
|
buffer = []
|
|
48
45
|
indent_level = 0
|
|
49
|
-
|
|
50
46
|
while True:
|
|
51
47
|
try:
|
|
52
48
|
prompt = "... " if indent_level > 0 else ">>> "
|
|
53
49
|
line = input(prompt)
|
|
54
|
-
|
|
55
50
|
if line.strip() == "exit":
|
|
56
51
|
break
|
|
57
|
-
|
|
58
|
-
# Support line continuation with \
|
|
59
52
|
if line.endswith("\\"):
|
|
60
53
|
buffer.append(line[:-1])
|
|
61
54
|
indent_level = 1
|
|
@@ -68,12 +61,10 @@ def run_repl():
|
|
|
68
61
|
print(' display(tasks) # View the list')
|
|
69
62
|
print(' \\ # Tip: Use \\ at the end of a line for multi-line typing')
|
|
70
63
|
continue
|
|
71
|
-
|
|
72
64
|
if line.strip().startswith("shl"):
|
|
73
65
|
print("! Hint: You are already INSIDE ShellLite. You don't need to type 'shl' here.")
|
|
74
66
|
print("! To run terminal commands, exit this REPL first.")
|
|
75
67
|
continue
|
|
76
|
-
|
|
77
68
|
if not line:
|
|
78
69
|
if indent_level > 0:
|
|
79
70
|
source = "\n".join(buffer)
|
|
@@ -81,7 +72,6 @@ def run_repl():
|
|
|
81
72
|
buffer = []
|
|
82
73
|
indent_level = 0
|
|
83
74
|
continue
|
|
84
|
-
|
|
85
75
|
if line.strip().endswith(":"):
|
|
86
76
|
indent_level = 1
|
|
87
77
|
buffer.append(line)
|
|
@@ -93,10 +83,8 @@ def run_repl():
|
|
|
93
83
|
execute_source(source, interpreter)
|
|
94
84
|
buffer = []
|
|
95
85
|
indent_level = 0
|
|
96
|
-
|
|
97
86
|
if line.strip():
|
|
98
87
|
execute_source(line, interpreter)
|
|
99
|
-
|
|
100
88
|
except KeyboardInterrupt:
|
|
101
89
|
print("\nExiting...")
|
|
102
90
|
break
|
|
@@ -104,34 +92,24 @@ def run_repl():
|
|
|
104
92
|
print(f"Error: {e}")
|
|
105
93
|
buffer = []
|
|
106
94
|
indent_level = 0
|
|
107
|
-
|
|
108
95
|
def install_globally():
|
|
109
|
-
"""Performs the global PATH installation."""
|
|
110
96
|
print("\n" + "="*50)
|
|
111
97
|
print(" ShellLite Global Installer")
|
|
112
98
|
print("="*50)
|
|
113
|
-
|
|
114
99
|
install_dir = os.path.join(os.environ['LOCALAPPDATA'], 'ShellLite')
|
|
115
100
|
if not os.path.exists(install_dir):
|
|
116
101
|
os.makedirs(install_dir)
|
|
117
|
-
|
|
118
102
|
target_exe = os.path.join(install_dir, 'shl.exe')
|
|
119
103
|
current_path = sys.executable
|
|
120
|
-
|
|
121
|
-
# If not running as EXE (e.g. py script), we can't 'install' the script easily as shl.exe
|
|
122
104
|
is_frozen = getattr(sys, 'frozen', False)
|
|
123
|
-
|
|
124
105
|
try:
|
|
125
106
|
if is_frozen:
|
|
126
107
|
shutil.copy2(current_path, target_exe)
|
|
127
108
|
else:
|
|
128
109
|
print("Error: Installation requires the shl.exe file.")
|
|
129
110
|
return
|
|
130
|
-
|
|
131
|
-
# Add to PATH (User level)
|
|
132
111
|
ps_cmd = f'$oldPath = [Environment]::GetEnvironmentVariable("Path", "User"); if ($oldPath -notlike "*ShellLite*") {{ [Environment]::SetEnvironmentVariable("Path", "$oldPath;{install_dir}", "User") }}'
|
|
133
112
|
subprocess.run(["powershell", "-Command", ps_cmd], capture_output=True)
|
|
134
|
-
|
|
135
113
|
print(f"\n[SUCCESS] ShellLite is now installed!")
|
|
136
114
|
print(f"Location: {install_dir}")
|
|
137
115
|
print("\nACTION REQUIRED:")
|
|
@@ -142,70 +120,45 @@ def install_globally():
|
|
|
142
120
|
input("Press Enter to continue...")
|
|
143
121
|
except Exception as e:
|
|
144
122
|
print(f"Installation failed: {e}")
|
|
145
|
-
|
|
146
123
|
def install_package(package_name: str):
|
|
147
|
-
"""
|
|
148
|
-
Downloads a package from GitHub.
|
|
149
|
-
Format: shl get user/repo
|
|
150
|
-
Target: ~/.shell_lite/modules/<repo>/ (containing main.shl or similar)
|
|
151
|
-
"""
|
|
152
124
|
if '/' not in package_name:
|
|
153
125
|
print("Error: Package must be in format 'user/repo'")
|
|
154
126
|
return
|
|
155
|
-
|
|
156
127
|
user, repo = package_name.split('/')
|
|
157
128
|
print(f"Fetching '{package_name}' from GitHub...")
|
|
158
|
-
|
|
159
|
-
# Define modules dir
|
|
160
129
|
home = os.path.expanduser("~")
|
|
161
130
|
modules_dir = os.path.join(home, ".shell_lite", "modules")
|
|
162
131
|
if not os.path.exists(modules_dir):
|
|
163
132
|
os.makedirs(modules_dir)
|
|
164
|
-
|
|
165
|
-
# Clean up existing
|
|
166
133
|
target_dir = os.path.join(modules_dir, repo)
|
|
167
134
|
if os.path.exists(target_dir):
|
|
168
135
|
print(f"Removing existing '{repo}'...")
|
|
169
136
|
shutil.rmtree(target_dir)
|
|
170
|
-
|
|
171
|
-
# Strategy 1: Download Main Branch ZIP
|
|
172
137
|
zip_url = f"https://github.com/{user}/{repo}/archive/refs/heads/main.zip"
|
|
173
|
-
|
|
174
138
|
try:
|
|
175
139
|
print(f"Downloading {zip_url}...")
|
|
176
140
|
with urllib.request.urlopen(zip_url) as response:
|
|
177
141
|
zip_data = response.read()
|
|
178
|
-
|
|
179
142
|
with zipfile.ZipFile(io.BytesIO(zip_data)) as z:
|
|
180
143
|
z.extractall(modules_dir)
|
|
181
|
-
|
|
182
|
-
# Rename extracted folder (repo-main -> repo)
|
|
183
144
|
extracted_name = f"{repo}-main"
|
|
184
145
|
extracted_path = os.path.join(modules_dir, extracted_name)
|
|
185
|
-
|
|
186
146
|
if os.path.exists(extracted_path):
|
|
187
147
|
os.rename(extracted_path, target_dir)
|
|
188
148
|
print(f"[SUCCESS] Installed '{repo}' to {target_dir}")
|
|
189
149
|
else:
|
|
190
150
|
print(f"Error: Could not find extracted folder '{extracted_name}'.")
|
|
191
|
-
|
|
192
151
|
return
|
|
193
|
-
|
|
194
152
|
except urllib.error.HTTPError:
|
|
195
|
-
# Strategy 2: Try Master Branch
|
|
196
153
|
zip_url = f"https://github.com/{user}/{repo}/archive/refs/heads/master.zip"
|
|
197
154
|
try:
|
|
198
155
|
print(f"Downloading {zip_url}...")
|
|
199
156
|
with urllib.request.urlopen(zip_url) as response:
|
|
200
157
|
zip_data = response.read()
|
|
201
|
-
|
|
202
158
|
with zipfile.ZipFile(io.BytesIO(zip_data)) as z:
|
|
203
159
|
z.extractall(modules_dir)
|
|
204
|
-
|
|
205
|
-
# Rename extracted folder (repo-master -> repo)
|
|
206
160
|
extracted_name = f"{repo}-master"
|
|
207
161
|
extracted_path = os.path.join(modules_dir, extracted_name)
|
|
208
|
-
|
|
209
162
|
if os.path.exists(extracted_path):
|
|
210
163
|
os.rename(extracted_path, target_dir)
|
|
211
164
|
print(f"[SUCCESS] Installed '{repo}' to {target_dir}")
|
|
@@ -213,30 +166,22 @@ def install_package(package_name: str):
|
|
|
213
166
|
print(f"Error: Could not find extracted folder '{extracted_name}'.")
|
|
214
167
|
except Exception as e:
|
|
215
168
|
print(f"Installation failed: {e}")
|
|
216
|
-
|
|
217
169
|
except Exception as e:
|
|
218
170
|
print(f"Installation failed: {e}")
|
|
219
|
-
|
|
220
|
-
|
|
221
171
|
def compile_file(filename: str, target: str = 'python'):
|
|
222
172
|
if not os.path.exists(filename):
|
|
223
173
|
print(f"Error: File '{filename}' not found.")
|
|
224
174
|
return
|
|
225
|
-
|
|
226
175
|
print(f"Compiling {filename} to {target.upper()}...")
|
|
227
|
-
|
|
228
176
|
with open(filename, 'r', encoding='utf-8') as f:
|
|
229
177
|
source = f.read()
|
|
230
|
-
|
|
231
178
|
try:
|
|
232
179
|
from .parser import Parser
|
|
233
180
|
from .lexer import Lexer
|
|
234
|
-
|
|
235
181
|
lexer = Lexer(source)
|
|
236
182
|
tokens = lexer.tokenize()
|
|
237
183
|
parser = Parser(tokens)
|
|
238
184
|
statements = parser.parse()
|
|
239
|
-
|
|
240
185
|
if target.lower() == 'js':
|
|
241
186
|
from .js_compiler import JSCompiler
|
|
242
187
|
compiler = JSCompiler()
|
|
@@ -247,17 +192,12 @@ def compile_file(filename: str, target: str = 'python'):
|
|
|
247
192
|
compiler = Compiler()
|
|
248
193
|
code = compiler.compile(statements)
|
|
249
194
|
ext = '.py'
|
|
250
|
-
|
|
251
195
|
output_file = filename.replace('.shl', ext)
|
|
252
196
|
if output_file == filename: output_file += ext
|
|
253
|
-
|
|
254
197
|
with open(output_file, 'w') as f:
|
|
255
198
|
f.write(code)
|
|
256
|
-
|
|
257
199
|
print(f"[SUCCESS] Transpiled to {output_file}")
|
|
258
|
-
|
|
259
200
|
if target.lower() == 'python':
|
|
260
|
-
# Optional: Compile to EXE using PyInstaller
|
|
261
201
|
try:
|
|
262
202
|
import PyInstaller.__main__
|
|
263
203
|
print("Building Executable with PyInstaller...")
|
|
@@ -269,23 +209,114 @@ def compile_file(filename: str, target: str = 'python'):
|
|
|
269
209
|
])
|
|
270
210
|
print(f"[SUCCESS] Built {os.path.splitext(os.path.basename(filename))[0]}.exe")
|
|
271
211
|
except ImportError:
|
|
272
|
-
pass
|
|
273
|
-
|
|
212
|
+
pass
|
|
274
213
|
except Exception as e:
|
|
275
214
|
print(f"Compilation Failed: {e}")
|
|
276
|
-
|
|
277
|
-
|
|
215
|
+
def lint_file(filename: str):
|
|
216
|
+
if not os.path.exists(filename):
|
|
217
|
+
print(json.dumps([{"line": 0, "message": f"File {filename} not found"}]))
|
|
218
|
+
return
|
|
219
|
+
try:
|
|
220
|
+
with open(filename, 'r', encoding='utf-8') as f:
|
|
221
|
+
source = f.read()
|
|
222
|
+
lexer = Lexer(source)
|
|
223
|
+
tokens = lexer.tokenize()
|
|
224
|
+
parser = Parser(tokens)
|
|
225
|
+
parser.parse()
|
|
226
|
+
print(json.dumps([]))
|
|
227
|
+
except Exception as e:
|
|
228
|
+
line = getattr(e, 'line', 1)
|
|
229
|
+
print(json.dumps([{
|
|
230
|
+
"line": line,
|
|
231
|
+
"message": str(e)
|
|
232
|
+
}]))
|
|
233
|
+
def resolve_cursor(filename: str, line: int, col: int):
|
|
234
|
+
try:
|
|
235
|
+
with open(filename, 'r', encoding='utf-8') as f:
|
|
236
|
+
source = f.read()
|
|
237
|
+
lexer = Lexer(source)
|
|
238
|
+
tokens = lexer.tokenize()
|
|
239
|
+
parser = Parser(tokens)
|
|
240
|
+
nodes = parser.parse()
|
|
241
|
+
target_token = None
|
|
242
|
+
for t in tokens:
|
|
243
|
+
if t.line == line:
|
|
244
|
+
if t.column <= col <= t.column + len(t.value):
|
|
245
|
+
target_token = t
|
|
246
|
+
break
|
|
247
|
+
if not target_token or target_token.type != 'ID':
|
|
248
|
+
print(json.dumps({"found": False}))
|
|
249
|
+
return
|
|
250
|
+
word = target_token.value
|
|
251
|
+
def find_def(n_list, name):
|
|
252
|
+
for node in n_list:
|
|
253
|
+
if isinstance(node, FunctionDef) and node.name == name:
|
|
254
|
+
return node, "Function"
|
|
255
|
+
if isinstance(node, ClassDef) and node.name == name:
|
|
256
|
+
return node, "Class"
|
|
257
|
+
if isinstance(node, Assign) and node.name == name:
|
|
258
|
+
return node, "Variable"
|
|
259
|
+
if isinstance(node, If):
|
|
260
|
+
res = find_def(node.body, name)
|
|
261
|
+
if res: return res
|
|
262
|
+
return None, None
|
|
263
|
+
found_node = None
|
|
264
|
+
found_type = None
|
|
265
|
+
queue = nodes[:]
|
|
266
|
+
while queue:
|
|
267
|
+
n = queue.pop(0)
|
|
268
|
+
if isinstance(n, FunctionDef):
|
|
269
|
+
if n.name == word:
|
|
270
|
+
found_node = n
|
|
271
|
+
found_type = "Function"
|
|
272
|
+
break
|
|
273
|
+
queue.extend(n.body)
|
|
274
|
+
elif isinstance(n, ClassDef):
|
|
275
|
+
if n.name == word:
|
|
276
|
+
found_node = n
|
|
277
|
+
found_type = "Class"
|
|
278
|
+
break
|
|
279
|
+
queue.extend(n.methods)
|
|
280
|
+
elif isinstance(n, Assign) and n.name == word:
|
|
281
|
+
found_node = n
|
|
282
|
+
found_type = "Variable"
|
|
283
|
+
break
|
|
284
|
+
elif isinstance(n, If): queue.extend(n.body)
|
|
285
|
+
elif isinstance(n, While): queue.extend(n.body)
|
|
286
|
+
elif isinstance(n, For): queue.extend(n.body)
|
|
287
|
+
if found_node:
|
|
288
|
+
print(json.dumps({
|
|
289
|
+
"found": True,
|
|
290
|
+
"file": filename,
|
|
291
|
+
"line": found_node.line,
|
|
292
|
+
"hover": f"**{found_type}** `{word}`"
|
|
293
|
+
}))
|
|
294
|
+
else:
|
|
295
|
+
print(json.dumps({"found": False}))
|
|
296
|
+
except Exception:
|
|
297
|
+
print(json.dumps({"found": False}))
|
|
298
|
+
def format_file(filename: str):
|
|
299
|
+
if not os.path.exists(filename):
|
|
300
|
+
print(f"Error: File '{filename}' not found.")
|
|
301
|
+
return
|
|
302
|
+
try:
|
|
303
|
+
with open(filename, 'r', encoding='utf-8') as f:
|
|
304
|
+
source = f.read()
|
|
305
|
+
from .formatter import Formatter
|
|
306
|
+
formatter = Formatter(source)
|
|
307
|
+
formatted_code = formatter.format()
|
|
308
|
+
with open(filename, 'w', encoding='utf-8') as f:
|
|
309
|
+
f.write(formatted_code)
|
|
310
|
+
print(f"[SUCCESS] Formatted {filename}")
|
|
311
|
+
except Exception as e:
|
|
312
|
+
print(f"Formatting failed: {e}")
|
|
278
313
|
def self_install_check():
|
|
279
|
-
"""Checks if shl is in PATH, if not, offer to install it."""
|
|
280
|
-
# Simple check: is shl.exe in a known Global path?
|
|
281
|
-
# Or just check if 'shl' works in shell
|
|
282
314
|
res = subprocess.run(["where", "shl"], capture_output=True, text=True)
|
|
283
315
|
if "ShellLite" not in res.stdout:
|
|
284
316
|
print("\nShellLite is not installed globally.")
|
|
285
317
|
choice = input("Would you like to install it so 'shl' works everywhere? (y/n): ").lower()
|
|
286
318
|
if choice == 'y':
|
|
287
319
|
install_globally()
|
|
288
|
-
|
|
289
320
|
def show_help():
|
|
290
321
|
print("""
|
|
291
322
|
ShellLite - The English-Like Programming Language
|
|
@@ -294,11 +325,12 @@ Usage:
|
|
|
294
325
|
shl Start the interactive REPL
|
|
295
326
|
shl help Show this help message
|
|
296
327
|
shl compile <file> Compile a script (Options: --target js)
|
|
328
|
+
shl fmt <file> Format a script
|
|
329
|
+
shl check <file> Lint a file (JSON output)
|
|
330
|
+
shl resolve <file> <line> <col> Resolve symbol (JSON output)
|
|
297
331
|
shl install Install ShellLite globally to your system PATH
|
|
298
|
-
|
|
299
332
|
For documentation, visit: https://github.com/Shrey-N/ShellDesk
|
|
300
333
|
""")
|
|
301
|
-
|
|
302
334
|
def main():
|
|
303
335
|
if len(sys.argv) > 1:
|
|
304
336
|
cmd = sys.argv[1]
|
|
@@ -325,14 +357,27 @@ def main():
|
|
|
325
357
|
else:
|
|
326
358
|
print("Usage: shl get <user/repo>")
|
|
327
359
|
elif cmd == "install":
|
|
328
|
-
# Install ShellLite itself
|
|
329
360
|
install_globally()
|
|
361
|
+
elif cmd == "fmt" or cmd == "format":
|
|
362
|
+
if len(sys.argv) > 2:
|
|
363
|
+
filename = sys.argv[2]
|
|
364
|
+
format_file(filename)
|
|
365
|
+
else:
|
|
366
|
+
print("Usage: shl fmt <filename>")
|
|
367
|
+
elif cmd == "check":
|
|
368
|
+
if len(sys.argv) > 2:
|
|
369
|
+
filename = sys.argv[2]
|
|
370
|
+
lint_file(filename)
|
|
371
|
+
elif cmd == "resolve":
|
|
372
|
+
if len(sys.argv) > 4:
|
|
373
|
+
filename = sys.argv[2]
|
|
374
|
+
line = int(sys.argv[3])
|
|
375
|
+
col = int(sys.argv[4])
|
|
376
|
+
resolve_cursor(filename, line, col)
|
|
330
377
|
else:
|
|
331
378
|
run_file(sys.argv[1])
|
|
332
379
|
else:
|
|
333
|
-
# No args - trigger install check, then REPL
|
|
334
380
|
self_install_check()
|
|
335
381
|
run_repl()
|
|
336
|
-
|
|
337
382
|
if __name__ == "__main__":
|
|
338
383
|
main()
|