goon

goon

https://git.tonybtw.com/goon.git git://git.tonybtw.com/goon.git
7,026 bytes raw

Goon Language Specification

Version: 0.1.0

Goon is an embeddable configuration language with Nix-like syntax. It evaluates to JSON and is designed for window manager configs and similar applications.

Lexical Structure

Comments

// Single line comment

/* Multi-line
   comment */

Identifiers

IDENT = [a-zA-Z_][a-zA-Z0-9_]*

Reserved keywords: let, if, then, else, true, false, import

Integers

INT = -?[0-9]+

Examples: 0, 42, -10

Strings

Double-quoted with escape sequences and interpolation:

"hello world"
"line1\nline2"
"value is ${x}"

Escape sequences:

  • \n - newline
  • \t - tab
  • \r - carriage return
  • \\ - backslash
  • \" - double quote
  • \$ - literal dollar sign (escape interpolation)

Interpolation:

  • ${identifier} is replaced with the string value of the variable
  • Integers and booleans are converted to strings automatically

Booleans

true
false

Operators and Punctuation

Token Description
= Assignment
=> Arrow (lambda)
.. Range
... Spread
? Ternary condition
: Ternary separator
; Statement terminator
, List/argument separator
. Field access
{} Record delimiters
[] List delimiters
() Grouping / parameters

Grammar

program     = statement* expression? ;

statement   = let_binding ;
let_binding = "let" IDENT "=" expression ";" ;

expression  = if_expr
            | ternary ;

if_expr     = "if" expression "then" expression "else" expression ;

ternary     = primary ("?" expression ":" expression)? ;

primary     = INT
            | STRING
            | "true" | "false"
            | IDENT ("(" args? ")")?      (* variable or function call *)
            | IDENT ("." IDENT)*          (* field access *)
            | record
            | list
            | lambda
            | import_expr
            | "(" expression ")" ;

lambda      = "(" params? ")" "=>" expression ;
params      = IDENT ("," IDENT)* ;
args        = expression ("," expression)* ;

record      = "{" (record_item (";" record_item)* ";"?)? "}" ;
record_item = IDENT "=" expression
            | "..." expression ;

list        = "[" (list_item ("," list_item)* ","?)? "]" ;
list_item   = range
            | "..." expression
            | expression ;

range       = INT ".." INT ;

import_expr = "import" "(" STRING ")" ;

Types

Goon has the following value types:

Type Description JSON Output
nil Null value null
bool Boolean true / false
int 64-bit integer Number
string UTF-8 string String
list Ordered collection Array
record Key-value map Object
lambda Function (not serializable)

Semantics

Let Bindings

Bind a name to a value. Semicolon is required.

let x = 42;
let name = "goon";

Bindings are visible after their definition in the same scope.

Records

Records are key-value maps with string keys.

{
    name = "goon";
    version = 1;
    nested = {
        foo = "bar";
    };
}

Field access with dot notation:

let config = { name = "test"; };
let n = config.name;  // "test"

Lists

Ordered collections of values.

[1, 2, 3]
["a", "b", "c"]
[{ x = 1; }, { x = 2; }]

Ranges

Ranges expand to inclusive integer sequences inside lists.

[1..5]      // [1, 2, 3, 4, 5]
[1..1]      // [1]

Spread Operator

Spread (...) merges values into lists or records.

In lists:

let a = [1, 2];
let b = [...a, 3, 4];  // [1, 2, 3, 4]

In records:

let defaults = { gap = 10; border = 2; };
let config = { ...defaults; gap = 20; };  // { gap = 20; border = 2; }

Later values override earlier ones.

Arrow Functions (Lambdas)

Anonymous functions for creating templates.

let double = (x) => { value = x; doubled = x; };
let make_key = (mod, key, cmd) => { mod = mod; key = key; cmd = cmd; };

Functions are called with parentheses:

let result = double(5);           // { value = 5; doubled = 5; }
let k = make_key("super", "a", "app");

Functions capture their lexical environment (closures).

String Interpolation

Variables can be embedded in strings:

let name = "world";
let greeting = "hello ${name}";  // "hello world"

let n = 42;
let msg = "value is ${n}";       // "value is 42"

Conditionals

If-then-else:

let x = if true then 1 else 2;   // 1

Ternary operator:

let x = true ? 1 : 2;            // 1

Both require an else branch.

Imports

Import other goon files:

let colors = import("./colors.goon");
let theme = colors.dark;
  • Paths are relative to the importing file
  • .goon extension is optional
  • Imported files are evaluated and their final expression is returned

Built-in Functions

map(list, function)

Apply a function to each element of a list.

let nums = [1..3];
let doubled = map(nums, (n) => n);  // [1, 2, 3] (identity)

let keys = map([1..9], (n) => {
    mod = "super";
    key = n;
    cmd = "workspace ${n}";
});

Constraints

  1. No arithmetic: Goon does not have +, -, *, / operators
  2. No comparison: No ==, <, > operators
  3. No recursion: Functions cannot call themselves
  4. Immutable: Values cannot be reassigned after binding

Output

Goon evaluates to a single value, typically a record, which is serialized to JSON.

let name = "myapp";
let version = 1;

{
    name = name;
    version = version;
    enabled = true;
}

Output:

{
  "name": "myapp",
  "version": 1,
  "enabled": true
}

CLI Usage

# Evaluate and output JSON
goon eval config.goon

# Pretty-print output
goon eval config.goon --pretty

# Check syntax without evaluating
goon check config.goon

# Show version
goon --version

C API

#include "goon.h"

// Create context
Goon_Ctx *ctx = goon_create();

// Load and evaluate
if (!goon_load_file(ctx, "config.goon")) {
    const Goon_Error *err = goon_get_error_info(ctx);
    goon_error_print(err);
    return 1;
}

// Get result
Goon_Value *result = goon_eval_result(ctx);

// Convert to JSON
char *json = goon_to_json_pretty(result, 2);
printf("%s\n", json);
free(json);

// Cleanup
goon_destroy(ctx);

Registering Built-in Functions

Goon_Value *my_builtin(Goon_Ctx *ctx, Goon_Value **args, size_t argc) {
    // Implementation
    return goon_int(ctx, 42);
}

goon_register(ctx, "my_func", my_builtin);

Future Considerations

The following features may be added in future versions:

  • Arithmetic operators (+, -, *, /, %)
  • Comparison operators (==, !=, <, >, <=, >=)
  • Logical operators (&&, ||, !)
  • Optional type annotations
  • Hex integer literals (0xFF)
  • Filter in map: map(list, fn, predicate)