curl-programming-lang 1.2.0__tar.gz → 1.3.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.
- {curl_programming_lang-1.2.0 → curl_programming_lang-1.3.0}/PKG-INFO +1 -1
- curl_programming_lang-1.3.0/ai_module.py +83 -0
- {curl_programming_lang-1.2.0 → curl_programming_lang-1.3.0}/curl_programming_lang.egg-info/PKG-INFO +1 -1
- {curl_programming_lang-1.2.0 → curl_programming_lang-1.3.0}/curl_programming_lang.egg-info/SOURCES.txt +1 -0
- {curl_programming_lang-1.2.0 → curl_programming_lang-1.3.0}/curl_programming_lang.egg-info/top_level.txt +1 -0
- {curl_programming_lang-1.2.0 → curl_programming_lang-1.3.0}/interpreter.py +23 -5
- {curl_programming_lang-1.2.0 → curl_programming_lang-1.3.0}/lexer.py +2 -0
- {curl_programming_lang-1.2.0 → curl_programming_lang-1.3.0}/main.py +1 -1
- {curl_programming_lang-1.2.0 → curl_programming_lang-1.3.0}/parser.py +35 -2
- {curl_programming_lang-1.2.0 → curl_programming_lang-1.3.0}/pyproject.toml +2 -2
- {curl_programming_lang-1.2.0 → curl_programming_lang-1.3.0}/LICENSE +0 -0
- {curl_programming_lang-1.2.0 → curl_programming_lang-1.3.0}/README.md +0 -0
- {curl_programming_lang-1.2.0 → curl_programming_lang-1.3.0}/curl_programming_lang.egg-info/dependency_links.txt +0 -0
- {curl_programming_lang-1.2.0 → curl_programming_lang-1.3.0}/curl_programming_lang.egg-info/entry_points.txt +0 -0
- {curl_programming_lang-1.2.0 → curl_programming_lang-1.3.0}/errors.py +0 -0
- {curl_programming_lang-1.2.0 → curl_programming_lang-1.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import urllib.request
|
|
4
|
+
import urllib.error
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CurlAIModule:
|
|
8
|
+
"""
|
|
9
|
+
AI standard library for Curl.
|
|
10
|
+
|
|
11
|
+
Configuration via environment variables:
|
|
12
|
+
CURL_AI_KEY or OPENAI_API_KEY — API key (not needed for local models)
|
|
13
|
+
CURL_AI_BASE_URL — base URL (default: https://api.openai.com/v1)
|
|
14
|
+
set to http://localhost:11434/v1 for Ollama
|
|
15
|
+
CURL_AI_MODEL — model name (default: gpt-4o-mini)
|
|
16
|
+
|
|
17
|
+
Usage in Curl:
|
|
18
|
+
import{"ai", ai}\
|
|
19
|
+
var{answer, ai.ask{"What is the capital of France?"}}\
|
|
20
|
+
pcType{var{answer}}\
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def _request(self, prompt):
|
|
24
|
+
api_key = os.environ.get("CURL_AI_KEY") or os.environ.get("OPENAI_API_KEY", "")
|
|
25
|
+
base_url = os.environ.get("CURL_AI_BASE_URL", "https://api.openai.com/v1").rstrip("/")
|
|
26
|
+
model = os.environ.get("CURL_AI_MODEL", "gpt-4o-mini")
|
|
27
|
+
|
|
28
|
+
if not api_key and "openai.com" in base_url:
|
|
29
|
+
raise RuntimeError(
|
|
30
|
+
"No API key found. Set CURL_AI_KEY (or OPENAI_API_KEY) in your environment.\n"
|
|
31
|
+
"For a free local model, install Ollama (https://ollama.com), run a model, then set:\n"
|
|
32
|
+
" export CURL_AI_BASE_URL=http://localhost:11434/v1\n"
|
|
33
|
+
" export CURL_AI_MODEL=llama3.2"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
payload = json.dumps({
|
|
37
|
+
"model": model,
|
|
38
|
+
"messages": [{"role": "user", "content": str(prompt)}],
|
|
39
|
+
"temperature": 0.7,
|
|
40
|
+
}).encode()
|
|
41
|
+
|
|
42
|
+
headers = {"Content-Type": "application/json"}
|
|
43
|
+
if api_key:
|
|
44
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
45
|
+
|
|
46
|
+
req = urllib.request.Request(
|
|
47
|
+
f"{base_url}/chat/completions",
|
|
48
|
+
data=payload,
|
|
49
|
+
headers=headers,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
with urllib.request.urlopen(req, timeout=60) as resp:
|
|
54
|
+
result = json.loads(resp.read())
|
|
55
|
+
return result["choices"][0]["message"]["content"].strip()
|
|
56
|
+
except urllib.error.HTTPError as e:
|
|
57
|
+
body = e.read().decode(errors="ignore")
|
|
58
|
+
raise RuntimeError(f"AI request failed ({e.code}): {body}")
|
|
59
|
+
except urllib.error.URLError as e:
|
|
60
|
+
raise RuntimeError(f"AI connection failed: {e.reason}")
|
|
61
|
+
|
|
62
|
+
def ask(self, prompt):
|
|
63
|
+
"""Send a prompt and return the response as a string."""
|
|
64
|
+
return self._request(prompt)
|
|
65
|
+
|
|
66
|
+
def summarize(self, text):
|
|
67
|
+
"""Summarize text in 2-3 sentences."""
|
|
68
|
+
return self._request(f"Summarize the following in 2-3 sentences:\n\n{text}")
|
|
69
|
+
|
|
70
|
+
def analyze(self, text):
|
|
71
|
+
"""Analyze text and return key insights."""
|
|
72
|
+
return self._request(f"Analyze the following and provide key insights:\n\n{text}")
|
|
73
|
+
|
|
74
|
+
def sentiment(self, text):
|
|
75
|
+
"""Return the sentiment of text: positive, negative, or neutral."""
|
|
76
|
+
return self._request(
|
|
77
|
+
f"What is the sentiment of this text? "
|
|
78
|
+
f"Reply with only one word: positive, negative, or neutral.\n\n{text}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def translate(self, text):
|
|
82
|
+
"""Translate text — include the target language in the text itself."""
|
|
83
|
+
return self._request(f"Translate the following:\n\n{text}")
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import importlib
|
|
2
|
+
from ai_module import CurlAIModule
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
def execute(ast, env=None):
|
|
@@ -54,11 +55,17 @@ def _exec_stmt(stmt, env):
|
|
|
54
55
|
_exec_other(stmt["language"], stmt["code"], env)
|
|
55
56
|
|
|
56
57
|
elif t == "import":
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
if stmt["package"] == "ai":
|
|
59
|
+
env["imports"][stmt["nickname"]] = CurlAIModule()
|
|
60
|
+
else:
|
|
61
|
+
try:
|
|
62
|
+
mod = importlib.import_module(stmt["package"])
|
|
63
|
+
env["imports"][stmt["nickname"]] = mod
|
|
64
|
+
except ImportError as e:
|
|
65
|
+
raise ImportError(f"Could not import '{stmt['package']}': {e}")
|
|
66
|
+
|
|
67
|
+
elif t == "method_call":
|
|
68
|
+
_eval(stmt, env) # execute and discard return value
|
|
62
69
|
|
|
63
70
|
elif t == "ai":
|
|
64
71
|
print(f"[pcAI — mode: {stmt['mode']} | {stmt['directions']}]")
|
|
@@ -124,6 +131,17 @@ def _eval(expr, env):
|
|
|
124
131
|
_call_func(expr["name"], env)
|
|
125
132
|
return None
|
|
126
133
|
|
|
134
|
+
if t == "method_call":
|
|
135
|
+
module_name = expr["module"]
|
|
136
|
+
method_name = expr["method"]
|
|
137
|
+
arg = _eval(expr["arg"], env)
|
|
138
|
+
if module_name not in env["imports"]:
|
|
139
|
+
raise NameError(f"'{module_name}' is not imported — use import{{\"ai\", {module_name}}}\\")
|
|
140
|
+
module = env["imports"][module_name]
|
|
141
|
+
if not hasattr(module, method_name):
|
|
142
|
+
raise AttributeError(f"'{module_name}' has no method '{method_name}'")
|
|
143
|
+
return getattr(module, method_name)(arg)
|
|
144
|
+
|
|
127
145
|
raise RuntimeError(f"Unknown expression type: {t!r}")
|
|
128
146
|
|
|
129
147
|
|
|
@@ -15,6 +15,7 @@ GT = "GT"
|
|
|
15
15
|
LTE = "LTE"
|
|
16
16
|
GTE = "GTE"
|
|
17
17
|
ASSIGN = "ASSIGN"
|
|
18
|
+
DOT = "DOT"
|
|
18
19
|
LBRACE = "LBRACE"
|
|
19
20
|
RBRACE = "RBRACE"
|
|
20
21
|
SEMICOLON = "SEMICOLON"
|
|
@@ -45,6 +46,7 @@ TOKEN_PATTERNS = [
|
|
|
45
46
|
(TIMES, r'\*'),
|
|
46
47
|
(DIVIDE, r'/'),
|
|
47
48
|
(ASSIGN, r'='),
|
|
49
|
+
(DOT, r'\.'),
|
|
48
50
|
(LBRACE, r'\{'),
|
|
49
51
|
(RBRACE, r'\}'),
|
|
50
52
|
(SEMICOLON, r';'),
|
|
@@ -136,7 +136,7 @@ def repl():
|
|
|
136
136
|
pass # Windows — basic input still works, just no arrow keys
|
|
137
137
|
|
|
138
138
|
print(f"Curl {__version__} ({platform.system()}) on {sys.platform}")
|
|
139
|
-
print('Type "help", "copyright", "credits"
|
|
139
|
+
print('Type "help", "copyright", "credits" or "license" for more information.')
|
|
140
140
|
|
|
141
141
|
env = {
|
|
142
142
|
"variables": {},
|
|
@@ -2,7 +2,7 @@ from lexer import (
|
|
|
2
2
|
KEYWORD, STRING, NUMBER, IDENTIFIER,
|
|
3
3
|
PLUS, MINUS, TIMES, DIVIDE,
|
|
4
4
|
EQ, NEQ, LT, GT, LTE, GTE,
|
|
5
|
-
LBRACE, RBRACE, SEMICOLON, COMMA, COLON, ARROW,
|
|
5
|
+
LBRACE, RBRACE, SEMICOLON, COMMA, COLON, ARROW, DOT,
|
|
6
6
|
LINE_END, BLOCK_END, RAW_CODE
|
|
7
7
|
)
|
|
8
8
|
|
|
@@ -19,6 +19,12 @@ class Parser:
|
|
|
19
19
|
return self.tokens[self.pos]
|
|
20
20
|
return None
|
|
21
21
|
|
|
22
|
+
def peek(self, offset=1):
|
|
23
|
+
idx = self.pos + offset
|
|
24
|
+
if idx < len(self.tokens):
|
|
25
|
+
return self.tokens[idx]
|
|
26
|
+
return None
|
|
27
|
+
|
|
22
28
|
def consume(self, token_type, value=None):
|
|
23
29
|
token = self.current()
|
|
24
30
|
if token is None:
|
|
@@ -72,6 +78,10 @@ class Parser:
|
|
|
72
78
|
if token is None:
|
|
73
79
|
return None
|
|
74
80
|
|
|
81
|
+
# module.method{arg}\ — e.g. ai.ask{"prompt"}\
|
|
82
|
+
if token[0] == IDENTIFIER and self.peek() and self.peek()[0] == DOT:
|
|
83
|
+
return self.parse_method_call_stmt()
|
|
84
|
+
|
|
75
85
|
if token[0] != KEYWORD:
|
|
76
86
|
raise SyntaxError(f"Expected a Curl keyword, got {token[0]} {repr(token[1])}")
|
|
77
87
|
|
|
@@ -118,7 +128,7 @@ class Parser:
|
|
|
118
128
|
if self.check(COMMA):
|
|
119
129
|
# var{name, value}\ → assignment
|
|
120
130
|
self.consume(COMMA)
|
|
121
|
-
value = self.
|
|
131
|
+
value = self.parse_concat_expr()
|
|
122
132
|
self.consume(RBRACE)
|
|
123
133
|
self.consume(LINE_END)
|
|
124
134
|
return {"type": "assign", "name": name, "value": value}
|
|
@@ -197,6 +207,16 @@ class Parser:
|
|
|
197
207
|
self.consume(LINE_END)
|
|
198
208
|
return {"type": "other_code", "language": lang, "code": code}
|
|
199
209
|
|
|
210
|
+
def parse_method_call_stmt(self):
|
|
211
|
+
module = self.consume(IDENTIFIER)[1]
|
|
212
|
+
self.consume(DOT)
|
|
213
|
+
method = self.consume(IDENTIFIER)[1]
|
|
214
|
+
self.consume(LBRACE)
|
|
215
|
+
arg = self.parse_concat_expr()
|
|
216
|
+
self.consume(RBRACE)
|
|
217
|
+
self.consume(LINE_END)
|
|
218
|
+
return {"type": "method_call", "module": module, "method": method, "arg": arg}
|
|
219
|
+
|
|
200
220
|
def parse_import(self):
|
|
201
221
|
self.consume(KEYWORD, "import")
|
|
202
222
|
self.consume(LBRACE)
|
|
@@ -286,6 +306,19 @@ class Parser:
|
|
|
286
306
|
self.consume(RBRACE)
|
|
287
307
|
return {"type": "func_call_expr", "name": name}
|
|
288
308
|
|
|
309
|
+
# module.method{arg} — e.g. ai.ask{"prompt"}
|
|
310
|
+
if token[0] == IDENTIFIER:
|
|
311
|
+
module = token[1]
|
|
312
|
+
self.pos += 1
|
|
313
|
+
if self.check(DOT):
|
|
314
|
+
self.consume(DOT)
|
|
315
|
+
method = self.consume(IDENTIFIER)[1]
|
|
316
|
+
self.consume(LBRACE)
|
|
317
|
+
arg = self.parse_concat_expr()
|
|
318
|
+
self.consume(RBRACE)
|
|
319
|
+
return {"type": "method_call", "module": module, "method": method, "arg": arg}
|
|
320
|
+
return {"type": "var_ref", "name": module}
|
|
321
|
+
|
|
289
322
|
raise SyntaxError(f"Unexpected token in expression: {token[0]} {repr(token[1])}")
|
|
290
323
|
|
|
291
324
|
def _parse_list_body(self):
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "curl-programming-lang"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.3.0"
|
|
8
8
|
description = "Curl is an open-source programming language built on Python technology"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.7"
|
|
@@ -29,4 +29,4 @@ Repository = "https://github.com/gautamritvik/Curl-Programming"
|
|
|
29
29
|
curlang = "main:main"
|
|
30
30
|
|
|
31
31
|
[tool.setuptools]
|
|
32
|
-
py-modules = ["main", "lexer", "parser", "interpreter", "errors"]
|
|
32
|
+
py-modules = ["main", "lexer", "parser", "interpreter", "errors", "ai_module"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|