# Fresh — Language Specification (v1)

This is the reference for **Fresh v1.0**. It defines the file format, the grammar, the built-in
operations, and the auto-generated CLI behavior. Features slated for later phases are listed in
[PhasedRoadmap.md](PhasedRoadmap.md) and are called out inline as *(Phase N)*.

---

## 1. File format

A Fresh script is a UTF-8 text file, conventionally ending in `.fresh`. It is invoked either
directly via a shebang or through the interpreter:

```fresh
#!/usr/bin/env fresh
```

```bash
$ fresh de.fresh build      # explicit
$ ./de build                # via shebang (no chmod needed — see note)
```

> **No `chmod` needed.** The recommended install ships a helper so a Fresh script placed on `PATH`
> is runnable without setting an executable bit. Where the OS strictly requires the bit, `fresh
> install <script>` sets it for you. You never manage permissions by hand.

The language is **line-oriented** and **indentation-scoped** (like the concept sketch). A logical
line is one physical line; there is no statement terminator.

---

## 2. Lexical rules

- **Comments** start with `#` and run to end of line. A `#!` on line 1 is a shebang, not a comment.
- **Blank lines** are ignored.
- **Tokens** are whitespace-separated. Everything after an operation keyword up to end of line is
  the operation's argument text (which is then tokenized for `RUN`, or taken as paths for file ops).
- **Keywords** are UPPERCASE verbs (`RUN`, `COPY`, `MOVE`, `MKDIR`, `REMOVE`, `PRINT`, `CD`,
  `DEF`, `NAME`, `HELP_DESCRIPTION`). Uppercasing keeps them visually distinct from shell commands.
- **Indentation** defines task bodies. The body of a `DEF` is the set of following lines indented
  more than the `DEF`. Two spaces or a tab both work; be consistent within a block.

---

## 3. Metadata directives

Placed at the top level (outside any `DEF`):

| Directive | Meaning |
|-----------|---------|
| `NAME <text>` | Human-facing tool name shown in help. Defaults to the script's file name. |
| `HELP_DESCRIPTION <text>` | One-line summary shown at the top of `help`. |

```fresh
NAME             Dtracker Extension Tool
HELP_DESCRIPTION Build and deploy an extension.
```

---

## 4. Variables

Top-level assignments create string variables:

```fresh
EXTENSION_NAME  = dtracker
DEPLOYMENT_USER = dedrone
DEPLOYMENT_HOST = 192.168.1.234
OUTPUT_DIR      = out/staging
```

- Whitespace around `=` is optional (`KEY=value` and `KEY = value` are equal).
- An empty value is allowed: `EXTENSION_NAME =` (declares it empty).
- **Interpolation** uses `$NAME` or `${NAME}` inside any operation argument or path:

```fresh
RUN mvn -pl $EXTENSION_NAME package
COPY $EXTENSION_NAME/target/$EXTENSION_NAME*.jar ${OUTPUT_DIR}/
```

- Unset variables interpolate to empty *and emit a warning* (fail-fast can be enabled to make this
  an error — see §8).
- Environment variables are visible by the same `$NAME` syntax; script variables take precedence.

---

## 5. Tasks (`DEF`) and the auto-CLI

A task is declared with `DEF <name>` and a body of operations:

```fresh
DEF build
    RUN rm $EXTENSION_NAME/target/*.jar
    RUN mvn package
```

Behavior:

- **Every `DEF` automatically becomes a subcommand.** `DEF build` is invoked as `./de build`.
- **Task names** are lowercase words; `-` is allowed (`DEF build-fast`).
- A task whose name starts with `_` is **private**: callable by other tasks *(composition is Phase
  3)* but hidden from `help` and not exposed as a subcommand.
- Operations in a task run **top to bottom**, and the task **stops on the first failure** (§8).

### Auto-generated help

`help` (and `<task> help`, `--help`, `-h`) are provided for free:

```bash
$ ./de help
Dtracker Extension Tool
Build and deploy an extension.

Commands:
  build     Build the extension.
  clean     Clean build artifacts.
  deploy    Stage the built jar.
```

The one-line description of each task comes from an optional `HELP` line as its first body line:

```fresh
DEF build
    HELP Build the extension.
    RUN mvn package
```

If `HELP` is omitted, the task still appears, with a generic description.

---

## 6. Built-in operations (v1)

### `RUN <command line>` — execute a system command *(primitive P1)*

Runs the command line as a child process, streaming its output. The line is tokenized like a shell
command line (spaces separate arguments; quotes group them) *after* variable interpolation. Globs
in a `RUN` argument are passed through to the invoked program unless Fresh is asked to pre-expand.

```fresh
RUN mvn clean package
RUN rm $EXTENSION_NAME/target/*.jar
RUN ssh $DEPLOYMENT_USER@$DEPLOYMENT_HOST tail -f dedrone_logs/dtracker.log
```

### `COPY <src>... <dst>` — copy files *(primitive P2)*

Copies one or more sources to a destination. **Parent directories of the destination are created
automatically.** Globs in `src` are expanded by Fresh.

```fresh
COPY $EXTENSION_NAME/target/$EXTENSION_NAME*.jar $OUTPUT_DIR/
```

- If `dst` ends in `/` or is an existing directory, sources are copied *into* it.
- Otherwise a single source is copied *to* that exact path.
- Directories are copied recursively.

### `MOVE <src>... <dst>` — move/rename files *(primitive P2)*

Identical destination semantics to `COPY`, but removes the source. Parents auto-created.

```fresh
MOVE build/app.jar dist/app.jar
```

### `MKDIR <dir>...` — ensure directories exist *(primitive P4)*

Recursive and idempotent — never fails if the directory already exists.

```fresh
MKDIR dist logs tmp/cache
```

### `REMOVE <path>...` — delete files/directories *(primitive P5)*

Idempotent (a missing path is not an error). Directories are removed recursively. Globs expanded.

```fresh
REMOVE $EXTENSION_NAME/target/*.jar build/
```

### `CD <dir>` — set the working directory *(primitive P7)*

Applies to subsequent operations in the task. Relative to the script's directory by default.

```fresh
DEF build
    CD $EXTENSION_NAME
    RUN mvn package
```

### `PRINT <text>` — write a message *(primitive P9)*

Interpolates variables and prints a status line.

```fresh
PRINT Building $EXTENSION_NAME ...
```

---

## 7. Execution model

1. The interpreter parses the whole file, collecting metadata, variables, and tasks.
2. The first CLI argument selects a task; if omitted or `help`, help is printed.
3. The selected task's operations run in order, in a shared variable scope.
4. Exit code is `0` on success, or the failing step's non-zero code.

Global flags (handled by the runtime, not the script):

| Flag | Effect |
|------|--------|
| `--dry-run` | Print each operation that *would* run, without executing. |
| `--verbose` | Echo each operation and its resolved arguments before running. |
| `--help`, `-h` | Show help and exit. |

---

## 8. Error handling & guarantees (v1)

- **Fail-fast:** a non-zero exit from `RUN`, or a failed file operation, stops the task and returns
  that code. (A later phase adds `ALLOW_FAIL` / `ONFAIL` for exceptions.)
- **Auto-parenting:** `COPY`/`MOVE` always create destination parents; `MKDIR` is idempotent.
- **No permissions work:** file ops use default modes; no `chmod`/`chown` is ever required.
- **Idempotent cleanup:** `REMOVE` on a missing path succeeds; `MKDIR` on an existing dir succeeds.
- **Clear errors:** a missing `COPY` source or unknown task prints a precise, actionable message.

---

## 9. Worked example — Dtracker, rewritten in Fresh

The original ~230-line Bash tool, expressed in Fresh v1 (composition and `--flags` shown as they
will read once Phases 2–3 land are in the roadmap; this v1 version uses only shipped features):

```fresh
#!/usr/bin/env fresh

NAME             Dtracker Extension Tool
HELP_DESCRIPTION Build and deploy an extension.

EXTENSION_NAME  = dtracker
DEPLOYMENT_USER = dedrone
DEPLOYMENT_HOST = 192.168.1.234
STAGING_DIR     = out/staging

DEF clean
    HELP Clean build artifacts.
    REMOVE $EXTENSION_NAME/target/*.jar
    RUN mvn clean

DEF build
    HELP Build the extension.
    REMOVE $EXTENSION_NAME/target/*.jar
    RUN mvn package

DEF deploy
    HELP Stage the built jar for deployment.
    COPY $EXTENSION_NAME/target/$EXTENSION_NAME*.jar $STAGING_DIR/

DEF watchlogs
    HELP Tail the live Dtracker log on the deploy host.
    RUN ssh $DEPLOYMENT_USER@$DEPLOYMENT_HOST tail -f dedrone_logs/dtracker-current.log
```

Usage:

```bash
$ ./de help          # auto-generated command list
$ ./de clean         # removes jars, runs mvn clean
$ ./de build         # builds the jar
$ ./de deploy        # copies jar into out/staging (created automatically)
$ ./de build --dry-run   # shows what build would do, runs nothing
```

### How the same script grows (preview of later phases)

```fresh
# Phase 2 — declared parameters become --flags with defaults:
DEF build
    PARAM name = dtracker      # exposes --name, default dtracker
    FLAG  fast                 # exposes --fast (boolean)
    HELP  Build the extension.
    REMOVE $name/target/*.jar
    RUN mvn package

# Phase 3 — composition + conditionals + remote:
DEF bd
    HELP Build then deploy.
    CALL build
    CALL deploy

DEF deploy
    UPLOAD $name/target/$name*.jar $DEPLOYMENT_USER@$DEPLOYMENT_HOST:extensions/

# Phase 4 — matrix expansion:
DEF release
    MATRIX os   = linux mac
    MATRIX arch = amd64 arm64
    RUN ./package.sh --os $os --arch $arch     # runs all 4 combinations
```

---

## 10. Grammar summary (EBNF, informal)

```ebnf
script      = [ shebang ] , { line } ;
line        = comment | blank | metadata | assignment | task ;
metadata    = ("NAME" | "HELP_DESCRIPTION") , text ;
assignment  = identifier , "=" , [ text ] ;
task        = "DEF" , task-name , newline , { indent , statement } ;
statement   = help | operation ;
help        = "HELP" , text ;
operation   = ("RUN" | "COPY" | "MOVE" | "MKDIR" | "REMOVE" | "CD" | "PRINT") , text ;
identifier  = uppercase-word ;
task-name   = lowercase-word , { "-" , lowercase-word } ;
```

Everything not covered by this grammar is a syntax error with a line-numbered message — the parser
favors precise diagnostics over guessing.
