curl-programming-lang 1.2.1__tar.gz → 1.4.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.1 → curl_programming_lang-1.4.0}/PKG-INFO +1 -1
- curl_programming_lang-1.4.0/ai_module.py +159 -0
- {curl_programming_lang-1.2.1 → curl_programming_lang-1.4.0}/curl_programming_lang.egg-info/PKG-INFO +1 -1
- {curl_programming_lang-1.2.1 → curl_programming_lang-1.4.0}/curl_programming_lang.egg-info/SOURCES.txt +1 -0
- {curl_programming_lang-1.2.1 → curl_programming_lang-1.4.0}/curl_programming_lang.egg-info/top_level.txt +1 -0
- {curl_programming_lang-1.2.1 → curl_programming_lang-1.4.0}/interpreter.py +23 -5
- {curl_programming_lang-1.2.1 → curl_programming_lang-1.4.0}/lexer.py +2 -0
- {curl_programming_lang-1.2.1 → curl_programming_lang-1.4.0}/parser.py +35 -2
- {curl_programming_lang-1.2.1 → curl_programming_lang-1.4.0}/pyproject.toml +2 -2
- {curl_programming_lang-1.2.1 → curl_programming_lang-1.4.0}/LICENSE +0 -0
- {curl_programming_lang-1.2.1 → curl_programming_lang-1.4.0}/README.md +0 -0
- {curl_programming_lang-1.2.1 → curl_programming_lang-1.4.0}/curl_programming_lang.egg-info/dependency_links.txt +0 -0
- {curl_programming_lang-1.2.1 → curl_programming_lang-1.4.0}/curl_programming_lang.egg-info/entry_points.txt +0 -0
- {curl_programming_lang-1.2.1 → curl_programming_lang-1.4.0}/errors.py +0 -0
- {curl_programming_lang-1.2.1 → curl_programming_lang-1.4.0}/main.py +0 -0
- {curl_programming_lang-1.2.1 → curl_programming_lang-1.4.0}/setup.cfg +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import urllib.request
|
|
4
|
+
import urllib.error
|
|
5
|
+
|
|
6
|
+
_DEFAULT_SYSTEM = (
|
|
7
|
+
"You are Curl-Bot, an AI assistant built into the Curl programming language. "
|
|
8
|
+
"Curl is an open-source language created by Ritvik Gautam that runs on Python. "
|
|
9
|
+
"You are helpful, concise, and friendly."
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
# Auto-compact after this many user+assistant message pairs in history
|
|
13
|
+
_COMPACT_THRESHOLD = 20
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CurlAIModule:
|
|
17
|
+
"""
|
|
18
|
+
AI standard library for Curl.
|
|
19
|
+
|
|
20
|
+
Configuration via environment variables:
|
|
21
|
+
CURL_AI_KEY or OPENAI_API_KEY — API key (not needed for local models)
|
|
22
|
+
CURL_AI_BASE_URL — base URL (default: https://api.openai.com/v1)
|
|
23
|
+
set to http://localhost:11434/v1 for Ollama
|
|
24
|
+
CURL_AI_MODEL — model name (default: gpt-4o-mini)
|
|
25
|
+
|
|
26
|
+
Usage in Curl:
|
|
27
|
+
import{"ai", ai}\
|
|
28
|
+
ai.context{"You are a pirate who only speaks in rhymes."}\
|
|
29
|
+
var{answer, ai.ask{"What is the capital of France?"}}\
|
|
30
|
+
pcType{var{answer}}\
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self):
|
|
34
|
+
self._system = _DEFAULT_SYSTEM
|
|
35
|
+
self._history = [] # list of {"role": ..., "content": ...}
|
|
36
|
+
|
|
37
|
+
def context(self, prompt):
|
|
38
|
+
"""Set a persistent system prompt (persona/context) for all subsequent AI calls."""
|
|
39
|
+
self._system = str(prompt)
|
|
40
|
+
self._history = [] # reset history when context changes
|
|
41
|
+
return ""
|
|
42
|
+
|
|
43
|
+
def reset(self, _=""):
|
|
44
|
+
"""Clear conversation history (start a fresh chat while keeping context)."""
|
|
45
|
+
self._history = []
|
|
46
|
+
return ""
|
|
47
|
+
|
|
48
|
+
def _raw_request(self, messages):
|
|
49
|
+
"""Send a messages list to the API and return the reply string."""
|
|
50
|
+
api_key = os.environ.get("CURL_AI_KEY") or os.environ.get("OPENAI_API_KEY", "")
|
|
51
|
+
base_url = os.environ.get("CURL_AI_BASE_URL", "https://api.openai.com/v1").rstrip("/")
|
|
52
|
+
model = os.environ.get("CURL_AI_MODEL", "gpt-4o-mini")
|
|
53
|
+
|
|
54
|
+
if not api_key and "openai.com" in base_url:
|
|
55
|
+
raise RuntimeError(
|
|
56
|
+
"No API key found. Set CURL_AI_KEY (or OPENAI_API_KEY) in your environment.\n"
|
|
57
|
+
"For a free local model, install Ollama (https://ollama.com), run a model, then set:\n"
|
|
58
|
+
" export CURL_AI_BASE_URL=http://localhost:11434/v1\n"
|
|
59
|
+
" export CURL_AI_MODEL=llama3.2"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
payload = json.dumps({
|
|
63
|
+
"model": model,
|
|
64
|
+
"messages": messages,
|
|
65
|
+
"temperature": 0.7,
|
|
66
|
+
}).encode()
|
|
67
|
+
|
|
68
|
+
headers = {"Content-Type": "application/json"}
|
|
69
|
+
if api_key:
|
|
70
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
71
|
+
|
|
72
|
+
req = urllib.request.Request(
|
|
73
|
+
f"{base_url}/chat/completions",
|
|
74
|
+
data=payload,
|
|
75
|
+
headers=headers,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
with urllib.request.urlopen(req, timeout=60) as resp:
|
|
80
|
+
result = json.loads(resp.read())
|
|
81
|
+
return result["choices"][0]["message"]["content"].strip()
|
|
82
|
+
except urllib.error.HTTPError as e:
|
|
83
|
+
body = e.read().decode(errors="ignore")
|
|
84
|
+
raise RuntimeError(f"AI request failed ({e.code}): {body}")
|
|
85
|
+
except urllib.error.URLError as e:
|
|
86
|
+
raise RuntimeError(f"AI connection failed: {e.reason}")
|
|
87
|
+
|
|
88
|
+
def _compact_history(self):
|
|
89
|
+
"""Summarize old history into a single assistant message to stay within limits."""
|
|
90
|
+
summary_messages = [
|
|
91
|
+
{"role": "system", "content": self._system},
|
|
92
|
+
{
|
|
93
|
+
"role": "user",
|
|
94
|
+
"content": (
|
|
95
|
+
"Summarize the following conversation history into 3-5 bullet points "
|
|
96
|
+
"so it can be used as context going forward. Be concise.\n\n"
|
|
97
|
+
+ "\n".join(
|
|
98
|
+
f"{m['role'].upper()}: {m['content']}" for m in self._history
|
|
99
|
+
)
|
|
100
|
+
),
|
|
101
|
+
},
|
|
102
|
+
]
|
|
103
|
+
summary = self._raw_request(summary_messages)
|
|
104
|
+
self._history = [{"role": "assistant", "content": f"[Previous conversation summary]\n{summary}"}]
|
|
105
|
+
|
|
106
|
+
def _request(self, prompt):
|
|
107
|
+
"""Send prompt with history, auto-compact when history is too long."""
|
|
108
|
+
if len(self._history) >= _COMPACT_THRESHOLD:
|
|
109
|
+
self._compact_history()
|
|
110
|
+
|
|
111
|
+
self._history.append({"role": "user", "content": str(prompt)})
|
|
112
|
+
|
|
113
|
+
messages = [{"role": "system", "content": self._system}] + self._history
|
|
114
|
+
reply = self._raw_request(messages)
|
|
115
|
+
|
|
116
|
+
self._history.append({"role": "assistant", "content": reply})
|
|
117
|
+
return reply
|
|
118
|
+
|
|
119
|
+
def ask(self, prompt):
|
|
120
|
+
"""Send a prompt (with conversation history) and return the response."""
|
|
121
|
+
return self._request(prompt)
|
|
122
|
+
|
|
123
|
+
def summarize(self, text):
|
|
124
|
+
"""Summarize text in 2-3 sentences (no history tracking)."""
|
|
125
|
+
messages = [
|
|
126
|
+
{"role": "system", "content": self._system},
|
|
127
|
+
{"role": "user", "content": f"Summarize the following in 2-3 sentences:\n\n{text}"},
|
|
128
|
+
]
|
|
129
|
+
return self._raw_request(messages)
|
|
130
|
+
|
|
131
|
+
def analyze(self, text):
|
|
132
|
+
"""Analyze text and return key insights (no history tracking)."""
|
|
133
|
+
messages = [
|
|
134
|
+
{"role": "system", "content": self._system},
|
|
135
|
+
{"role": "user", "content": f"Analyze the following and provide key insights:\n\n{text}"},
|
|
136
|
+
]
|
|
137
|
+
return self._raw_request(messages)
|
|
138
|
+
|
|
139
|
+
def sentiment(self, text):
|
|
140
|
+
"""Return the sentiment of text: positive, negative, or neutral (no history tracking)."""
|
|
141
|
+
messages = [
|
|
142
|
+
{"role": "system", "content": self._system},
|
|
143
|
+
{
|
|
144
|
+
"role": "user",
|
|
145
|
+
"content": (
|
|
146
|
+
"What is the sentiment of this text? "
|
|
147
|
+
"Reply with only one word: positive, negative, or neutral.\n\n" + text
|
|
148
|
+
),
|
|
149
|
+
},
|
|
150
|
+
]
|
|
151
|
+
return self._raw_request(messages)
|
|
152
|
+
|
|
153
|
+
def translate(self, text):
|
|
154
|
+
"""Translate text — include the target language in the text itself (no history tracking)."""
|
|
155
|
+
messages = [
|
|
156
|
+
{"role": "system", "content": self._system},
|
|
157
|
+
{"role": "user", "content": f"Translate the following:\n\n{text}"},
|
|
158
|
+
]
|
|
159
|
+
return self._raw_request(messages)
|
|
@@ -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';'),
|
|
@@ -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.4.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
|
|
File without changes
|