Compare commits

...

19 Commits

Author SHA1 Message Date
358d72b968 added telugu to language packs
All checks were successful
Deploy Docs / deploy (push) Successful in 11s
2026-06-13 12:29:30 -04:00
d3b130b666 added telugu
All checks were successful
Deploy Docs / deploy (push) Successful in 49s
2026-06-13 12:20:09 -04:00
17f9d85b99 added chinese to languag epacks
All checks were successful
Deploy Docs / deploy (push) Successful in 10s
2026-05-21 14:45:02 +00:00
e761f1dc68 Merge pull request 'finish-mds for full project' (#3) from finish-mds into main
All checks were successful
Deploy Docs / deploy (push) Successful in 13s
Reviewed-on: #3
2026-05-21 14:42:25 +00:00
7555636306 fixed some bugs 2026-05-21 14:40:58 +00:00
943791cb07 added architeceutre and releasing guides 2026-05-21 14:39:13 +00:00
e763c9fbc6 added core contribution guide 2026-05-21 14:38:06 +00:00
1af1801065 added lang pack contribution guide 2026-05-21 14:37:27 +00:00
d9c907a25a added template 2026-05-21 14:36:47 +00:00
16fb3478bb changed mkdocs
All checks were successful
Deploy Docs / deploy (push) Successful in 9s
2026-05-20 23:04:30 -05:00
9468719411 added chiense language repo
All checks were successful
Deploy Docs / deploy (push) Successful in 10s
2026-05-20 22:25:24 -05:00
c2def7f305 Merge pull request 'update-mds' (#2) from update-mds into main
All checks were successful
Deploy Docs / deploy (push) Successful in 12s
Reviewed-on: #2
2026-05-21 03:13:36 +00:00
6ec5f9afbb added language packs ghome 2026-05-20 22:13:21 -05:00
bd53d1c0f5 added custom opacks md 2026-05-20 22:12:47 -05:00
ff9f0e1e14 added postfix syntax 2026-05-20 22:12:32 -05:00
4436ddb8f3 added cli reference 2026-05-20 22:12:04 -05:00
4227d48f30 added index and getting started 2026-05-20 22:11:25 -05:00
bd5ec35ce2 Merge pull request 'docs site from scratch with languages.yml control' (#1) from dynamic-readme-linkage into main
All checks were successful
Deploy Docs / deploy (push) Successful in 10s
Reviewed-on: #1
2026-05-21 03:04:01 +00:00
1245333858 docs site from scratch with languages.yml control 2026-05-20 22:01:15 -05:00
15 changed files with 1494 additions and 24 deletions

View File

@@ -3,6 +3,7 @@ name: Deploy Docs
on:
push:
branches: ["main"]
workflow_dispatch:
jobs:
deploy:
@@ -19,6 +20,76 @@ jobs:
- name: Install MkDocs
run: pip install -r requirements.txt
- name: Fetch language READMEs and update nav
env:
GITEA_URL: https://git.keshavanand.net
ORG: foreign-thon
run: |
python3 << 'PYEOF'
import urllib.request
import json
import os
import re
gitea_url = os.environ["GITEA_URL"]
org = os.environ["ORG"]
# Read languages.yml manually (no yaml dep needed)
with open("languages.yml") as f:
lines = f.readlines()
languages = []
current = {}
for line in lines:
line = line.strip()
if line.startswith("- code:"):
if current:
languages.append(current)
current = {"code": line.split(":", 1)[1].strip()}
elif line.startswith("name:") and current:
current["name"] = line.split(":", 1)[1].strip()
elif line.startswith("repo:") and current:
current["repo"] = line.split(":", 1)[1].strip()
if current:
languages.append(current)
nav_lines = []
for lang in languages:
code = lang["code"]
name = lang["name"]
repo = lang["repo"]
readme_url = f"{gitea_url}/{org}/{repo}/raw/branch/main/README.md"
try:
with urllib.request.urlopen(readme_url) as r:
content = r.read().decode("utf-8")
out_path = f"docs/language-packs/{code}.md"
with open(out_path, "w", encoding="utf-8") as f:
f.write(content)
nav_lines.append(f" - '{name} ({code})': language-packs/{code}.md")
print(f"✓ {repo} → {out_path}")
except Exception as e:
print(f"✗ {repo}: {e}")
# Inject into mkdocs.yml between markers
with open("mkdocs.yml") as f:
yml = f.read()
nav_block = "\n".join(nav_lines)
new_yml = re.sub(
r" # LANGS_NAV_START.*?# LANGS_NAV_END",
f" # LANGS_NAV_START\n{nav_block}\n # LANGS_NAV_END",
yml,
flags=re.DOTALL
)
with open("mkdocs.yml", "w") as f:
f.write(new_yml)
print(f"Nav updated with {len(nav_lines)} languages.")
PYEOF
- name: Build
run: mkdocs build --strict
@@ -32,12 +103,7 @@ jobs:
echo "$SSH_DEPLOY_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H $SERVER_HOST >> ~/.ssh/known_hosts
# Empties the folder safely without triggering Zsh glob errors
# and without needing root permissions on /var/www
ssh -i ~/.ssh/deploy_key $SERVER_USER@$SERVER_HOST \
"find /var/www/foreignthon-docs -mindepth 1 -delete"
# Copy built site
"rm -rf /var/www/foreignthon-docs/* && mkdir -p /var/www/foreignthon-docs"
scp -i ~/.ssh/deploy_key -r site/* \
$SERVER_USER@$SERVER_HOST:/var/www/foreignthon-docs/

View File

@@ -1 +1,117 @@
# Coming soon
# CLI Reference
All commands support `-h` / `--help`.
---
## `fpy new`
Scaffold a new ForeignThon project.
```bash
fpy new <name> --lang <code> # create in new directory
fpy new --lang <code> # initialize current directory (must be empty)
fpy new <name> --lang <code> --no-git
fpy new <name> --lang custom # scaffold a blank language pack
```
| Flag | Description |
|---|---|
| `--lang`, `-l` | Language code — required |
| `--no-git` | Skip `git init` and initial commit |
!!! note "Using `--lang custom`"
Prompts for a language code, English name, and native name. Creates a `custom.json` with all Python keywords pre-filled as stubs and wires it up in `.foreignthon.toml` automatically.
---
## `fpy run`
Transpile and run a source file.
```bash
fpy run script.es.py
fpy run script.py --lang es # override language detection
fpy run script.es.py --keep # also write the compiled .py to disk
```
| Flag | Description |
|---|---|
| `--lang`, `-l` | Override language detection |
| `--keep` | Write `.compiled.py` alongside source after running |
---
## `fpy compile`
Transpile to standard Python without running.
```bash
fpy compile script.es.py # → script.compiled.py (same directory)
fpy compile script.es.py -o dist/ # → dist/script.compiled.py
fpy compile script.es.py -o output.py # → output.py
```
| Flag | Description |
|---|---|
| `--output`, `-o` | Output file or directory |
---
## `fpy decompile`
Convert standard Python back to a foreign language. Keywords and builtins are translated — variable names are untouched.
```bash
fpy decompile script.py --lang es
fpy decompile script.py --lang es --postfix
fpy decompile script.py --lang es -o out/
```
| Flag | Description |
|---|---|
| `--lang`, `-l` | Target language — required |
| `--postfix` | Rewrite conditionals to `@@` postfix style |
| `--output`, `-o` | Output file or directory |
!!! note
Decompile is lossy — variable names and comments are not translated back. It is useful for bootstrapping a foreign-language version of an existing Python file, not as a perfect round-trip.
---
## `fpy check`
Validate syntax without running.
```bash
fpy check script.es.py
# ✓ script.es.py looks good.
# or
# ✗ Syntax error: invalid syntax (script.es.py, line 4)
```
Exits with code `1` on failure — useful in CI pipelines.
---
## `fpy pack`
Validate a language pack JSON file against the required schema.
```bash
fpy pack mylang.json
# ✓ Pack 'Russian' is valid.
# or
# ✗ Missing sections: {'error_messages'}
```
---
## Language detection order
When running a file, ForeignThon resolves the language in this order:
1. `--lang` flag (highest priority)
2. Shebang comment: `# foreignthon: es`
3. File extension: `.es.py``es`
4. `.foreignthon.toml` in the project root

View File

@@ -1 +1,122 @@
# Coming soon
# Contributing to Core
`foreignthon-core` is the transpiler engine, CLI, and pack loader. Contributions that improve correctness, performance, or usability are welcome.
---
## Repository
[https://git.keshavanand.net/foreign-thon/foreignthon-core](https://git.keshavanand.net/foreign-thon/foreignthon-core)
---
## Dev setup
```bash
git clone https://git.keshavanand.net/foreign-thon/foreignthon-core
cd foreignthon-core
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]" # installs foreignthon + pytest + ruff
```
Verify the install:
```bash
fpy --version
pytest
```
---
## Running tests
```bash
pytest # all tests
pytest -v # verbose
pytest -k postfix # filter by name
```
Tests live in `tests/test_engine.py`. They use a local `test_pack.json` fixture (a copy of the Spanish pack) so no installed language pack is needed.
---
## Code style
ForeignThon uses [Ruff](https://docs.astral.sh/ruff/) for linting and formatting:
```bash
ruff check src/ # lint
ruff format src/ # format
```
The CI gate runs both. A PR that fails either will not be merged.
Line length is 88. Import order follows the `I` rule set (isort-compatible).
---
## Project layout
```
foreignthon-core/
├── src/
│ └── foreignthon/
│ ├── __init__.py # version
│ ├── cli.py # click commands (fpy)
│ ├── errors.py # bilingual error hook
│ ├── pack.py # pack discovery and validation
│ ├── template.json # canonical list of all pack keys
│ └── transpiler.py # tokenizer-based engine
└── tests/
├── test_engine.py
└── test_pack.json # Spanish fixture (no install needed)
```
---
## Adding new keywords or builtins
`template.json` is the **single source of truth** for the full set of keywords, builtins, exceptions, and stdlib names that all language packs must cover.
To add a new entry (e.g. a new builtin):
1. Add it to `template.json` with the English value as the default.
2. Add a matching entry to `test_pack.json` (Spanish values).
3. Update `_build_mapping()` in `transpiler.py` if a new section is needed.
4. Bump `foreignthon-core` version.
5. Announce to language pack maintainers so they can update their packs.
Do not add entries to `template.json` that are specific to one language pack.
---
## Adding a new CLI command
All commands are defined in `cli.py` using [Click](https://click.palletsprojects.com/).
- Add your command as a function decorated with `@main.command()`.
- Include a docstring — Click uses it as the `--help` text.
- Add `CONTEXT_SETTINGS` to every command for consistent `-h` support.
- Write tests in `tests/test_engine.py` or a new test file.
---
## Submitting a PR
1. Fork the repository on Gitea.
2. Create a branch: `git checkout -b feature/my-change`.
3. Make your change, add or update tests, run `pytest` and `ruff check`.
4. Open a PR with a clear description of the problem and the fix.
For large changes (new features, engine behaviour changes), open an issue first to discuss before writing code.
---
## What not to change
- **`template.json`** keys should only grow, never shrink — removing a key is a breaking change for all existing language packs.
- The tokenizer-based approach in `transpiler.py` is intentional. Do not replace it with a regex or AST-based approach without a very strong reason — the tokenizer correctly handles strings, comments, and f-strings without any special casing.
- The bilingual error format (`[XX] ForeignName: msg` / `[EN] EnglishName: msg`) is part of the public interface. Do not change the format without a major version bump.

View File

@@ -1 +1,189 @@
# Coming soon
# Contributing Language Packs
Language packs are published independently to PyPI — no access to the `foreignthon-core` repo is needed. There are more packs than the ones listed here; the ones on the official docs site are the ones hosted under the [`foreign-thon` org on Gitea](https://git.keshavanand.net/foreign-thon/).
---
## The right starting point
**Do not start from scratch.** Fork the official language template:
```
https://git.keshavanand.net/foreign-thon/language-template.git
```
The template gives you:
| File | Purpose |
|---|---|
| `src/foreignthon_xx/__init__.py` | Exposes `get_pack_path()` and reads version/author metadata |
| `src/foreignthon_xx/xx.json` | The pack file — all keys are English stubs to replace |
| `tests/test_pack.py` | Universal test suite shared by every official pack |
| `.gitea/workflows/ci.yml` | Runs `pytest` on every push and PR |
| `.gitea/workflows/publish.yml` | Builds and uploads to PyPI on a `v*` tag |
| `.gitea/workflows/trigger-docs.yml` | Pings the docs site to rebuild when `README.md` changes |
| `pyproject.toml` | Pre-wired entry points, hatchling build, GPL v3 |
---
## Step 1 — Fork and rename
Fork the template on Gitea (or GitHub mirror). Rename your fork to `foreignthon-xx` where `xx` is the [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) two-letter code for your language (use ISO 639-2 three-letter code if no two-letter code exists).
Clone your fork locally:
```bash
git clone https://git.keshavanand.net/yourname/foreignthon-xx
cd foreignthon-xx
```
---
## Step 2 — Rename the template files
The template uses placeholder names. Rename:
```
src/foreignthon_xx/xx.json → src/foreignthon_fr/fr.json (example: French)
src/foreignthon_xx/ → src/foreignthon_fr/
```
Then update every occurrence of `xx` in:
- `pyproject.toml``name`, `entry-points` key, `packages` path, `keywords`, `description`
- `src/foreignthon_fr/__init__.py` — the `get_pack_path()` return value
---
## Step 3 — Fill in the JSON
Open `src/foreignthon_fr/fr.json`. Every value is an English Python identifier — **never change the values**. Replace the **keys** with your language's words.
```json
{
"meta": {
"name": "French",
"native_name": "Français",
"code": "fr"
},
"keywords": {
"si": "if",
"sinon": "else",
...
},
"builtins": { ... },
"exceptions": { ... },
"error_messages": { ... },
"stdlib": { ... },
"postfix_keywords": []
}
```
The filename must exactly match `meta.code` (e.g. `fr.json` for code `"fr"`).
Set `postfix_keywords` to a list of English keywords (e.g. `["if", "elif", "while"]`) if your language is SOV and benefits from `@@` postfix style on decompile. Set it to `[]` for SVO languages.
For a full reference on the JSON schema see [Custom Packs → Pack schema](../custom-packs.md#pack-schema).
---
## Step 4 — Validate and test
Install your pack in editable mode and run the test suite:
```bash
pip install -e .
pip install pytest
fpy pack src/foreignthon_fr/fr.json
# ✓ Pack 'French' is valid.
pytest tests/ -v
```
The shared test suite (`tests/test_pack.py`) checks:
- All required sections exist (`meta`, `keywords`, `builtins`, `exceptions`, `error_messages`, `stdlib`, `postfix_keywords`)
- Every keyword value is a real Python keyword
- Every builtin/exception value is a real Python builtin
- The filename matches `meta.code`
- All `postfix_keywords` entries have a matching translation in `keywords`
The CI (`ci.yml`) runs this same suite on every push and pull request automatically.
---
## Step 5 — Publish to PyPI
You can publish from your own fork at any time — the pack works as soon as it is on PyPI, regardless of whether it is in the official org.
Tag a release and push:
```bash
git tag v0.1.0
git push origin v0.1.0
```
The `publish.yml` workflow builds the wheel and uploads it to PyPI using the `PYPI_TOKEN` secret. If you are working from your personal fork you will need to add your own `PYPI_TOKEN` secret in your fork's Gitea/GitHub settings.
Once published, anyone can install your pack:
```bash
pip install foreignthon-fr
fpy new myproject --lang fr
```
---
## Getting hosted under the official org
The `foreign-thon` org on Gitea is where official packs live and where the org-level CI secrets (`PYPI_TOKEN`, `GIT_RELEASE_TOKEN`, `DOCS_TRIGGER_TOKEN`) are inherited automatically.
**Nobody has direct repo-creation access in the org.** To get your pack hosted there:
1. Make sure your pack is working and published to PyPI.
2. Open an issue or PR on [foreignthon-docs](https://git.keshavanand.net/foreign-thon/foreignthon-docs) requesting inclusion, and include:
- Your language code, English name, and native name
- A link to your published PyPI package
- Your Gitea handle
3. The maintainer will transfer or mirror your repo into the org and wire up the secrets.
---
## Getting listed on the docs site
The docs site table in [Language Packs → Overview](../language-packs/index.md) is maintained in `languages.yml` in the `foreignthon-docs` repo. Open a PR to add your entry:
```yaml
- code: fr
name: French
repo: foreignthon-fr
```
The `trigger-docs.yml` workflow in your pack repo fires a rebuild of the docs site whenever `README.md` is pushed to `main`.
---
## Quality bar
Before submitting for listing, your pack should satisfy:
- [ ] `fpy pack xx.json` passes
- [ ] `pytest tests/` passes
- [ ] Every foreign key is unique within the pack
- [ ] No English words used as foreign keys unless the language genuinely keeps them (e.g. `lambda`)
- [ ] `meta.code` matches the JSON filename and the entry-point key in `pyproject.toml`
- [ ] Package name is `foreignthon-xx` (hyphen), module name is `foreignthon_xx` (underscore)
- [ ] `pip install foreignthon-xx` works from PyPI
---
## Versioning and updates
When `foreignthon-core` releases a new version that adds entries to `template.json`, update your pack to cover them and release a new version. Update the `foreignthon` lower-bound dependency in `pyproject.toml` if your pack requires the new core:
```toml
dependencies = ["foreignthon>=0.5.4"]
```
Follow [Semantic Versioning](https://semver.org/). Use `0.x.y` while the keyword set is still being refined; move to `1.0.0` once stable.

View File

@@ -1 +1,95 @@
# Coming soon
## Custom Packs
ForeignThon lets you extend or override any installed language pack locally — no PyPI account, no new package required. You can also scaffold a completely new language from scratch.
---
## Local override
Create a `custom.json` in your project root with only the keys you want to change:
```json
{
"builtins": {
"show": "print"
},
"keywords": {
"when": "if"
}
}
```
Then reference it in `.foreignthon.toml`:
```toml
[foreignthon]
lang = "es"
custom_pack = "custom.json"
```
Custom keys are merged on top of the installed pack. Installed pack keys are preserved — only the keys you define in `custom.json` are overridden.
!!! tip
ForeignThon walks up the directory tree to find `.foreignthon.toml`, so you can place it at the project root and run `fpy` from any subdirectory.
---
## Scaffold a new language
If no pack exists for your language yet:
```bash
fpy new myproject --lang custom
```
You will be prompted for:
- Language code (e.g. `ru`, `fr`, `ar`)
- English name (e.g. `Russian`)
- Native name (e.g. `Русский`)
This generates a `custom.json` based on the official template — every Python keyword, builtin, exception, and stdlib module is listed with the English value as a placeholder. Replace the **keys** with your language's words.
```json
{
"keywords": {
"if": "if", replace the key, keep the value
"for": "for",
"def": "def",
...
}
}
```
The `.foreignthon.toml` is automatically wired to use this file.
---
## Pack schema
A standalone pack must have these top-level sections:
| Section | Purpose |
|---|---|
| `meta` | Name, code, version, authors |
| `keywords` | Python reserved words |
| `builtins` | Built-in functions |
| `exceptions` | Built-in exception classes |
| `error_messages` | Translations for bilingual error output |
| `stdlib` | Common standard library module names |
| `postfix_keywords` | English keywords to rewrite in `--postfix` output |
Validate your pack at any time:
```bash
fpy pack custom.json
# ✓ Pack 'Russian' is valid.
```
---
## Publishing
Once your `custom.json` is complete and working, you can turn it into a proper `foreignthon-xx` package on PyPI so others can install it with `pip install foreignthon-xx`.
See [Contributing → Language Packs](contributing/language-packs.md) for the full guide.

View File

@@ -1 +1,160 @@
# Coming soon
# Architecture
This page describes how `foreignthon-core` works internally.
---
## Pipeline
```
source.xx.py
_check_shebang() ← reads "# foreignthon: xx" if present
load_pack(lang_code) ← discovers + validates the JSON pack
_apply_postfix_syntax() ← rewrites "expr @@keyword:" lines
_swap_tokens() ← tokenizer pass: replaces NAME tokens
standard Python string ← ready to compile or write to disk
```
---
## Module overview
| Module | Responsibility |
|---|---|
| `transpiler.py` | The engine — postfix rewriter and tokenizer pass |
| `pack.py` | Pack discovery, loading, and validation |
| `cli.py` | Click commands (`fpy run`, `fpy compile`, etc.) |
| `errors.py` | Bilingual exception hook |
| `template.json` | Canonical set of all keywords/builtins a pack must cover |
---
## Tokenizer-based translation
ForeignThon uses Python's standard `tokenize` module rather than regex or AST manipulation.
`tokenize.generate_tokens()` splits source code into typed tokens. ForeignThon only looks at `NAME` tokens — identifiers. It replaces any `NAME` token whose string appears as a key in the active pack mapping. All other token types (strings, comments, operators, numbers) pass through unchanged.
This gives three important guarantees:
1. **Strings are safe.** A keyword inside `"..."` or `f"..."` is a `STRING` token, never a `NAME` — it is never touched.
2. **Comments are safe.** Comment tokens are passed through verbatim.
3. **Variable names are safe.** A variable like `si_condition` contains `si` only as a substring; as a `NAME` token it is `si_condition`, which is not in the mapping.
The whitespace between tokens is preserved by tracking `(row, col)` positions and copying the gaps from the original source.
---
## Pack discovery
Language packs register themselves using Python [entry points](https://packaging.python.org/en/latest/specifications/entry-points/):
```toml
# in foreignthon-es/pyproject.toml
[project.entry-points."foreignthon.langs"]
es = "foreignthon_es"
```
`pack.py` calls `importlib.metadata.entry_points(group="foreignthon.langs")` at runtime to discover all installed packs. Installing a pack is sufficient — no configuration file needs to be edited.
Each pack module must expose:
```python
def get_pack_path() -> Path:
return files(__name__) / "xx.json"
```
The core calls `get_pack_path()` to locate the JSON, loads it, and validates that all required sections are present.
Results are cached with `@lru_cache` so each pack is loaded at most once per process.
---
## Pack mapping
Four sections of the JSON are merged into a single flat dict for translation:
```python
mapping = {}
mapping.update(pack["keywords"])
mapping.update(pack["builtins"])
mapping.update(pack["exceptions"])
mapping.update(pack["stdlib"])
```
The merged mapping is `{ foreign_word: english_word }`. It is passed directly to `_swap_tokens()`.
If two sections define the same foreign key, later sections win (stdlib last). In practice this does not occur because pack authors ensure uniqueness.
---
## Postfix syntax (`@@`)
The `@@` operator is a source-level pre-processing step that runs **before** tokenization.
A line like:
```
x > 0 @@si:
escribir(x)
```
is rewritten to:
```
si x > 0:
escribir(x)
```
The rewriter uses a regex that matches `(.+?)@@(<keyword>)` and moves the keyword to the front. It only operates on lines that contain `@@`, preserving indentation and line endings.
`@@` is never valid Python and never appears in the tokenizer output.
**Decompile direction:** `fpy decompile --postfix` does the reverse — it looks for lines of the form `foreign_kw expr:` where `foreign_kw` is in the pack's `postfix_keywords` list, and rewrites them to `expr @@foreign_kw:`.
---
## Bilingual error hook
`errors.py` installs a custom `sys.excepthook` before running user code:
1. On exception, it looks up the exception type name in the pack's `exceptions` section (reverse map: English → foreign).
2. It looks up a translated message in `error_messages`.
3. It prints `[XX] ForeignName: translated_msg` then `[EN] EnglishName: original_msg`.
4. It always calls `traceback.print_exception()` afterwards so the full traceback is shown.
Tracebacks point to the original `.xx.py` file. This is achieved by populating `linecache.cache` with the original source before `exec()`-ing the compiled code, so Python's traceback machinery reads the right lines.
---
## Custom pack override
When `.foreignthon.toml` declares `custom_pack = "path/to/custom.json"`:
- If the custom JSON has `meta.code` set, it is treated as a **standalone pack** and used directly.
- If `meta.code` is absent, it is treated as an **override** — it is merged on top of the installed pack, replacing only the keys it defines.
The CLI (`cli.py`) handles this in `_load_effective_pack()` by walking up the directory tree to find `.foreignthon.toml`.
---
## File naming and language detection
Language detection order (highest priority first):
1. `--lang` CLI flag
2. Shebang comment `# foreignthon: xx` on the first line
3. Double extension `.xx.py``xx`
4. Fallback to `"en"` (no-op — English is Python)
`_detect_lang()` and `_check_shebang()` in `transpiler.py` implement steps 3 and 2 respectively. Step 1 is handled by the `run` command in `cli.py`.

View File

@@ -1 +1,136 @@
# Coming soon
# Releasing
This page covers how to release `foreignthon-core` and how language pack maintainers should respond to a new core release.
---
## Core release (`foreignthon`)
### 1. Update the version
Version is set in `pyproject.toml`:
```toml
[project]
name = "foreignthon"
version = "0.5.4" # bump this
```
ForeignThon follows [Semantic Versioning](https://semver.org/):
| Change | Version bump |
|---|---|
| Bug fix, no API change | Patch (`0.5.3``0.5.4`) |
| New feature, backward compatible | Minor (`0.5.x``0.6.0`) |
| Breaking change (removes a key, changes CLI contract) | Major (`0.x.y``1.0.0`) |
### 2. Update `template.json` if needed
If the release adds new keywords, builtins, or sections, update `template.json` to include them. This is the canonical list all language packs are expected to cover. Document what changed in your commit message and PR so pack maintainers know what to add.
### 3. Run CI locally
```bash
pytest
ruff check src/
ruff format --check src/
```
All must pass.
### 4. Tag and push
The Gitea CI publishes to PyPI on any tag matching `v*`:
```bash
git tag v0.5.4
git push origin v0.5.4
```
The `publish.yml` workflow builds the wheel and uploads it using the `PYPI_TOKEN` secret configured in Gitea.
### 5. Verify
```bash
pip install --upgrade foreignthon
fpy --version
```
---
## Language pack release (`foreignthon-xx`)
Each language pack is independently versioned and released.
### Responding to a core release
When `foreignthon-core` adds new entries to `template.json`:
1. Add the new keys to your `xx.json` with translations.
2. Run `fpy pack src/foreignthon_xx/xx.json` to validate.
3. Run `pytest tests/` to confirm all checks pass.
4. Bump your pack's version in `pyproject.toml`.
5. Tag and push — the `publish.yml` workflow handles PyPI upload.
Update the `foreignthon` dependency lower bound in `pyproject.toml` if your pack requires the new core version:
```toml
dependencies = ["foreignthon>=0.5.4"]
```
### Releasing independently
You can release a pack at any time — to fix a translation, add missing entries, or extend coverage. The process is the same:
```bash
# bump version in pyproject.toml
git add pyproject.toml src/foreignthon_xx/xx.json
git commit -m "v0.3.3: fix translation for 'return'"
git tag v0.3.3
git push origin v0.3.3
```
---
## Docs release (`foreignthon-docs`)
The docs site rebuilds automatically when a language pack fires its `trigger-docs.yml` workflow after a successful publish. No manual step is needed for routine pack releases.
To manually trigger a docs rebuild (e.g. after editing `foreignthon-docs` directly):
```bash
git push origin main
```
The `deploy.yml` workflow builds the MkDocs site and deploys it to [foreignthon.keshavanand.net](https://foreignthon.keshavanand.net).
---
## Gitea secrets
| Secret | Used by |
|---|---|
| `PYPI_TOKEN` | `publish.yml` in core and all packs |
| `DOCS_WEBHOOK` | `trigger-docs.yml` in language packs |
Secrets are set per-repository in **Gitea → Settings → Actions → Secrets**.
---
## Checklist
### Core
- [ ] Version bumped in `pyproject.toml`
- [ ] `template.json` updated if new keys added
- [ ] `pytest` passes
- [ ] `ruff check` passes
- [ ] Tagged `v*` and pushed
- [ ] PyPI upload confirmed
### Language pack
- [ ] New `template.json` entries translated in `xx.json`
- [ ] `fpy pack xx.json` passes
- [ ] `pytest tests/` passes
- [ ] Version bumped
- [ ] `foreignthon` dependency lower bound updated if required
- [ ] Tagged and pushed

View File

@@ -1 +1,135 @@
# Coming soon
# Getting Started
## Requirements
- Python 3.9 or later
- A language pack (`pip install foreignthon-xx`)
---
## Installation
```bash
pip install foreignthon
```
For global CLI access across projects, use `pipx`:
```bash
pipx install foreignthon
```
---
## Create a project
```bash
fpy new myproject --lang <code>
cd myproject
```
This scaffolds:
```
myproject/
├── .foreignthon.toml # project config
├── .gitignore
├── README.md
└── src/
└── main.<lang>.py # hello world in your language
```
The `.foreignthon.toml` stores your language and any local pack overrides:
```toml
[foreignthon]
lang = "es"
# custom_pack = "custom.json"
```
---
## File naming
ForeignThon detects the language from the file extension:
```
script.es.py → Spanish
script.ta.py → Tamil
script.fr.py → French
```
You can also declare the language at the top of the file:
```python
# foreignthon: es
```
Or override it at runtime:
```bash
fpy run script.py --lang es
```
---
## Run
```bash
fpy run src/main.es.py
```
---
## Compile
```bash
fpy compile src/main.es.py
# → src/main.compiled.py
```
```bash
fpy compile src/main.es.py -o dist/
# → dist/main.compiled.py
```
The compiled file is standard Python. Commit it alongside your source — anyone can run it without ForeignThon installed.
---
## Validate
```bash
fpy check src/main.es.py
# ✓ main.es.py looks good.
```
Checks syntax without running — useful in CI.
---
## Errors
When something goes wrong, ForeignThon shows the error in your language first, then English:
```
[ES] ErrorDeDivisionCero: Error división por cero
[EN] ZeroDivisionError: division by zero
File "src/main.es.py", line 8
```
Tracebacks point to your original source file, not any intermediate.
---
## Variable names
Variable names are completely optional — English names work alongside foreign keywords with no issues. Only keywords and builtins are ever swapped.
---
## Next steps
- [CLI Reference](cli-reference.md) — all commands and flags
- [Language Packs](language-packs/index.md) — available languages
- [Custom Packs](custom-packs.md) — extend or override a pack locally

View File

@@ -1 +1,42 @@
# ForeignThon
Write Python in any human language.
ForeignThon is a transpiler that converts `.xx.py` files into standard Python — keywords, builtins, and exceptions all translated into your language. The compiled output runs anywhere without ForeignThon installed.
---
## How it works
```
source.es.py → fpy → source.compiled.py → Python
```
ForeignThon uses Python's `tokenize` module to swap `NAME` tokens. Strings, comments, and f-strings are never touched. The result is identical, valid Python.
---
## Install
```bash
pip install foreignthon
pip install foreignthon-es # or any other language pack
```
---
## At a glance
| Feature | Description |
|---|---|
| Transpiler | Tokenizer-based, safe, unicode-aware |
| File format | `.xx.py` where `xx` is the language code |
| Errors | Shown in your language first, English below |
| Postfix syntax | `@@` operator for SOV languages |
| Custom packs | Local JSON override, no PyPI needed |
| CLI | `fpy run`, `fpy compile`, `fpy decompile`, `fpy new` |
---
[Get started →](getting-started.md){ .md-button .md-button--primary }
[CLI Reference →](cli-reference.md){ .md-button }

View File

@@ -1 +0,0 @@
# Coming soon

View File

@@ -0,0 +1,58 @@
# Language Packs
Language packs are separate PyPI packages that define keyword, builtin, and exception mappings for a specific human language. Each is installed independently and discovered automatically by ForeignThon.
---
## Install a pack
```bash
pip install foreignthon-<code>
```
Multiple packs can coexist — install as many as you need.
---
## Available packs
| Language | Code | Install |
|---|---|---|
| Spanish | `es` | `pip install foreignthon-es` |
| Tamil | `ta` | `pip install foreignthon-ta` |
| Chineese | `zh` | `pip install foreignthon-zh` |
| Telugu | `te` | `pip install foreignthon-te` |
---
## How discovery works
ForeignThon uses Python [entry points](https://packaging.python.org/en/latest/specifications/entry-points/) to discover installed packs. Installing a pack is sufficient — no configuration required.
```toml
# Each pack registers itself in its pyproject.toml:
[project.entry-points."foreignthon.langs"]
es = "foreignthon_es"
```
---
## What a pack covers
| Section | Examples |
|---|---|
| Keywords | `if`, `for`, `def`, `class`, `return` … |
| Builtins | `print`, `range`, `len`, `type` … |
| Exceptions | `ValueError`, `TypeError`, `KeyError` … |
| Error messages | Bilingual error output translations |
| Stdlib | `math`, `sys`, `os`, `random` … |
| Postfix keywords | Which keywords to rewrite with `@@` on decompile |
Third-party library names (numpy, pandas, etc.) are intentionally out of scope.
---
## Don't see your language?
Anyone can publish a `foreignthon-xx` pack — no core access required. See [Contributing → Language Packs](../contributing/language-packs.md) to get started, or use a [local custom pack](../custom-packs.md) in the meantime.

View File

@@ -0,0 +1,240 @@
# Add Your Language
Anyone can publish a `foreignthon-xx` language pack to PyPI — no access to the core repo is needed, and the pack works for anyone who installs it the moment it is on PyPI.
The recommended path is to start from the official language template. See [Contributing → Language Packs](../contributing/language-packs.md) for the full contribution and review process. This page is a quick reference.
---
## Start from the template
Fork the official language template on Gitea:
```
https://git.keshavanand.net/foreign-thon/language-template.git
```
The template gives you the full correct structure — `__init__.py`, JSON pack, the universal `tests/test_pack.py`, and all three CI workflows — pre-wired and ready to rename.
!!! tip "Why fork instead of starting fresh?"
The template includes the shared test suite (`tests/test_pack.py`) that every official pack uses. Starting from it means your CI is already correct and your pack will pass the same checks as `foreignthon-es` and `foreignthon-ta` from day one.
Alternatively, if you just want a local pack for your own project without publishing, use:
```bash
fpy new myproject --lang custom
```
This scaffolds a `custom.json` locally — no PyPI account needed. See [Custom Packs](../custom-packs.md) for details.
---
## Package structure
After renaming the template for your language (e.g. French, code `fr`):
```
foreignthon-fr/
├── .gitea/workflows/
│ ├── ci.yml # pytest on push / PR
│ ├── publish.yml # PyPI upload on v* tag
│ └── trigger-docs.yml # docs rebuild on README.md change
├── .gitignore
├── LICENSE # GPL v3
├── README.md
├── pyproject.toml
└── src/
└── foreignthon_fr/
├── __init__.py
└── fr.json
```
---
## `pyproject.toml`
```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "foreignthon-fr"
version = "0.1.0"
description = "French language pack for ForeignThon."
license = { text = "GPL v3" }
requires-python = ">=3.9"
authors = [
{ name = "Your Name", email = "you@example.com" }
]
keywords = ["foreignthon", "french", "français"]
dependencies = ["foreignthon>=0.5.3"]
[project.urls]
Homepage = "https://git.keshavanand.net/foreign-thon/foreignthon-fr"
[project.entry-points."foreignthon.langs"]
fr = "foreignthon_fr"
[tool.hatch.build.targets.wheel]
packages = ["src/foreignthon_fr"]
```
The `[project.entry-points."foreignthon.langs"]` block is what makes ForeignThon auto-discover your pack — the key is the language code, the value is the Python module name.
---
## `__init__.py`
```python
from importlib.resources import files
from importlib.metadata import version, metadata, PackageNotFoundError
try:
package_name = (__package__ or "").replace("_", "-")
__version__ = version(package_name)
pkg_metadata = metadata(package_name)
raw_authors = pkg_metadata.get_all("Author") or []
raw_emails = pkg_metadata.get_all("Author-email") or []
combined = []
for item in (raw_authors + raw_emails):
clean_name = item.split("<")[0].strip()
if clean_name and clean_name not in combined:
combined.append(clean_name)
__authors__ = combined
except PackageNotFoundError:
__version__ = "0.0.0"
__authors__ = []
def get_pack_path():
return files(__name__) / "fr.json"
```
Change `"fr.json"` to match your actual filename.
---
## The JSON pack
Keys are your language's words. Values are English Python identifiers — **never change the values**.
```json
{
"meta": {
"name": "French",
"native_name": "Français",
"code": "fr"
},
"keywords": {
"si": "if",
"sinon": "else",
"sinonsi": "elif",
"pour": "for",
"tantque": "while",
"déf": "def",
"classe": "class",
"importer": "import",
"depuis": "from",
"comme": "as",
"retourner": "return",
"arrêter": "break",
"continuer": "continue",
"passer": "pass",
"essayer": "try",
"sauf": "except",
"finalement": "finally",
"lever": "raise",
"avec": "with",
"dans": "in",
"est": "is",
"et": "and",
"ou": "or",
"non": "not",
"supprimer": "del",
"global": "global",
"nonlocal": "nonlocal",
"affirmer": "assert",
"générer": "yield",
"attendre": "await",
"asynchrone": "async",
"lambda": "lambda",
"Vrai": "True",
"Faux": "False",
"Rien": "None"
},
"builtins": {
"afficher": "print",
"saisir": "input",
"longueur": "len",
"intervalle": "range",
...
},
"exceptions": {
"ErreurDeValeur": "ValueError",
...
},
"error_messages": {
"ValueError": "Erreur de valeur",
...
},
"stdlib": {
"mathématiques": "math",
...
},
"postfix_keywords": []
}
```
### `postfix_keywords`
Set this to a list of English keywords if your language is SOV (subject-object-verb) and benefits from `@@` postfix style on decompile. Example (Tamil):
```json
"postfix_keywords": ["if", "elif", "while", "class", "with"]
```
Set it to `[]` for SVO languages like Spanish or French.
### Filename rule
The JSON filename **must** match `meta.code` exactly. `fr.json` for code `"fr"`.
---
## Validate and test
```bash
pip install -e .
pip install pytest
fpy pack src/foreignthon_fr/fr.json
# ✓ Pack 'French' is valid.
pytest tests/ -v
```
The shared `tests/test_pack.py` checks that all sections exist, all values are real Python identifiers, the filename matches `meta.code`, and `postfix_keywords` entries each have a translation.
---
## Publish
Tag and push — the CI handles the rest:
```bash
git tag v0.1.0
git push origin v0.1.0
```
The `publish.yml` workflow builds the wheel and uploads it to PyPI using the `PYPI_TOKEN` secret. Add your own `PYPI_TOKEN` to your fork's secrets if you are not yet under the official org.
---
## Getting listed
Once your pack is working and on PyPI, open a PR or issue on [foreignthon-docs](https://git.keshavanand.net/foreign-thon/foreignthon-docs) to get added to the [Language Packs overview](index.md). See [Contributing → Language Packs](../contributing/language-packs.md) for the full process.

View File

@@ -1 +1,87 @@
# Coming soon
## Postfix Syntax
Some languages are SOV — subject-object-verb — meaning the condition naturally comes before the keyword rather than after it. ForeignThon supports this with the `@@` operator.
---
## The problem
In English-order Python, the keyword always comes first:
```python
if condition:
...
```
In many languages, the natural order is the opposite — the condition is stated first, then the action. Forcing English word order on these languages makes the code feel unnatural.
---
## The solution
The `@@` operator lets you put any keyword after its expression:
```
condition @@keyword:
body
```
This is equivalent to:
```
keyword condition:
body
```
Both produce identical compiled Python. `@@` is purely a source-level syntax — it is processed before tokenization and never appears in the output.
---
## Rules
- `@@` rewrites only the line it appears on — nothing else changes
- Indentation follows standard Python rules, unchanged
- Prefix and postfix can be mixed freely in the same file
- Works for any keyword in any language pack
- `@@` is not valid Python syntax, so it never conflicts with existing code
---
## Supported constructs
| Construct | Prefix | Postfix |
|---|---|---|
| if | `keyword condition:` | `condition @@keyword:` |
| elif | `keyword condition:` | `condition @@keyword:` |
| while | `keyword condition:` | `condition @@keyword:` |
| def | `keyword name(args):` | `name(args) @@keyword:` |
| class | `keyword Name:` | `Name @@keyword:` |
| for | `keyword var in iter:` | `var @@in_kw iter @@for_kw:` |
!!! note
`for` loops with postfix require two `@@` operators and can be complex. Most users keep `for` in prefix style.
---
## Decompile with postfix
When converting Python back to a foreign language, pass `--postfix`:
```bash
fpy decompile script.py --lang <code> --postfix
```
Which keywords get rewritten is controlled by the `postfix_keywords` list in the language pack JSON. A language that uses SVO order sets this to `[]` — postfix output is never forced on languages that don't need it.
```json
"postfix_keywords": ["if", "elif", "while", "def", "class"]
```
---
## Input vs output
| Direction | Mechanism | Controlled by |
|---|---|---|
| Input (writing `.xx.py`) | `@@` in source | Always available for any keyword |
| Output (`fpy decompile --postfix`) | Pack's `postfix_keywords` | Language pack author |

18
languages.yml Normal file
View File

@@ -0,0 +1,18 @@
# Languages listed here get their README fetched from their repo
# and included in the docs site. Only add stable, complete packs.
languages:
- code: es
name: Spanish
repo: foreignthon-es
- code: ta
name: Tamil
repo: foreignthon-ta
- code: zh
name: Chinese
repo: foreignthon-zh
- code: te
name: Telugu
repo: foreignthon-te

View File

@@ -1,21 +1,34 @@
site_name: ForeignThon
site_url: https://foreignthon.keshavanand.net
site_description: Write Python in any human language
repo_url: https://git.keshavanand.net/foreign-thon/foreignthon-core
repo_name: foreign-thon/foreignthon-core
repo_url: https://git.keshavanand.net/foreign-thon/
repo_name: foreignthon
edit_uri: ""
theme:
name: material
palette:
- scheme: default
toggle:
icon: material/brightness-7
name: Switch to dark mode
# Dark mode default
- scheme: slate
primary: deep purple
accent: cyan
toggle:
icon: material/brightness-4
icon: material/weather-sunny
name: Switch to light mode
# Light mode
- scheme: default
primary: deep purple
accent: cyan
toggle:
icon: material/weather-night
name: Switch to dark mode
font:
text: Inter
code: JetBrains Mono
features:
- navigation.tabs
- navigation.sections
@@ -25,6 +38,8 @@ theme:
- search.suggest
- content.code.copy
- content.code.annotate
- content.tabs.link
- toc.follow
nav:
- Home: index.md
@@ -34,8 +49,8 @@ nav:
- Custom Packs: custom-packs.md
- Language Packs:
- Overview: language-packs/index.md
- Spanish (es): language-packs/es.md
- Tamil (ta): language-packs/ta.md
# LANGS_NAV_START
# LANGS_NAV_END
- Add your language: language-packs/template.md
- Contributing:
- Core: contributing/core.md