ChaiScript/unittests/grammar_constructs.chai
leftibot 092ec417d2
Fix #628: Grammar railroad diagram (#673)
* Fix #628: Add EBNF grammar for railroad diagram generation

Add a formal EBNF grammar file (grammar/chaiscript.ebnf) that can be
pasted into rr (https://www.bottlecaps.de/rr/ui) to produce navigable
railroad diagrams of ChaiScript's syntax. The grammar was validated
against the parser implementation and covers all language constructs
including class inheritance, guard conditions, raw strings, and const
declarations that were missing from the original proposal. A reference
section was added to the cheatsheet, and a regression test exercises
every documented grammar construct.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Address review: add grammar railroad diagram link to README

Add a Grammar section to readme.md linking to the EBNF grammar file
and to mingodad's railroad diagram generator for direct viewing.

Requested by @lefticus in PR #673 review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: leftibot <leftibot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 10:59:48 -06:00

220 lines
4.4 KiB
ChaiScript

// Regression test: exercises grammar constructs documented in grammar/chaiscript.ebnf
// --- Variable declarations ---
var a = 1
auto b = 2
global c = 3
const d = 42
assert_equal(1, a)
assert_equal(2, b)
assert_equal(3, c)
assert_equal(42, d)
// --- Reference variables ---
var orig = 10
var &ref = orig
ref = 20
assert_equal(20, orig)
// --- Numeric literals ---
assert_equal(255, 0xFF)
assert_equal(255, 0xff)
assert_equal(5, 0b101)
assert_equal(42, 42)
assert_equal(3.14, 3.14)
// --- String interpolation ---
var name = "world"
assert_equal("hello world", "hello ${name}")
// --- Escape sequences ---
assert_equal("\n", "\n")
assert_equal("\t", "\t")
// --- Single-quoted char ---
assert_equal('A', 'A')
// --- Operators and precedence ---
assert_equal(7, 1 + 2 * 3)
assert_equal(true, 5 > 3 && 2 < 4)
assert_equal(true, false || true)
assert_equal(6, 3 << 1)
assert_equal(1, 3 >> 1)
assert_equal(5, 7 & 5)
assert_equal(7, 5 | 3)
assert_equal(6, 5 ^ 3)
assert_equal(-1, ~0)
// --- Ternary operator ---
assert_equal("yes", true ? "yes" : "no")
assert_equal("no", false ? "yes" : "no")
// --- Prefix operators ---
var x = 5
++x
assert_equal(6, x)
--x
assert_equal(5, x)
assert_equal(true, !false)
// --- Assignment operators ---
var v = 10
v += 5; assert_equal(15, v)
v -= 3; assert_equal(12, v)
v *= 2; assert_equal(24, v)
v /= 4; assert_equal(6, v)
v %= 4; assert_equal(2, v)
v <<= 2; assert_equal(8, v)
v >>= 1; assert_equal(4, v)
v |= 3; assert_equal(7, v)
v &= 5; assert_equal(5, v)
v ^= 3; assert_equal(6, v)
// --- Lambda ---
var add = fun(a, b) { a + b }
assert_equal(5, add(2, 3))
// --- Lambda with capture ---
var captured = 100
var get_captured = fun[captured]() { captured }
assert_equal(100, get_captured())
// --- Function definition ---
def multiply(a, b) { a * b }
assert_equal(12, multiply(3, 4))
// --- Guard condition on function ---
def abs_val(x) : x >= 0 { x }
def abs_val(x) : x < 0 { -x }
assert_equal(5, abs_val(5))
assert_equal(5, abs_val(-5))
// --- Class definition ---
class Animal
{
attr sound
def Animal(s) { this.sound = s }
def speak() { this.sound }
}
var dog = Animal("woof")
assert_equal("woof", dog.speak())
// --- Class with inheritance ---
class Puppy : Animal
{
attr name
def Puppy(n, s) { this.name = n; this.sound = s }
def greet() { to_string(this.name) + " says " + to_string(this.speak()) }
}
var p = Puppy("Rex", "yip")
assert_equal("Rex says yip", p.greet())
// --- Control flow: if/else ---
var result = ""
if (true) { result = "yes" } else { result = "no" }
assert_equal("yes", result)
// --- Control flow: while ---
var counter = 0
while (counter < 3) { ++counter }
assert_equal(3, counter)
// --- Control flow: for ---
var sum = 0
for (var i = 0; i < 5; ++i) { sum += i }
assert_equal(10, sum)
// --- Control flow: ranged for ---
var items = [10, 20, 30]
var total = 0
for (item : items) { total += item }
assert_equal(60, total)
// --- Switch/case ---
def classify(n) {
var label = ""
switch (n) {
case (1) { label = "one"; break }
case (2) { label = "two"; break }
default { label = "other" }
}
return label
}
assert_equal("one", classify(1))
assert_equal("two", classify(2))
assert_equal("other", classify(99))
// --- Try/catch/finally ---
var caught = false
var finalized = false
try {
throw("oops")
} catch (e) {
caught = true
} finally {
finalized = true
}
assert_true(caught)
assert_true(finalized)
// --- Inline containers ---
var vec = [1, 2, 3]
assert_equal(3, vec.size())
var m = ["a": 1, "b": 2]
assert_equal(1, m["a"])
var r = [1, 2, 3, 4, 5]
assert_equal(5, r.size())
// --- Dot access chaining ---
assert_equal(3, [1, 2, 3].size())
// --- Array access ---
var arr = [10, 20, 30]
assert_equal(20, arr[1])
// --- Backtick identifier ---
var `my var` = 42
assert_equal(42, `my var`)
// --- Special identifiers ---
assert_equal(true, true)
assert_equal(false, false)
// --- Nested block ---
var block_result = 0
{ block_result = 42 }
assert_equal(42, block_result)
// --- Break and continue ---
var break_sum = 0
for (var i = 0; i < 10; ++i) {
if (i == 5) { break }
break_sum += i
}
assert_equal(10, break_sum)
var cont_sum = 0
for (var i = 0; i < 5; ++i) {
if (i == 2) { continue }
cont_sum += i
}
assert_equal(8, cont_sum)
// --- Return from function ---
def early_return(n) {
if (n > 0) { return "positive" }
return "non-positive"
}
assert_equal("positive", early_return(1))
assert_equal("non-positive", early_return(-1))
// --- Colon assignment ---
var ca = 0
ca := 99
assert_equal(99, ca)