Metadata-Version: 2.3
Name: plumlang
Version: 0.1.6
Summary: 
Author: alandamatta
Author-email: alandamatta@live.com
Requires-Python: >=3.11
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Dist: ollama (>=0.6.2,<0.7.0)
Description-Content-Type: text/markdown

# plumlang

**plum** is a transpiler that adds a native AI operator to Python.

Write `.plum` files — Python files that can call local LLMs inline, as expressions, using the `?[...]` operator. The `plum` CLI compiles them to valid Python and runs the result.

```python
# greet.plum
prompt = "A single famous name and what they did in 5 words max"
name = ?[ prompt | llama3.2 ]

print(f"Hello, {name}")
```

```bash
$ plum greet.plum
Hello, Marie Curie discovered radioactivity pioneering nuclear science
```

---

## Why plum?

Every time you want a quick AI call in a Python script, you're writing the same setup: import the client, construct a messages array, call `.chat()`, extract `.message.content`. It's four lines of plumbing for what should be one.

plum treats the LLM call as a language primitive — the same way Python has `f"..."` for string interpolation, plum has `?[...]` for AI interpolation. No imports. No clients. No boilerplate.

```python
# Without plum
import ollama
response = ollama.chat(model="llama3.2", messages=[{"role": "user", "content": prompt}])
result = response.message.content

# With plum
result = ?[ prompt | llama3.2 ]
```

---

## Requirements

- Python 3.11+
- [Ollama](https://ollama.com) running locally with at least one model pulled

---

## Installation

```bash
pip install plumlang
```

---

## Usage

```bash
plum <file.plum> [args...]
```

### Check version

```bash
plum --version
```

---

## Syntax

### AI expression — prefix form

The core operator. Sends a prompt to a local model and returns the response as a string.

```python
result = ?["your prompt here" | model-name]
```

The prompt can be a string literal or a variable:

```python
topic = "the speed of light"
summary = ?[ f"Explain {topic} in one sentence" | llama3.2 ]
```

### Model chain — `use`

Declare a file-level fallback order at the top of the file. plum tries each model in order, falling back if one is unavailable.

```python
use llama3.2 | gemma3 | mistral

summary = ?["Explain black holes in one sentence"]
```

When a `use` declaration is present, the inline model selector is optional. When both are present, the inline selector wins for that expression.

### AI expression — postfix form

For inline use in expressions. Requires a `use` declaration.

```python
use llama3.2

emails = ["buy now!!!", "meeting at 3pm"]
spam_flags = [f"is this spam: {e}"? for e in emails]
```

### Return type annotation — `->`

Without `->`, every AI expression returns a string. Use `->` to coerce the output to a specific type.

```python
use llama3.2

decision = ?["Is the Earth flat?" | llama3.2] -> bool
is_spam  = f"is this spam: {email}"? -> bool
```

> **Note:** Conditionals without `-> bool` are almost always truthy — any non-empty string is `True` in Python, including `"no"` and `"false"`. Always annotate when using AI expressions in `if` statements.

### Syntax reference

| Syntax | Meaning |
|---|---|
| `?["prompt" \| model]` | Prefix AI expression with inline model |
| `?["prompt"]` | Prefix form; requires `use` declaration |
| `expr?` | Postfix AI expression; requires `use` declaration |
| `-> Type` | Coerce output to the given type |
| `use m1 \| m2 \| m3` | File-level model fallback chain |

---

## Use cases

- **Quick AI scripts** — grep through logs, summarize files, generate content, without wiring up a full API client
- **Data pipelines** — classify, label, or transform rows in a loop with a single expression
- **Prototyping** — sketch AI-powered features locally before integrating them into a full app
- **Learning** — experiment with local models using the simplest possible syntax

---

## Examples

### Classify items in a list

```python
use llama3.2

reviews = ["Great product!", "Completely broken.", "Works fine."]
for review in reviews:
    sentiment = ?[ f"Sentiment of this review (one word): {review}" | llama3.2 ]
    print(f"{sentiment}: {review}")
```

### Summarize a file passed as an argument

```python
import sys

content = open(sys.argv[1]).read()
summary = ?[ f"Summarize this in 3 bullet points:\n{content}" | llama3.2 ]
print(summary)
```

```bash
plum summarize.plum notes.txt
```

### Use a model chain with fallback

```python
use llama3.2 | gemma3 | mistral

answer = ?["What is the capital of France?"]
print(answer)
```

---

## How it works

plum is a transpiler. It never executes `.plum` files directly — it compiles them to plain Python and runs the result.

```
plum file.plum
      ↓
1. Validate   — file exists, .plum extension, not empty
2. Read       — load raw text
3. Scan       — find lines containing ?[ or postfix ?
4. Parse      — extract prompt, model, return type; build AST nodes
5. Resolve    — follow imports; recurse on .plum imports
6. Generate   — replace ?[...] nodes with Python API calls
7. Execute    — run generated Python, stream output
8. Cleanup    — remove temp files, return exit code
```

The generated Python is standard Ollama API calls. You can think of `.plum` files the same way you think of `.ts` files — you don't run them directly, you compile first.

### Import rules

| From | Can import `.py` | Can import `.plum` |
|---|---|---|
| `.plum` file | yes | yes |
| `.py` file | yes | no |

---

## License

See [LICENSE.txt](LICENSE.txt).

