Metadata-Version: 2.4
Name: chemql-python
Version: 1.6.10
Summary: A chemistry-focused query language and REPL
Author-email: Mohammad Rakibul Islam <rakibulofficial1017@gmail.com>
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: prompt_toolkit>=3.0
Requires-Dist: rich>=13.7
Provides-Extra: test
Requires-Dist: pytest>=8.0; extra == "test"
Dynamic: license-file

# ChemQL & CodecQL

> A Python-embedded query language and terminal toolchain for chemistry data — with a purpose-built editor, package manager, and standard library.

ChemQL is a small language for querying, computing with, and simulating chemistry data (elements, molecules, reactions), layered on a Python runtime. It ships with an interactive REPL, a terminal editor (CodecQL), a desktop editor (CodecQLX), a type system (structs, records, enums), namespaces, modules, a macro/preprocessor system, a package manager, and a standard library.

- **Distribution name:** `chemql-python`
- **Import / CLI name:** `chemql`
- **Language version:** `1.6.10` (per `chemql.__version__`)
- **Editor version:** `codecql 0.1.1`
- **GUI Editor version:** `CodecQLX 1.1.0`
- **Python import / CLI name:** `chemql`
- **Author:** Mohammad Rakibul Islam
- **Minimum Python:** **3.12+** (verified with `vermin`)
- **Registry:** `https://chemqlreg.pythonanywhere.com/`

> ⚠️ **Safety:** ChemQL executes embedded Python (`[ ... ]`, `[[[ ... ]]]`) and real shell commands (`` ` ... ` ``, ` ``` ` blocks) with the permissions of the running process. Only run code and packages you trust.

---

## Install

```bash
pip install chemql-python
```

Verify:

```bash
chemql --version
```

If the `chemql` command is unavailable or fails to start, use
`python -m chemql` with the same arguments instead:

```bash
python -m chemql --version
python -m chemql -c 'search elements symbol = "Fe" return name symbol number'
python -m chemql -f script.cql
```

Python **3.12+** is required. The project's minimum version is checked with [`vermin`](https://github.com/netromdk/vermin).

---

## Quick start

```text
$ chemql
Type help or ? for help
>>> search elements name like "Hydrogen"
H (Hydrogen)
>>> search molecules molecular_weight < 20 sort molecular_weight
H₂ (Hydrogen)
>>> balance H2 + O2 -> H2O
2 H₂ + O₂ → 2 H₂O
>>> let water = {{ findmol "Water" }}
>>> water.smiles
O
>>> findmol "caffeine" return name formula molecular_weight
```

Non-interactive:

```bash
chemql -c 'search elements symbol = "Fe" return name symbol number'
chemql -f script.cql
chemql -f script.cql -o out.txt       # ANSI-stripped output to a file
chemql --no-ansi
chemql --suppress-warnings
```

Open a file in the bundled editor:

```bash
chemql edit script.cql
```

---

## Core commands

| Command | Purpose |
|---|---|
| `search [first\|last\|all] <source> <conditions> [ops] [return fields]` | Query a data source |
| `source <elements\|molecules\|reactions>` | Set the default source |
| `findel <name-or-symbol> [return …]` | Element lookup |
| `findmol <name-or-formula> [return …]` | Molecule lookup (local → PubChem → ChemSpider → NIST) |
| `findre <name-or-id> [return …]` | Reaction lookup (local → Rhea → Chempirical) |
| `balance <reactants> ->\|<-> <products>` | Balance a chemical equation |
| `react <coefficients><molecules…>` | Match a stored reaction under current conditions |
| `set temperature\|pressure\|catalysts <value>` | Set reaction conditions |
| `add\|remove catalyst "<name>"` | Modify the catalyst list |
| `conditions` | Show current T / P / catalysts |
| `let <name> = <expr>` / `unset <name>` / `variables` | Bind, remove, list variables |
| `define NAME <value>` / `@NAME` | Object-like macro |
| `define NAME{} … enddefine` / `@NAME{…}` | Function-like macro |
| `definitions` / `macros` / `remove definition\|macro NAME` | Inspect / remove |
| `func NAME … endfunc` | Define a function |
| `static func Type.method … endfunc` | Static method |
| `struct` / `record` / `enum` … `end*` | Define types |
| `namespace NAME … endnamespace` | Group declarations with visibility |
| `load <pkg> [as <alias>]` / `load <member> from <pkg> [as <alias>]` | Load packages |
| `help [topic]` / `?` | Help |
| `dump [all\|good\|bad] [queries\|results] FILE` | Export execution history |
| `view` | Open the periodic-table viewer in a browser |
| `clear` / `version` / `exit` | Misc |

---

## Query syntax

**Sources:** `elements`, `molecules`, `reactions`.

**Operators:**

| Operator | Meaning |
|---|---|
| `=` `!=` `>` `<` `>=` `<=` | Comparison (type-converted against the field) |
| `like` | Case-insensitive wildcard match |
| `like!` | Case-sensitive wildcard match |
| `likep` | Regex match |
| `has` | Nested containment (exact for `molecule` fields) |
| `and` `or` `not` `( )` | Boolean logic |

**`like` wildcard language** (not SQL):

| Token | Matches |
|---|---|
| `*` | Any characters |
| `%` | Digits only |
| `&` | Letters only |
| `_` | Any single character |
| `#` | A single digit |
| `?` | A single letter |
| `\` | Escape |

**Result operations:** `sort FIELD [asc|desc]`, `limit N`, `count`, `return FIELD …`, and set ops `union` / `intersection`.

Examples:

```chemql
search elements number > 10 and number < 20 return name symbol
search molecules molecular_weight < 50 sort molecular_weight limit 10
search reactions reversible = true return name equation source
search elements name like "H*"
```

---

## Chemistry features

### Data

Bundled JSON under `chemql/data/`:

| File | Purpose |
|---|---|
| `PeriodicTableJSON.json` | Element metadata (number, symbol, mass, category, phase, …) |
| `Molecules.json` | Molecule records: formula, IUPAC name, SMILES, CID, mp/bp/density/state |
| `Reactions.json` | Curated reactions, plus Rhea imports (see provenance) |
| `MoleculeBonds.json` | Bond connectivity per molecule |
| `BondEnergies.json` | Bond energy table used for ΔH estimates |

Reference conditions are °C at 1 atm (mp/bp), g/cm³ at 25 °C (density), and `state` at 25 °C / 1 atm.

### External lookups

`findmol` and `findre` fall back to public APIs when local data misses:

- **Molecules:** PubChem → ChemSpider (needs `CHEMSPIDER_API_KEY`) → NIST WebBook
- **Reactions:** Rhea → Chempirical

`search` itself stays **local** (offline-safe).

### Balancing

`balance` builds an element × species matrix and solves it with exact `Fraction` Gaussian elimination, returning smallest-integer coefficients. It fails clearly when the balance is not unique.

### Reaction simulation

`react` matches the supplied reactants (forward, or reverse for reversible reactions) against `REACTIONS`, gated by current conditions:

- Temperature tolerance: **±5 K**
- Pressure tolerance: **±5 %**
- Catalysts: required set must be a **subset** of available

### Chemistry built-ins

| Built-in | Description |
|---|---|
| `element{symbol, field?}` | Periodic-table lookup |
| `count_atoms{formula, symbol}` | Atoms of one element in a formula (handles nested parentheses) |
| `molar_mass{formula}` | Molar mass |
| `empirical_formula{formula}` | Reduced formula |

---

## Type system

### Structs — mutable, methods, inheritance

```chemql
struct Fruit
    name!
    weight = 1.0
endstruct

struct Apple from Fruit
    appletype
endstruct

func Fruit.talk
    if this.weight is not None
        return "I am a " + this.name
    endif
    return "I am a " + this.name
endfunc
```

- `name!` or `required name` — required field
- `name = value` — optional with default (`None` if omitted)
- `required` / `!` **cannot** be combined with `=` (rejected at `endstruct`)
- Field ordering is normalized automatically
- Subclass field redeclaration overrides or strips the default
- `func Type.method` — instance method (`this`); `static func` — static
- Structs can inherit from **structs, records, or Python classes**

### Records — immutable, positional, trailing defaults

```chemql
record Point
    x
    y
    z = 0
endrecord
```

- Every field is required unless it has a default
- Defaulted fields must be **trailing** (rejected otherwise)
- Instances are **immutable** (`can't set attribute`)
- Elementwise operator overloading (`+ - * / // % **`, unary `-`, `abs`)
- Records inherit **only** from records
- No in-language methods (hybrid-Python only)

### Enums — named singletons with payloads

```chemql
enum Level
    low = 25
    mid = 50
    high = 75
endenum
```

- Auto-numbering starts at `1` when `=` is omitted
- `Level::low::value` → `25`; `Level::low::name` → `"low"`
- Equality is **identity-based** (`Level::low == 25` is `False`)
- Members cannot be reassigned
- Enums inherit from enums; **no methods**

### Summary

| | mutable | defaults | methods | inheritance |
|---|---|---|---|---|
| **struct** | yes | required / optional / defaulted | yes (incl. static) | structs, records, Python classes |
| **record** | no | trailing only | no (hybrid only) | records only |
| **enum** | no | auto-increment | no | enums only |

---

## Namespaces & visibility

```chemql
namespace foo
    public let x = 10
    private let y = 20
    let z = 30          // private by default
endnamespace
```

- **Default visibility is private** for namespace members
- `public` / `private` applies to `let`, `func`, `struct`, `record`, `enum`, `namespace`
- Reading a private member raises an error mentioning it is set to private
- Code inside the namespace can access its own private members
- Missing members produce a distinct "no attribute" error

---

## Modules

`.cql` files can be loaded as packages:

```chemql
// foo.cql
public func isEven
    return \1 % 2 == 0
endfunc
```

```chemql
// bar.cql
load foo
foo::isEven{3}

load isEven from foo
isEven{4}
```

- `load pkg` → `pkg::name` access; only `public` members
- `load member from pkg [as alias]` → import one member
- Cross-module private access raises `PermissionError`
- Packages live in `./`, `~/.chemql/packages/<name>/<version>/`, or `chemql/stdlib/`
- Override root with `CHEMQL_PACKAGE_DIR`

---

## Functions, arguments, macros

```chemql
func square
    return \1 * \1
endfunc
```

**Two access forms:**

- `\1`, `\2`, … — 1-indexed positional placeholders, **substituted textually** (works inside strings; `\10` = tenth argument, greedy `\d+`)
- `arguments` — a per-call `list` of positional arguments, `arguments[0]` indexed from 0, **function scope only**

> ⚠️ `arguments` is not available in macro bodies — macros get `\N` only.

### Definitions (object-like)

```chemql
define MAX 9
@MAX * 9          // 81
print("2@MAX")    // "29"
```

- `@NAME` expands textually, including inside strings
- One name slot shared with macros; **definitions win** on collision
- `remove definition NAME` (or `remove definition @NAME`)

### Macros (function-like)

```chemql
define MAX{}
\1 if \1 > \2 else \2
enddefine

@MAX{1, 3}        // 3
```

- Body uses `\N`; `arguments` is **not** available
- Macros may expand other macros (multi-pass)
- `remove macro NAME`

> **Gotcha:** defining `NAME{}` while `NAME = value` exists makes the macro unreachable until `remove definition NAME`.

---

## Control flow

```chemql
if x > 10
    let y = "big"
elif x > 5
    let y = "medium"
else
    let y = "small"
endif

for i in range(3)
    print(i)
endfor

while i < 5
    let i = i + 1
endwhile
```

- `break` / `continue` / `skip` (alias for continue)
- Bracketed Python-style blocks also accepted: `[for …] … [endblock]`

---

## Error handling

```chemql
risk
    signal "Something went wrong"
fix
    let handled = error
cleanup
    let finished = true
endrisk
```

- `signal "msg"` raises a recoverable ChemQL signal
- `warn "msg"` sets `warnings` and prints (suppress with `--suppress-warnings`)
- `error` holds the last error type (or `None` after success)

---

## Python integration

Inline expression:

```chemql
[1 + 2 * 3]              // 7
[x * 2]
```

Multiline Python block:

```chemql
[[[
for x in range(3):
    print(x)
]]]
```

Embedded queries inside Python:

```chemql
let m = [len({{ search molecules molecular_weight < 20 }})]
```

Attribute access, `{{ }}` queries, and `::` namespace paths are rewritten before evaluation.

> **Safety reminder:** all of the above runs with your user's permissions.

---

## Shell integration

Inline:

```chemql
`ls -la`
```

Multiline:

````
```bash
cd /tmp
ls
```
````

- **POSIX:** persistent Bash via a PTY with a unique prompt handshake
- **Windows:** persistent PowerShell request/marker loop
- `clear` / `cls` bypass the shell

---

## CodecQL — the terminal editor

**A small terminal editor built for ChemQL.** Syntax highlighting, inline suggestions, a built-in terminal pane, and one-key execution — nothing to configure to get started.

| | |
|---|---|
| Version | 0.1.1 |
| Ships with | `chemql-python` |
| Files | `.cql` (ChemQL), `.py`, `cqlpackage.json` / `cqlpackage.toml`, and any plain-text file |

### Starting CodecQL

```bash
chemql edit FILE                       # open (or create) a file
python -m chemql.codecql FILE          # equivalent
chemql edit --main-highlighting        # edit the highlighting.json shipped with the package
chemql edit --local-highlighting       # edit ./highlighting.json (created as {} if missing)
chemql edit --version
```

If the `codecql` command is unavailable or fails to start, run
`python -m chemql.codecql FILE` instead. It accepts the same options as
`chemql edit`, including the highlighting configuration options.

Opening a path that does not exist starts an empty buffer; it is created on the first save.

**Requirements:** the same as ChemQL (Python 3.12+), plus a terminal that understands ANSI escape sequences and 256 colours. On Windows, `colorama` is used when installed; otherwise virtual-terminal mode is enabled directly. Copying to the system clipboard needs one of `clip` (Windows), `clip.exe` (WSL), `pbcopy` (macOS), or `wl-copy` / `xclip` / `xsel` (Linux).

### Key bindings

| Key | Action |
|---|---|
| `Ctrl+S` | Save |
| `Ctrl+Q` | Quit. With unsaved changes, press it a second time to quit without saving |
| `Ctrl+Z` / `Ctrl+Y` | Undo / redo (last 200 edits) |
| `Ctrl+F` | Search |
| `Ctrl+R` | Save, then run the file |
| `Ctrl+T` | Open a terminal pane |
| `Tab` | Accept the inline suggestion; with none showing, insert a tab |
| `Enter` | New line, keeping the current indentation |
| Arrow keys, `Home`, `End` | Move |
| `Shift` + movement | Select |
| `Ctrl`/`Alt` + `←` `→` | Move by word |
| `Ctrl` + `↑` `↓` | Scroll the view without moving the text |
| `Backspace`, `Delete` | Delete a character, or the selection |
| `Ctrl+Backspace` / `Alt+Backspace` | Delete the word to the left |
| `Ctrl+Delete` / `Alt+Delete` | Delete the word to the right |
| `Ctrl+C` / `Ctrl+Insert` | Copy the selection (editor + system clipboard when available). It does **not** quit |
| `Ctrl+X` | Cut the selection |
| `Ctrl+V` / `Shift+Insert` | Paste the editor's clipboard |

Pasting from your terminal or OS clipboard also works: CodecQL enables bracketed paste, so multi-line pastes arrive as a single insertion.

**Auto-indent.** After a line that opens a block (`func`, `define NAME{}`, `struct`, `record`, `enum`, `namespace`, `if`, `elif`, `else`, `for`, `while`) or ends in a `\` continuation, the next line is indented with a tab. Pressing Enter after `endnamespace` removes one level of indentation. Other `end...` keywords do not dedent automatically.

**Status bar and footer.** The bar shows the file name, `Ln`/`Col`, the line count, `Unsaved` when the buffer is dirty, and search status. The footer shows messages and a reminder of the main shortcuts.

### Search

Press `Ctrl+F` and type; matches are highlighted as you type.

| Key | Action |
|---|---|
| `Enter`, `↓`, `→` | Next match |
| `↑`, `←` | Previous match |
| `Esc`, `Ctrl+C` | Close search |

A plain query is a **literal, case-insensitive** search. Add a flag prefix followed by `:` to change that:

| Flag | Meaning |
|---|---|
| `r` | Treat the query as a regular expression |
| `l` | Treat the query as literal text (the default; cannot be combined with `r`) |
| `w` | Match whole words only |
| `i` | Ignore case |

Once any flag is given, matching becomes case-sensitive unless `i` is included.

```text
water            literal, case-insensitive
l:Water          literal, case-sensitive
r:^search\s      regular expression, case-sensitive
ri:^search\s     regular expression, case-insensitive
wi:set           whole word "set", case-insensitive
```

An invalid regular expression is reported in the status bar.

### Running code and the terminal pane

**`Ctrl+R`** saves the file (if it has unsaved changes) and runs the command configured for its extension. The default for `.cql` is:

```text
chemql -f $absoluteName
```

`$absoluteName` is replaced by the shell-quoted absolute path. Set your own with `run-command` in `highlighting.json`; the bundled configuration also runs `.py` files with `python -u` and `.rs` files with `cargo run`. The file must have a name before it can be run.

**`Ctrl+T`** opens a plain terminal without running anything.

| Platform | Behaviour |
|---|---|
| Linux, macOS, WSL | A pane opens in the bottom third of the screen running your `$SHELL` (`-i`). `Ctrl+↑` / `Ctrl+↓` resize it. `Ctrl+T`, `Ctrl+Q` or `Esc` closes it and returns to the editor. |
| Windows Terminal | The command runs in a new split pane beside the editor. Close it by exiting the shell. |
| Other Windows consoles | The command runs in a separate console window. |

### Syntax highlighting

What is coloured depends on the file:

| File | Highlighting |
|---|---|
| `.cql` | Comments (`//`, `#`), strings, numbers (including units such as `25C`, `1atm`), chemical formulas (`H2O`, `NaCl`), operators, delimiters (`{{ }}`, `[[[ ]]]`, brackets), commands, control keywords, word operators (`and`, `like`, `sort`, ...), sources, reaction conditions, field names, Python built-ins, `True`/`False`/`None`, and the name after `enum`/`struct`/`record`/`namespace` |
| `.py` | Python keywords, built-ins and constants, plus comments, strings, numbers and operators |
| `cqlpackage.json`, `cqlpackage.toml` | Keys, strings, numbers, booleans, TOML tables and comments |
| anything else | Comments, strings, numbers, operators and delimiters only |

Highlighting is purely lexical and line-based: it never changes the file. Extensions are matched case-insensitively.

### Inline suggestions

While you type at the **end of a line**, CodecQL shows a grey "ghost" completion. Press `Tab` to accept it. Suggestions are context-aware: they depend on what comes before the cursor on the line (and, for queries, on the most recent `source` command above it).

```text
se|                              ->  search
search |                         ->  search elements
search elements na|              ->  search elements name
search elements atomic_mass |    ->  search elements atomic_mass >
search elements phase = |        ->  search elements phase = "Gas"
```

Suggestions are driven entirely by the `suggestions` rules in `highlighting.json`, so you can change or extend them without touching Python. They are only offered when the cursor is at the end of the line and nothing is selected.

### Configuration: `highlighting.json`

CodecQL loads configuration from two places, in this order:

1. `highlighting.json` in the package directory (`chemql edit --main-highlighting` opens it)
2. `highlighting.json` in the current working directory (`chemql edit --local-highlighting` opens it)

> **Each extension's profile is replaced as a whole.** If the local file defines `".cql"`, it completely replaces the main file's `.cql` profile, including its `run-command` and `suggestions`. Copy what you want to keep.

#### Profile format

The top level maps file extensions to profiles. A leading dot is added if you omit it, and extensions are lower-cased.

```json
{
    ".cql": {
        "run-command": "chemql -f $absoluteName",
        "rules": [],
        "suggestions": []
    },
    ".py": {
        "run-command": "python -u $absoluteName",
        "rules": []
    }
}
```

| Field | Description |
|---|---|
| `run-command` | Command run by `Ctrl+R`. `$absoluteName` is replaced by the quoted path. |
| `rules` | Highlighting rules (below). |
| `suggestions` | Suggestion rules (below). |

An older format, where an extension maps directly to a list of rules, is still accepted. Unknown profile fields and duplicate extensions are rejected, and a configuration error is reported when the file is loaded.

#### Highlighting rules

```json
{
    "name": "my-keywords",
    "type": "keywords",
    "values": ["alpha", "beta"],
    "style": "keyword"
}
```

| Field | Description |
|---|---|
| `name` | Non-empty label |
| `type` | `keywords` or `regex` |
| `values` | For `keywords`: a non-empty list of words. Matching is case-insensitive |
| `pattern`, `case_sensitive` | For `regex`: a valid regular expression, and whether it is case-sensitive (default `true`) |
| `style` | A style string, below |

Rules are checked in order and the first matching keyword wins. A keyword rule takes priority over CodecQL's built-in categories for that extension, so with an empty `rules` list for `.cql` the built-in ChemQL highlighting applies unchanged.

> ⚠️ Currently only `keywords` rules affect how text is drawn. `regex` rules are validated but **not yet applied**.

**Style strings** combine space-separated parts:

| Part | Effect |
|---|---|
| `keyword` `command` `operator` `string` `comment` `number` `constant` `type` `source` `condition` `property` `builtin` | A colour from the current theme |
| `bold` `dim` `italic` `underline` `reverse` `strike` | Text attributes |
| `#rrggbb` | 24-bit foreground colour |
| `bg:#rrggbb` | 24-bit background colour |

```json
"style": "bold #ff8800"
```

An unknown part is an error, so stick to this list.

#### Example: highlighting a new language

```json
{
    ".toml": {
        "run-command": "cat $absoluteName",
        "rules": [
            {"name": "booleans", "type": "keywords", "values": ["true", "false"], "style": "constant"}
        ]
    }
}
```

### Suggestion rules in depth

A suggestion rule says: *when the text before the cursor looks like this, offer these completions.*

```json
{
    "name": "second-word",
    "pattern": "(?:^|;|\\{\\{)\\s*search\\s+$",
    "min-prefix": 0,
    "suggestions": ["elements", "molecules", "reactions"]
}
```

| Field | Description |
|---|---|
| `name` | Unique, non-empty. Used by `$name$` references |
| `pattern` | Regular expression, searched **case-insensitively** in the *context* (below) |
| `suggestions` | Non-empty list of completions |
| `min-prefix` | Minimum number of characters already typed in the current word before the rule applies (default `0`) |

#### How a rule is chosen

1. The line up to the cursor is split into a **prefix** (the run of non-whitespace characters just before the cursor) and a **context** (everything before that prefix, including whitespace).
2. Rules are tried **in order**. The **first** rule whose pattern matches the context wins, and no later rule is consulted, even if the winner offers nothing for the current prefix.
3. From the winner's `suggestions`, the first entry that starts with the prefix (case-insensitively) and is longer than it is chosen. The untyped remainder is shown as ghost text, and `Tab` inserts it.

Practical consequences:

- **Order matters. Put specific rules before general ones.** A broad rule placed early will shadow everything after it.
- **Anchor with `$`.** To mean "the cursor follows this word", end the pattern with `\s+$`. The context always ends with the whitespace before the prefix.
- **Prefixes are whitespace-delimited.** In `help{ab`, the prefix is the whole `help{ab`, so completions after punctuation without a space are not possible. Design completions around spaces.
- **A suggestion may contain spaces.** `"{{ search elements"` is offered once the prefix is `{`, and it is inserted as a whole.
- **Use `min-prefix: 1` for free-form positions**, so a ghost does not appear (and `Tab` still inserts a tab) before you have typed anything.
- **Remember JSON escaping.** Every regex backslash is doubled: `\s` is written `"\\s"`.

#### References

| Syntax | In a `pattern` | In a `suggestions` entry |
|---|---|---|
| `$rule-name$` | Replaced by an alternation of that rule's suggestions, e.g. `(?:search\|source\|...)` | Replaced by all of that rule's suggestions |
| `$active-source-fields$` | *(not supported)* | Field names of the active source: the source in the current `search` statement if it names one, otherwise the most recent `source` command above the line. Empty if there is none |
| `$query-start-options$` | *(not supported)* | The active source's fields, or the three sources when there is no active source |
| `$query-operators$` | *(not supported)* | `= != > < >= <= like like! likep has and or sort limit count return first last` |

A `$rule-name$` reference must point at a rule defined **earlier** in the list, which also rules out cycles. References are expanded one level: a referenced rule's own `$...$` entries are not expanded further.

#### Reference-only rules

A rule whose pattern is `(?!)` can never match, so it never fires. The bundled configuration uses this to hold named lists, such as all field names or all operators, which later rules splice into their patterns and suggestions:

```json
{ "name": "operators", "pattern": "(?!)", "min-prefix": 0,
  "suggestions": ["=", "!=", ">", "<", ">=", "<=", "like", "like!", "likep", "has"] }
```

A later rule can then recognise "a field name followed by an operator" with `\\s(?:$all-fields$)\\s+(?:$operators$)\\s+$`.

#### Testing a rule

The engine is plain Python, so you can try rules without opening the editor:

```python
from chemql.highlighting import HighlightProfiles

profiles = HighlightProfiles("highlighting.json")
line = "search elements atomic_mass "
suffix = profiles.get_suggestion_suffix("x.cql", line, len(line), previous_lines=[])
print(line + suffix)          # search elements atomic_mass >
```

`previous_lines` supplies the lines above, which is how the active `source` is found.

### What the bundled rules complete

The shipped `highlighting.json` defines about 70 rules for `.cql`. In summary:

| Context | Suggests |
|---|---|
| Start of a line, or after `;` (after 1 character) | Every ChemQL command and block keyword |
| After `{{` | Query commands (`search`, `findel`, `findmol`, `findre`, ...) |
| `search ` | Sources, `first`/`last`/`all`, and the active source's fields |
| `search SOURCE ` | That source's fields, plus `not`, `sort`, `limit`, `count` |
| After a field | Operators that suit its type: numeric (`>` `<` `=` ...), text (`like` `=` `has` ...), collection (`has`), boolean (`=` `!=`) |
| After `not` | Operators, or fields when negating a whole condition |
| After an operator | Known values for `phase`, `category`, `block`, `state` and `reversible` |
| After a complete condition | `and`, `or`, `sort`, `limit`, `count`, `return`, `union`, `intersection` |
| After `and`, `or`, `not`, `(` | Fields |
| After `sort`, `limit` | Fields, `asc`/`desc`, then `limit`/`count`/`return`; common limits |
| After `return` | Fields |
| After `union` / `intersection` | `search elements` / `molecules` / `reactions` |
| `findel`, `findmol`, `findre` | `return`, then that source's fields |
| `balance` | `+`, `->`, `<->` as appropriate |
| `set`, `add`, `remove` | Settings, `catalyst`, common temperature, pressure and catalyst values |
| `source`, `list`, `dump`, `help`, `load`, `install` | Their arguments, including standard-library modules and built-in names |
| `let` | `=`, then query, built-in and constant starters |
| `public`, `private`, `static`, `struct`, `record` | Declaration keywords, `from` |
| `for` | `in`, then `range(` or a query |

Value suggestions for `phase`, `category` and `block` reflect the standard periodic-table dataset. If your data uses different spellings, adjust the `value-...` rules.

### Colours: `colors.json`

CodecQL reads its theme from `colors.json` next to `codecql.py`. If the file is missing, a built-in palette is used.

```json
{
    "syntax": {
        "comment":   {"ansi256": 244},
        "string":    {"ansi256": 114},
        "number":    {"ansi256": 179},
        "formula":   {"ansi256": 117},
        "operator":  {"ansi256": 213},
        "delimiter": {"ansi256": 250},
        "command":   {"ansi256": 81},
        "keyword":   {"ansi256": 141},
        "constant":  {"ansi256": 215},
        "type":      {"ansi256": 150},
        "source":    {"ansi256": 110},
        "condition": {"ansi256": 180},
        "property":  {"ansi256": 116},
        "builtin":   {"ansi256": 222}
    },
    "ui": {
        "line_numbers":      {"ansi256": 244},
        "status_background": {"ansi256": 24, "channel": "background"},
        "status_foreground": {"ansi256": 255}
    }
}
```

Each entry needs an `ansi256` number from 0 to 255, and an optional `channel` of `foreground` (default) or `background`. If you provide the file, include **every** category shown above.

### CodecQL known limitations

- Files are saved with `\n` line endings and **no trailing newline**.
- Inline suggestions appear only when the cursor is at the end of the line.
- `regex` highlighting rules are validated but **not yet applied**.
- Auto-dedent applies only after `endnamespace`.
- One file per session: there are no tabs or split editors.
- Tabs are drawn four columns wide.
- CodecQL's Windows Backspace / Ctrl+Backspace are intentionally inverted relative to POSIX.

## CodecQLX — the desktop editor

**CodecQLX** is the Tkinter desktop editor for ChemQL. It complements the
terminal-based CodecQL with a multi-file workspace, graphical diagnostics,
ChemQL-aware tools, and configurable themes.

### Starting CodecQLX

```bash
codecqlx FILE_OR_FOLDER ...
python -m chemql.codecqlx FILE_OR_FOLDER ...
codecqlx --version
```

If the `codecqlx` command is unavailable or fails to start, use
`python -m chemql.codecqlx` with the same arguments:

```bash
python -m chemql.codecqlx FILE_OR_FOLDER ...
python -m chemql.codecqlx --version
```

Paths are optional. A new empty file is opened when no path is supplied;
files and folders passed on the command line are opened in the editor. The
`codecqlx` command is installed with `chemql-python`.

CodecQLX uses Python's standard-library Tkinter GUI toolkit. On Linux, install
the platform Tk package if it is not already available (for example,
`python3-tk` on Debian/Ubuntu). The optional ChemQL panels are disabled when
the `chemql` package cannot be imported.

### Features

- Syntax highlighting and context-aware autocomplete for `.cql` files
- Hoverable diagnostics, gutter markers, a Problems panel, and an overview ruler
- File tree sidebar, recent files, and multiple editor tabs
- Side-by-side editor panes for working with different files
- Query Console that executes ChemQL in-process and displays table or card results
- Interactive Periodic Table lookup
- Balance Equation panel
- Find and replace, bookmarks, zoom controls, and run-file/run-selection actions
- Dark and light themes, font settings, autocomplete/minimap toggles, and
  per-token color preferences

Use **View** to toggle the file tree, Chemistry panel, and bottom panels.
Enable **View > Split Editor**, then choose **File > Open File in Other Pane**
to work with a second file side by side. Close all tabs in the second pane
before turning split view off.
CodecQLX stores its preferences in `.codecql_gui/settings.json` under the
user profile (`%APPDATA%` on Windows).

### Keyboard shortcuts

| Key | Action |
|---|---|
| `Ctrl+N` / `Ctrl+O` | New file / open file |
| `Ctrl+Shift+O` | Open folder |
| `Ctrl+S` / `Ctrl+Shift+S` | Save / save as |
| `Ctrl+W` | Close tab |
| `Ctrl+F` | Find and replace |
| `Ctrl+R` / `Ctrl+Shift+R` | Run file / run selection |
| `Tab` / `Enter` | Accept the selected autocomplete suggestion |
| `Up` / `Down` / `Esc` | Navigate or dismiss autocomplete |
| `Ctrl+F2` / `F2` / `Shift+F2` | Toggle / next / previous bookmark |
| `Ctrl+=` / `Ctrl+-` / `Ctrl+0` | Zoom in / out / reset |

---

## Standard library

Load with `load <pkg>`; call as `<pkg>::name{…}`.

| Package | Purpose |
|---|---|
| `math` | Constants, trigonometry, logarithms, number theory |
| `math::statistics` | Mean, median, mode, variance, standard deviation, quantiles |
| `text` | String transforms, splitting/joining, regex helpers |
| `collections` | Uniqueness, flattening, pairing, frequency counts, product |
| `datetime` | Dates, timestamps, local/UTC time, date arithmetic |
| `json` | JSON encode/decode helpers |
| `random` | Pseudo-random numbers and sequence sampling |
| `decimal` | Base-10 decimal arithmetic and quantization |
| `fractions` | Exact rational numbers and fraction arithmetic |
| `pathlib` | Path inspection and UTF-8 text file operations |

`help{stdlib}` lists them; `help{math}` and `help{math::sqrt}` document each.

---

## Built-in functions

General: `abs round min max sum len type str int float bool upper lower contains replace split join first last unique sort reversed read_text read_lines read_json read_csv write_text write_csv write_json file_exists list_files help`

Chemistry: `element count_atoms molar_mass empirical_formula`

`help` with a topic is documented from `_BUILTIN_HELP`.

---

## Package manager

```bash
chemql install <name> [--version V]
chemql upgrade <name> [--version V]
chemql uninstall <name>
chemql list
chemql search <query>
chemql info <name>
chemql build            # requires cqlpackage.json / cqlpackage.toml
chemql upload           # publish to the registry
chemql init [name] [-d dir]
```

Inside the REPL: `install <name> [--version V]`.

Package layout:

```
~/.chemql/packages/<name>/<version>/
```

## License and attribution

[MIT License](https://github.com/rakibulofficial1017/ChemQL/blob/main/LICENSE)

Created by [Mohammad Rakibul Islam](https://github.com/rakibulofficial1017).
Source code: [rakibulofficial1017/ChemQL](https://github.com/rakibulofficial1017/ChemQL).

------------------------------------------------------------------------

For the command reference that matches your installed build, type `help`
inside ChemQL. Use `list all` to inspect fields available in your
bundled datasets.
