mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-04-30 19:09:26 +08:00
* 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>
This commit is contained in:
parent
0fd9cab654
commit
092ec417d2
@ -915,3 +915,12 @@ set_print_handler(fun(s) { my_custom_log(s) })
|
|||||||
|
|
||||||
## Extras
|
## Extras
|
||||||
ChaiScript itself does not provide a link to the math functions defined in `<cmath>`. You can either add them yourself, or use the [ChaiScript_Extras](https://github.com/ChaiScript/ChaiScript_Extras) helper library. (Which also provides some additional string functions.)
|
ChaiScript itself does not provide a link to the math functions defined in `<cmath>`. You can either add them yourself, or use the [ChaiScript_Extras](https://github.com/ChaiScript/ChaiScript_Extras) helper library. (Which also provides some additional string functions.)
|
||||||
|
|
||||||
|
## Grammar Railroad Diagrams
|
||||||
|
|
||||||
|
A formal EBNF grammar for ChaiScript is available in [`grammar/chaiscript.ebnf`](grammar/chaiscript.ebnf). You can visualize it as navigable railroad diagrams by pasting its contents into one of these tools:
|
||||||
|
|
||||||
|
* [rr — Railroad Diagram Generator (IPv6)](https://www.bottlecaps.de/rr/ui)
|
||||||
|
* [rr — Railroad Diagram Generator (IPv4)](https://rr.red-dove.com/ui)
|
||||||
|
|
||||||
|
Open either link, switch to the **Edit Grammar** tab, paste the file contents, then click **View Diagram**.
|
||||||
|
|||||||
177
grammar/chaiscript.ebnf
Normal file
177
grammar/chaiscript.ebnf
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* ChaiScript Grammar — EBNF for Railroad Diagram Generation
|
||||||
|
*
|
||||||
|
* View as navigable railroad diagrams at:
|
||||||
|
* https://www.bottlecaps.de/rr/ui (IPv6)
|
||||||
|
* https://rr.red-dove.com/ui (IPv4)
|
||||||
|
*
|
||||||
|
* Copy and paste this file into the 'Edit Grammar' tab, then
|
||||||
|
* click 'View Diagram'.
|
||||||
|
*
|
||||||
|
* This grammar uses the notation accepted by
|
||||||
|
* https://github.com/GuntherRademacher/rr :
|
||||||
|
* - "::=" as rule separator
|
||||||
|
* - no semicolon at end of rule
|
||||||
|
* - "?" "+" "*" for repetition
|
||||||
|
* - C comments
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ---- Top-level ---- */
|
||||||
|
|
||||||
|
statements ::= ( def | try | if | while | class | for
|
||||||
|
| switch | return | break | continue
|
||||||
|
| equation | block | eol )+
|
||||||
|
|
||||||
|
/* ---- Functions ---- */
|
||||||
|
|
||||||
|
def ::= "def" id ( "::" id )? "(" decl_arg_list ")" eol*
|
||||||
|
( ":" guard )? eol* block
|
||||||
|
|
||||||
|
lambda ::= "fun" ( "[" id_arg_list "]" )? "(" decl_arg_list ")" eol* block
|
||||||
|
|
||||||
|
guard ::= operator
|
||||||
|
|
||||||
|
/* ---- Exception handling ---- */
|
||||||
|
|
||||||
|
try ::= "try" eol* block catch* finally?
|
||||||
|
catch ::= "catch" ( "(" arg ")" )? eol* block
|
||||||
|
finally ::= "finally" eol* block
|
||||||
|
|
||||||
|
/* ---- Control flow ---- */
|
||||||
|
|
||||||
|
if ::= "if" "(" equation ( eol equation )? ")" eol* block
|
||||||
|
( "else" ( if | eol* block ) )*
|
||||||
|
|
||||||
|
while ::= "while" "(" operator ")" eol* block
|
||||||
|
|
||||||
|
for ::= "for" "(" ( for_guards | equation ":" equation ) ")" eol* block
|
||||||
|
for_guards ::= equation eol equation eol equation
|
||||||
|
|
||||||
|
switch ::= "switch" "(" operator ")" eol* "{" ( case | default )+ "}"
|
||||||
|
case ::= "case" "(" operator ")" eol* block
|
||||||
|
default ::= "default" eol* block
|
||||||
|
|
||||||
|
/* ---- Classes ---- */
|
||||||
|
|
||||||
|
class ::= "class" id ( ":" id )? eol* class_block
|
||||||
|
class_block ::= "{" class_statements* "}"
|
||||||
|
class_statements ::= def | var_decl | eol
|
||||||
|
|
||||||
|
/* ---- Blocks & flow keywords ---- */
|
||||||
|
|
||||||
|
block ::= "{" statements* "}"
|
||||||
|
return ::= "return" operator?
|
||||||
|
break ::= "break"
|
||||||
|
continue ::= "continue"
|
||||||
|
|
||||||
|
/* ---- Line termination ---- */
|
||||||
|
|
||||||
|
eol ::= "\n" | "\r\n" | ";"
|
||||||
|
|
||||||
|
/* ---- Equations & operators ---- */
|
||||||
|
|
||||||
|
equation ::= operator ( ( "=" | ":=" | "+=" | "-=" | "*=" | "/="
|
||||||
|
| "%=" | "<<=" | ">>=" | "&=" | "^=" | "|=" )
|
||||||
|
equation )?
|
||||||
|
|
||||||
|
operator ::= prefix
|
||||||
|
| value
|
||||||
|
| operator binary_operator operator
|
||||||
|
| operator "?" operator ":" operator
|
||||||
|
|
||||||
|
prefix ::= ( "++" | "--" | "-" | "+" | "!" | "~" ) operator
|
||||||
|
|
||||||
|
binary_operator ::= "||" | "&&"
|
||||||
|
| "|" | "^" | "&"
|
||||||
|
| "==" | "!="
|
||||||
|
| "<" | "<=" | ">" | ">="
|
||||||
|
| "<<" | ">>"
|
||||||
|
| "+" | "-"
|
||||||
|
| "*" | "/" | "%"
|
||||||
|
|
||||||
|
/* ---- Values & access ---- */
|
||||||
|
|
||||||
|
value ::= var_decl | dot_fun_array | prefix
|
||||||
|
|
||||||
|
dot_fun_array ::= ( lambda | num | quoted_string
|
||||||
|
| single_quoted_string | raw_string
|
||||||
|
| paren_expression | inline_container
|
||||||
|
| id )
|
||||||
|
( fun_call | array_call | dot_access )*
|
||||||
|
|
||||||
|
fun_call ::= "(" arg_list ")"
|
||||||
|
array_call ::= "[" operator "]"
|
||||||
|
dot_access ::= "." id
|
||||||
|
|
||||||
|
/* ---- Variable declarations ---- */
|
||||||
|
|
||||||
|
var_decl ::= ( "auto" | "var" | "const" ) ( reference | id )
|
||||||
|
| "global" ( reference | id )
|
||||||
|
| "attr" id ( "::" id )?
|
||||||
|
|
||||||
|
reference ::= "&" id
|
||||||
|
|
||||||
|
/* ---- Parenthesised & inline containers ---- */
|
||||||
|
|
||||||
|
paren_expression ::= "(" operator ")"
|
||||||
|
|
||||||
|
inline_container ::= "[" container_arg_list "]"
|
||||||
|
container_arg_list ::= value_range
|
||||||
|
| map_pair ( "," map_pair )*
|
||||||
|
| operator ( "," operator )*
|
||||||
|
|
||||||
|
value_range ::= operator ".." operator
|
||||||
|
map_pair ::= operator ":" operator
|
||||||
|
|
||||||
|
/* ---- String literals ---- */
|
||||||
|
|
||||||
|
quoted_string ::= '"' ( char | escape | interpolation )* '"'
|
||||||
|
single_quoted_string ::= "'" ( char | escape ) "'"
|
||||||
|
raw_string ::= 'R"' delimiter? "(" char* ")" delimiter? '"'
|
||||||
|
delimiter ::= [a-zA-Z0-9_]+
|
||||||
|
interpolation ::= "${" equation "}"
|
||||||
|
|
||||||
|
/* ---- Escape sequences ---- */
|
||||||
|
|
||||||
|
escape ::= "\" ( "'" | '"' | "?" | "\" | "a" | "b"
|
||||||
|
| "f" | "n" | "r" | "t" | "v" | "$"
|
||||||
|
| "0"
|
||||||
|
| "x" hex_digit+
|
||||||
|
| "u" hex_digit hex_digit hex_digit hex_digit
|
||||||
|
| "U" hex_digit hex_digit hex_digit hex_digit
|
||||||
|
hex_digit hex_digit hex_digit hex_digit
|
||||||
|
| octal_digit+ )
|
||||||
|
|
||||||
|
/* ---- Argument lists ---- */
|
||||||
|
|
||||||
|
id_arg_list ::= id ( "," id )*
|
||||||
|
decl_arg_list ::= ( arg ( "," arg )* )?
|
||||||
|
arg_list ::= ( equation ( "," equation )* )?
|
||||||
|
arg ::= id id?
|
||||||
|
|
||||||
|
/* ---- Identifiers ---- */
|
||||||
|
|
||||||
|
id ::= ( [a-zA-Z_] [a-zA-Z0-9_]* )
|
||||||
|
| ( "`" [^`]+ "`" )
|
||||||
|
| "true" | "false"
|
||||||
|
| "Infinity" | "NaN"
|
||||||
|
| "_"
|
||||||
|
| "__LINE__" | "__FILE__" | "__FUNC__" | "__CLASS__"
|
||||||
|
|
||||||
|
/* ---- Numeric literals ---- */
|
||||||
|
|
||||||
|
num ::= hex | binary | float | integer
|
||||||
|
|
||||||
|
hex ::= "0" ( "x" | "X" ) [0-9a-fA-F]+ int_suffix*
|
||||||
|
binary ::= "0" ( "b" | "B" ) [01]+ int_suffix*
|
||||||
|
float ::= [0-9]+ "." [0-9]+ ( ( "e" | "E" ) ( "+" | "-" )? [0-9]+ )? float_suffix?
|
||||||
|
integer ::= [0-9]+ int_suffix*
|
||||||
|
|
||||||
|
int_suffix ::= "l" | "L" | "ll" | "LL" | "u" | "U"
|
||||||
|
float_suffix ::= "l" | "L" | "f" | "F"
|
||||||
|
|
||||||
|
/* ---- Character classes ---- */
|
||||||
|
|
||||||
|
octal_digit ::= [0-7]
|
||||||
|
hex_digit ::= [0-9a-fA-F]
|
||||||
|
char ::= [^"\]
|
||||||
10
readme.md
10
readme.md
@ -91,6 +91,16 @@ the doxygen documentation in the build folder or see the website
|
|||||||
http://www.chaiscript.com.
|
http://www.chaiscript.com.
|
||||||
|
|
||||||
|
|
||||||
|
Grammar
|
||||||
|
=======
|
||||||
|
|
||||||
|
A formal EBNF grammar for ChaiScript is available in
|
||||||
|
[grammar/chaiscript.ebnf](grammar/chaiscript.ebnf). To view it as a railroad
|
||||||
|
diagram, paste the grammar into
|
||||||
|
[mingodad's railroad diagram generator](https://mingodad.github.io/plgh/json2ebnf.html)
|
||||||
|
or [bottlecaps.de/rr](https://www.bottlecaps.de/rr/ui).
|
||||||
|
|
||||||
|
|
||||||
The shortest complete example possible follows:
|
The shortest complete example possible follows:
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
|
|||||||
219
unittests/grammar_constructs.chai
Normal file
219
unittests/grammar_constructs.chai
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
// 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)
|
||||||
Loading…
x
Reference in New Issue
Block a user