fix: remove legacy bnf
This commit is contained in:
280
Cargo.lock
generated
280
Cargo.lock
generated
@@ -2,12 +2,6 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "allocator-api2"
|
|
||||||
version = "0.2.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -64,26 +58,6 @@ version = "1.0.102"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bnf"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "35b77b055f8cb1d566fa4ef55bc699f60eefb17927dc25fa454a05b6fabf7aa4"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom",
|
|
||||||
"hashbrown",
|
|
||||||
"nom",
|
|
||||||
"rand",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bumpalo"
|
|
||||||
version = "3.20.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.56"
|
version = "1.2.56"
|
||||||
@@ -94,12 +68,6 @@ dependencies = [
|
|||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.6.0"
|
version = "4.6.0"
|
||||||
@@ -152,49 +120,12 @@ version = "1.15.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "equivalent"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foldhash"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "getrandom"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"js-sys",
|
|
||||||
"libc",
|
|
||||||
"r-efi",
|
|
||||||
"wasip2",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.16.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
|
||||||
dependencies = [
|
|
||||||
"allocator-api2",
|
|
||||||
"equivalent",
|
|
||||||
"foldhash",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -231,22 +162,6 @@ version = "1.70.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "1.0.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "js-sys"
|
|
||||||
version = "0.3.91"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
|
|
||||||
dependencies = [
|
|
||||||
"once_cell",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -273,21 +188,6 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom"
|
|
||||||
version = "8.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.4"
|
version = "1.21.4"
|
||||||
@@ -300,15 +200,6 @@ version = "1.70.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ppv-lite86"
|
|
||||||
version = "0.2.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
|
||||||
dependencies = [
|
|
||||||
"zerocopy",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.106"
|
version = "1.0.106"
|
||||||
@@ -327,41 +218,6 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "r-efi"
|
|
||||||
version = "5.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand"
|
|
||||||
version = "0.9.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
|
||||||
dependencies = [
|
|
||||||
"rand_chacha",
|
|
||||||
"rand_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_chacha"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
|
||||||
dependencies = [
|
|
||||||
"ppv-lite86",
|
|
||||||
"rand_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.9.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-lite"
|
name = "regex-lite"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
@@ -376,67 +232,17 @@ version = "0.1.0"
|
|||||||
name = "rust-langrpg"
|
name = "rust-langrpg"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bnf",
|
|
||||||
"clap",
|
"clap",
|
||||||
"either",
|
"either",
|
||||||
"inkwell",
|
"inkwell",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustversion"
|
|
||||||
version = "1.0.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.27"
|
version = "1.0.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
||||||
dependencies = [
|
|
||||||
"serde_core",
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_core"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.149"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
|
||||||
dependencies = [
|
|
||||||
"itoa",
|
|
||||||
"memchr",
|
|
||||||
"serde",
|
|
||||||
"serde_core",
|
|
||||||
"zmij",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@@ -492,60 +298,6 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasip2"
|
|
||||||
version = "1.0.2+wasi-0.2.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
|
||||||
dependencies = [
|
|
||||||
"wit-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen"
|
|
||||||
version = "0.2.114"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"once_cell",
|
|
||||||
"rustversion",
|
|
||||||
"wasm-bindgen-macro",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro"
|
|
||||||
version = "0.2.114"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
|
|
||||||
dependencies = [
|
|
||||||
"quote",
|
|
||||||
"wasm-bindgen-macro-support",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro-support"
|
|
||||||
version = "0.2.114"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
|
|
||||||
dependencies = [
|
|
||||||
"bumpalo",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-shared"
|
|
||||||
version = "0.2.114"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -560,35 +312,3 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-bindgen"
|
|
||||||
version = "0.51.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy"
|
|
||||||
version = "0.8.42"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3"
|
|
||||||
dependencies = [
|
|
||||||
"zerocopy-derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy-derive"
|
|
||||||
version = "0.8.42"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zmij"
|
|
||||||
version = "1.0.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
|
||||||
|
|||||||
@@ -23,10 +23,6 @@ default-run = "rust-langrpg"
|
|||||||
name = "rust-langrpg"
|
name = "rust-langrpg"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "demo"
|
|
||||||
path = "src/bin/demo.rs"
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Library (rlib — used by the binaries and tests)
|
# Library (rlib — used by the binaries and tests)
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
@@ -41,7 +37,6 @@ crate-type = ["rlib"]
|
|||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bnf = "0.6"
|
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
either = "1"
|
either = "1"
|
||||||
inkwell = { version = "0.8", features = ["llvm21-1"] }
|
inkwell = { version = "0.8", features = ["llvm21-1"] }
|
||||||
|
|||||||
89
README.md
89
README.md
@@ -12,42 +12,24 @@ Language reference: https://www.ibm.com/docs/en/i/7.5.0?topic=introduction-overv
|
|||||||
cargo build --release
|
cargo build --release
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running
|
### Compiling an RPG IV program
|
||||||
|
|
||||||
The compiler ships as a standalone binary that loads the embedded BNF grammar, builds a parser, and runs a suite of RPG IV snippet examples to demonstrate the grammar in action:
|
|
||||||
|
|
||||||
```rust-langrpg/README.md
|
```rust-langrpg/README.md
|
||||||
cargo run --bin demo
|
cargo run --release -- -o hello hello.rpg
|
||||||
|
./hello
|
||||||
```
|
```
|
||||||
|
|
||||||
You will see output similar to:
|
You will see output similar to:
|
||||||
|
|
||||||
```rust-langrpg/README.md
|
```rust-langrpg/README.md
|
||||||
=== RPG IV Free-Format Parser ===
|
DSPLY Hello, World!
|
||||||
|
|
||||||
[grammar] Loaded successfully.
|
|
||||||
[parser] Built successfully (all non-terminals resolved).
|
|
||||||
|
|
||||||
=== Parsing Examples ===
|
|
||||||
|
|
||||||
┌─ simple identifier (identifier) ─────────────────────
|
|
||||||
│ source : "myVar"
|
|
||||||
│ result : OK
|
|
||||||
└──────────────────────────────────────────────
|
|
||||||
...
|
|
||||||
=== Summary ===
|
|
||||||
total : 42
|
|
||||||
matched : 42
|
|
||||||
failed : 0
|
|
||||||
|
|
||||||
All examples parsed successfully.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Hello World in RPG IV
|
### Hello World in RPG IV
|
||||||
|
|
||||||
The following is a complete Hello World program written in RPG IV free-format syntax, as understood by this parser:
|
The following is a complete Hello World program written in RPG IV free-format syntax:
|
||||||
|
|
||||||
hello.rpg:
|
`hello.rpg`:
|
||||||
|
|
||||||
```rust-langrpg/README.md
|
```rust-langrpg/README.md
|
||||||
CTL-OPT DFTACTGRP(*NO);
|
CTL-OPT DFTACTGRP(*NO);
|
||||||
@@ -68,10 +50,22 @@ Breaking it down:
|
|||||||
- `DSPLY greeting;` — displays the value of `greeting` to the operator message queue
|
- `DSPLY greeting;` — displays the value of `greeting` to the operator message queue
|
||||||
- `RETURN;` — returns from the procedure
|
- `RETURN;` — returns from the procedure
|
||||||
|
|
||||||
To validate this program, execute the compiler to build the data:
|
### Compiler options
|
||||||
|
|
||||||
```sh
|
```
|
||||||
cargo run --release -- -o main hello.rpg
|
rust-langrpg [OPTIONS] <SOURCES>...
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<SOURCES>... RPG IV source file(s) to compile
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-o <OUTPUT> Output executable path [default: a.out]
|
||||||
|
--emit-ir Print LLVM IR to stdout instead of producing a binary
|
||||||
|
-O <LEVEL> Optimisation level 0-3 [default: 0]
|
||||||
|
--no-link Produce a .o object file, skip linking
|
||||||
|
--runtime <PATH> Path to librpgrt.so [default: auto-detect]
|
||||||
|
-h, --help Print help
|
||||||
|
-V, --version Print version
|
||||||
```
|
```
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
@@ -90,38 +84,28 @@ RPG IV source (.rpg)
|
|||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
┌─────────────────────────────────────────┐
|
┌─────────────────────────────────────────┐
|
||||||
│ 1. BNF validation (bnf crate) │
|
│ 1. Parsing + lowering (src/lower.rs) │
|
||||||
│ src/rpg.bnf — embedded at compile │
|
│ Hand-written tokenizer + │
|
||||||
│ time via include_str! │
|
│ recursive-descent parser │
|
||||||
└────────────────┬────────────────────────┘
|
│ → typed AST (src/ast.rs) │
|
||||||
│ parse tree (validation only)
|
|
||||||
▼
|
|
||||||
┌─────────────────────────────────────────┐
|
|
||||||
│ 2. Lowering pass (src/lower.rs) │
|
|
||||||
│ Hand-written recursive-descent │
|
|
||||||
│ tokenizer + parser → typed AST │
|
|
||||||
└────────────────┬────────────────────────┘
|
└────────────────┬────────────────────────┘
|
||||||
│ ast::Program
|
│ ast::Program
|
||||||
▼
|
▼
|
||||||
┌─────────────────────────────────────────┐
|
┌─────────────────────────────────────────┐
|
||||||
│ 3. LLVM code generation (src/codegen.rs│
|
│ 2. LLVM code generation (src/codegen.rs│
|
||||||
│ inkwell bindings → LLVM IR module │
|
│ inkwell bindings → LLVM IR module │
|
||||||
└────────────────┬────────────────────────┘
|
└────────────────┬────────────────────────┘
|
||||||
│ .o object file
|
│ .o object file
|
||||||
▼
|
▼
|
||||||
┌─────────────────────────────────────────┐
|
┌─────────────────────────────────────────┐
|
||||||
│ 4. Linking (cc + librpgrt.so) │
|
│ 3. Linking (cc + librpgrt.so) │
|
||||||
│ Produces a standalone Linux ELF │
|
│ Produces a standalone Linux ELF │
|
||||||
└─────────────────────────────────────────┘
|
└─────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
### Stage 1 — BNF validation (`src/rpg.bnf` + `bnf` crate)
|
### Stage 1 — Parsing and lowering to a typed AST (`src/lower.rs`)
|
||||||
|
|
||||||
The RPG IV free-format grammar is encoded in BNF notation in `src/rpg.bnf` and embedded at compile time with `include_str!`. At startup the compiler parses the grammar with the [`bnf`](https://docs.rs/bnf/latest/bnf/) crate to build a `GrammarParser`. Each source file is validated against the top-level `<program>` rule before any further processing. This stage acts as a gate: malformed source is rejected early with a clear parse error.
|
A hand-written tokenizer and recursive-descent parser converts the raw source text directly into the typed `Program` AST defined in `src/ast.rs`. RPG IV keywords are case-insensitive and the parser handles mixed-case source naturally.
|
||||||
|
|
||||||
### Stage 2 — Lowering to a typed AST (`src/lower.rs`)
|
|
||||||
|
|
||||||
The BNF parser only validates structure; it does not produce a typed tree suitable for code generation. A hand-written tokenizer and recursive-descent parser in `lower.rs` converts the raw source text into the typed `Program` AST defined in `src/ast.rs`.
|
|
||||||
|
|
||||||
The AST covers the full language surface that the compiler handles:
|
The AST covers the full language surface that the compiler handles:
|
||||||
|
|
||||||
@@ -133,7 +117,7 @@ The AST covers the full language surface that the compiler handles:
|
|||||||
|
|
||||||
Unrecognised constructs produce `Statement::Unimplemented` or placeholder declaration variants rather than hard errors, so the compiler continues to lower the parts it understands.
|
Unrecognised constructs produce `Statement::Unimplemented` or placeholder declaration variants rather than hard errors, so the compiler continues to lower the parts it understands.
|
||||||
|
|
||||||
### Stage 3 — LLVM code generation (`src/codegen.rs`)
|
### Stage 2 — LLVM code generation (`src/codegen.rs`)
|
||||||
|
|
||||||
The typed `Program` is handed to the code generator, which uses [`inkwell`](https://crates.io/crates/inkwell) (safe Rust bindings to LLVM 21) to build an LLVM IR module:
|
The typed `Program` is handed to the code generator, which uses [`inkwell`](https://crates.io/crates/inkwell) (safe Rust bindings to LLVM 21) to build an LLVM IR module:
|
||||||
|
|
||||||
@@ -147,7 +131,7 @@ The typed `Program` is handed to the code generator, which uses [`inkwell`](http
|
|||||||
|
|
||||||
The module is then compiled to a native `.o` object file for the host target via LLVM's target machine API, with optional optimisation passes (`-O0` through `-O3`).
|
The module is then compiled to a native `.o` object file for the host target via LLVM's target machine API, with optional optimisation passes (`-O0` through `-O3`).
|
||||||
|
|
||||||
### Stage 4 — Linking
|
### Stage 3 — Linking
|
||||||
|
|
||||||
The object file is linked into a standalone ELF executable by invoking the system C compiler (`cc`). The executable is linked against `librpgrt.so`.
|
The object file is linked into a standalone ELF executable by invoking the system C compiler (`cc`). The executable is linked against `librpgrt.so`.
|
||||||
|
|
||||||
@@ -180,17 +164,14 @@ DSPLY Hello, World!
|
|||||||
```
|
```
|
||||||
rust-langrpg/
|
rust-langrpg/
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── rpg.bnf — RPG IV free-format BNF grammar (embedded at compile time)
|
│ ├── lib.rs — Library root (re-exports ast, lower, codegen)
|
||||||
│ ├── lib.rs — Grammar loader and demo helpers
|
|
||||||
│ ├── ast.rs — Typed AST node definitions
|
│ ├── ast.rs — Typed AST node definitions
|
||||||
│ ├── lower.rs — Tokenizer + recursive-descent lowering pass
|
│ ├── lower.rs — Tokenizer + recursive-descent parser + lowering pass
|
||||||
│ ├── codegen.rs — LLVM IR code generation (inkwell)
|
│ ├── codegen.rs — LLVM IR code generation (inkwell)
|
||||||
│ ├── main.rs — Compiler CLI (clap) + linker invocation
|
│ └── main.rs — Compiler CLI (clap) + linker invocation
|
||||||
│ └── bin/
|
|
||||||
│ └── demo.rs — Grammar demo binary
|
|
||||||
├── rpgrt/
|
├── rpgrt/
|
||||||
│ └── src/
|
│ └── src/
|
||||||
│ └── lib.rs — Runtime library (librpgrt.so)
|
│ └── lib.rs — Runtime library (librpgrt.so)
|
||||||
├── hello.rpg — Hello World example program
|
├── hello.rpg — Hello World example program
|
||||||
└── count.rpg — Counting loop example program
|
└── fib.rpg — Fibonacci sequence example program
|
||||||
```
|
```
|
||||||
|
|||||||
318
src/bin/demo.rs
318
src/bin/demo.rs
@@ -1,318 +0,0 @@
|
|||||||
//! demo — runs the built-in RPG IV snippet suite and prints results.
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//! cargo run --bin demo
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
use bnf::Term;
|
|
||||||
use rust_langrpg::{load_grammar, run_example, Example};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// ── 1. Load grammar ────────────────────────────────────────────────────
|
|
||||||
println!("=== RPG IV Free-Format Parser ===");
|
|
||||||
println!();
|
|
||||||
|
|
||||||
let grammar = match load_grammar() {
|
|
||||||
Ok(g) => {
|
|
||||||
println!("[grammar] Loaded successfully.");
|
|
||||||
g
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("[grammar] Failed to parse BNF: {}", e);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ── 2. Build parser ────────────────────────────────────────────────────
|
|
||||||
let parser = match grammar.build_parser() {
|
|
||||||
Ok(p) => {
|
|
||||||
println!("[parser] Built successfully (all non-terminals resolved).");
|
|
||||||
p
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("[parser] Failed to build: {}", e);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
println!();
|
|
||||||
|
|
||||||
// ── 3. Example snippets ─────────────────────────────────────────────────
|
|
||||||
println!("=== Parsing Examples ===");
|
|
||||||
println!();
|
|
||||||
|
|
||||||
let examples: &[Example] = &[
|
|
||||||
// ── Identifiers ─────────────────────────────────────────────────────
|
|
||||||
Example {
|
|
||||||
label: "simple identifier",
|
|
||||||
rule: "identifier",
|
|
||||||
src: "myVar",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "identifier with digits and underscore",
|
|
||||||
rule: "identifier",
|
|
||||||
src: "calc_Total2",
|
|
||||||
},
|
|
||||||
// ── Literals ────────────────────────────────────────────────────────
|
|
||||||
Example {
|
|
||||||
label: "integer literal",
|
|
||||||
rule: "integer-literal",
|
|
||||||
src: "42",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "numeric literal (decimal)",
|
|
||||||
rule: "numeric-literal",
|
|
||||||
src: "3.14",
|
|
||||||
},
|
|
||||||
// ── Named constants ──────────────────────────────────────────────────
|
|
||||||
Example {
|
|
||||||
label: "named constant *ON",
|
|
||||||
rule: "named-constant",
|
|
||||||
src: "*ON",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "named constant *BLANKS",
|
|
||||||
rule: "named-constant",
|
|
||||||
src: "*BLANKS",
|
|
||||||
},
|
|
||||||
// ── Type specifications ──────────────────────────────────────────────
|
|
||||||
Example {
|
|
||||||
label: "CHAR type spec",
|
|
||||||
rule: "type-spec",
|
|
||||||
src: "CHAR(10)",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "PACKED type spec",
|
|
||||||
rule: "type-spec",
|
|
||||||
src: "PACKED(7:2)",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "INT type spec",
|
|
||||||
rule: "type-spec",
|
|
||||||
src: "INT(10)",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "DATE type spec",
|
|
||||||
rule: "type-spec",
|
|
||||||
src: "DATE",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "IND (indicator) type spec",
|
|
||||||
rule: "type-spec",
|
|
||||||
src: "IND",
|
|
||||||
},
|
|
||||||
// ── Declarations ─────────────────────────────────────────────────────
|
|
||||||
Example {
|
|
||||||
label: "standalone character variable",
|
|
||||||
rule: "standalone-decl",
|
|
||||||
src: "DCL-S myName CHAR(25);",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "standalone integer with initialiser",
|
|
||||||
rule: "standalone-decl",
|
|
||||||
src: "DCL-S counter INT(10) INZ(0);",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "constant declaration",
|
|
||||||
rule: "constant-decl",
|
|
||||||
src: "DCL-C MAX_SIZE CONST(100);",
|
|
||||||
},
|
|
||||||
// ── Control-option specification ─────────────────────────────────────
|
|
||||||
Example {
|
|
||||||
label: "CTL-OPT NOMAIN",
|
|
||||||
rule: "control-spec",
|
|
||||||
src: "CTL-OPT NOMAIN;",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "CTL-OPT DFTACTGRP(*NO)",
|
|
||||||
rule: "control-spec",
|
|
||||||
src: "CTL-OPT DFTACTGRP(*NO);",
|
|
||||||
},
|
|
||||||
// ── Statements ───────────────────────────────────────────────────────
|
|
||||||
Example {
|
|
||||||
label: "assignment statement",
|
|
||||||
rule: "assign-stmt",
|
|
||||||
src: "result=a+b;",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "EVAL assignment",
|
|
||||||
rule: "assign-stmt",
|
|
||||||
src: "EVAL result=42;",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "RETURN with value",
|
|
||||||
rule: "return-stmt",
|
|
||||||
src: "RETURN result;",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "bare RETURN",
|
|
||||||
rule: "return-stmt",
|
|
||||||
src: "RETURN;",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "LEAVE statement",
|
|
||||||
rule: "leave-stmt",
|
|
||||||
src: "LEAVE;",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "ITER statement",
|
|
||||||
rule: "iter-stmt",
|
|
||||||
src: "ITER;",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "EXSR call",
|
|
||||||
rule: "exsr-stmt",
|
|
||||||
src: "EXSR calcTotals;",
|
|
||||||
},
|
|
||||||
// ── I/O statements ───────────────────────────────────────────────────
|
|
||||||
Example {
|
|
||||||
label: "READ file",
|
|
||||||
rule: "read-stmt",
|
|
||||||
src: "READ myFile;",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "WRITE record",
|
|
||||||
rule: "write-stmt",
|
|
||||||
src: "WRITE outputRec;",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "CHAIN key into file",
|
|
||||||
rule: "chain-stmt",
|
|
||||||
src: "CHAIN keyFld myFile;",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "SETLL to beginning",
|
|
||||||
rule: "setll-stmt",
|
|
||||||
src: "SETLL *START myFile;",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "OPEN file",
|
|
||||||
rule: "open-stmt",
|
|
||||||
src: "OPEN myFile;",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "CLOSE file",
|
|
||||||
rule: "close-stmt",
|
|
||||||
src: "CLOSE myFile;",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "CLOSE *ALL",
|
|
||||||
rule: "close-stmt",
|
|
||||||
src: "CLOSE *ALL;",
|
|
||||||
},
|
|
||||||
// ── Expressions ──────────────────────────────────────────────────────
|
|
||||||
Example {
|
|
||||||
label: "simple addition expression",
|
|
||||||
rule: "expression",
|
|
||||||
src: "a+b",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "comparison expression",
|
|
||||||
rule: "expression",
|
|
||||||
src: "x>=10",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "NOT expression",
|
|
||||||
rule: "expression",
|
|
||||||
src: "NOT flag",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "combined AND / OR",
|
|
||||||
rule: "expression",
|
|
||||||
src: "a=1ANDb=2",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "parenthesised expression",
|
|
||||||
rule: "expression",
|
|
||||||
src: "(x+y)",
|
|
||||||
},
|
|
||||||
// ── Built-in functions ───────────────────────────────────────────────
|
|
||||||
Example {
|
|
||||||
label: "%LEN built-in",
|
|
||||||
rule: "built-in-function",
|
|
||||||
src: "%LEN(myField)",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "%TRIM built-in",
|
|
||||||
rule: "built-in-function",
|
|
||||||
src: "%TRIM(name)",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "%EOF built-in (no arg)",
|
|
||||||
rule: "built-in-function",
|
|
||||||
src: "%EOF()",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "%SUBST built-in (3-arg)",
|
|
||||||
rule: "built-in-function",
|
|
||||||
src: "%SUBST(str:1:5)",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "%DEC built-in",
|
|
||||||
rule: "built-in-function",
|
|
||||||
src: "%DEC(value:9:2)",
|
|
||||||
},
|
|
||||||
// ── Duration codes ───────────────────────────────────────────────────
|
|
||||||
Example {
|
|
||||||
label: "duration code *DAYS",
|
|
||||||
rule: "duration-code",
|
|
||||||
src: "*DAYS",
|
|
||||||
},
|
|
||||||
// ── Date / time formats ──────────────────────────────────────────────
|
|
||||||
Example {
|
|
||||||
label: "date format *ISO",
|
|
||||||
rule: "date-format",
|
|
||||||
src: "*ISO",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "time format *HMS",
|
|
||||||
rule: "time-format",
|
|
||||||
src: "*HMS",
|
|
||||||
},
|
|
||||||
// ── Qualified names ──────────────────────────────────────────────────
|
|
||||||
Example {
|
|
||||||
label: "simple qualified name",
|
|
||||||
rule: "qualified-name",
|
|
||||||
src: "myDs.field",
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
label: "deeply qualified name",
|
|
||||||
rule: "qualified-name",
|
|
||||||
src: "ds.subDs.leaf",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let total = examples.len();
|
|
||||||
let mut ok = 0usize;
|
|
||||||
let mut bad = 0usize;
|
|
||||||
|
|
||||||
for ex in examples {
|
|
||||||
let target = Term::Nonterminal(ex.rule.to_string());
|
|
||||||
let matched = parser
|
|
||||||
.parse_input_starting_with(ex.src, &target)
|
|
||||||
.next()
|
|
||||||
.is_some();
|
|
||||||
|
|
||||||
if matched {
|
|
||||||
ok += 1;
|
|
||||||
} else {
|
|
||||||
bad += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
run_example(&parser, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── 4. Summary ─────────────────────────────────────────────────────────
|
|
||||||
println!("=== Summary ===");
|
|
||||||
println!(" total : {}", total);
|
|
||||||
println!(" matched : {}", ok);
|
|
||||||
println!(" failed : {}", bad);
|
|
||||||
println!();
|
|
||||||
|
|
||||||
if bad == 0 {
|
|
||||||
println!("All examples parsed successfully.");
|
|
||||||
} else {
|
|
||||||
eprintln!("{} example(s) did not parse — check the BNF or the snippet.", bad);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
401
src/lib.rs
401
src/lib.rs
@@ -1,404 +1,9 @@
|
|||||||
//! rust-langrpg — RPG IV free-format parser library
|
//! rust-langrpg — RPG IV free-format parser library
|
||||||
//!
|
//!
|
||||||
//! Loads the BNF grammar embedded at compile time, builds a [`bnf::GrammarParser`],
|
//! Provides the typed AST ([`ast`]), recursive-descent parser and lowering
|
||||||
//! and exposes helpers used by both the compiler binary and the demo binary.
|
//! pass ([`lower`]), and LLVM code-generator ([`codegen`]) used by the
|
||||||
//!
|
//! compiler pipeline.
|
||||||
//! Also provides the typed AST ([`ast`]), BNF-to-AST lowering pass ([`lower`]),
|
|
||||||
//! and LLVM code-generator ([`codegen`]) used by the compiler pipeline.
|
|
||||||
|
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
pub mod lower;
|
pub mod lower;
|
||||||
pub mod codegen;
|
pub mod codegen;
|
||||||
|
|
||||||
use bnf::{Grammar, Term};
|
|
||||||
|
|
||||||
/// The RPG IV BNF grammar, embedded at compile time from `src/rpg.bnf`.
|
|
||||||
const RPG_BNF: &str = include_str!("rpg.bnf");
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Public API
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// Load and validate the RPG IV grammar.
|
|
||||||
///
|
|
||||||
/// Returns `Err` if the BNF source is malformed.
|
|
||||||
pub fn load_grammar() -> Result<Grammar, bnf::Error> {
|
|
||||||
RPG_BNF.parse::<Grammar>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse `source` as the given `rule` (a non-terminal name such as `"statement"`).
|
|
||||||
///
|
|
||||||
/// Returns `Some(parse_tree_string)` for the first successful parse, or `None`
|
|
||||||
/// when the grammar cannot match the input.
|
|
||||||
pub fn parse_as<'a>(
|
|
||||||
parser: &'a bnf::GrammarParser,
|
|
||||||
source: &str,
|
|
||||||
rule: &str,
|
|
||||||
) -> Option<String> {
|
|
||||||
let target = Term::Nonterminal(rule.to_string());
|
|
||||||
parser
|
|
||||||
.parse_input_starting_with(source, &target)
|
|
||||||
.next()
|
|
||||||
.map(|tree| tree.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Demo helpers
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
pub struct Example {
|
|
||||||
pub label: &'static str,
|
|
||||||
pub rule: &'static str,
|
|
||||||
pub src: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_example(parser: &bnf::GrammarParser, ex: &Example) {
|
|
||||||
println!(" ┌─ {} ({}) ─────────────────────", ex.label, ex.rule);
|
|
||||||
println!(" │ source : {:?}", ex.src);
|
|
||||||
match parse_as(parser, ex.src, ex.rule) {
|
|
||||||
Some(tree) => {
|
|
||||||
let preview: String = tree.lines().take(6).collect::<Vec<_>>().join("\n │ ");
|
|
||||||
println!(" │ result : OK");
|
|
||||||
println!(" │ tree : {}", preview);
|
|
||||||
}
|
|
||||||
None => println!(" │ result : NO PARSE"),
|
|
||||||
}
|
|
||||||
println!(" └──────────────────────────────────────────────");
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Unit tests
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// Leak a `Grammar` onto the heap so the returned `GrammarParser` can hold
|
|
||||||
/// a `'static` reference to it. The tiny allocation is intentional: this
|
|
||||||
/// is test-only code and the process exits shortly after.
|
|
||||||
fn make_parser() -> bnf::GrammarParser<'static> {
|
|
||||||
let grammar: &'static bnf::Grammar =
|
|
||||||
Box::leak(Box::new(load_grammar().expect("BNF must be valid")));
|
|
||||||
grammar
|
|
||||||
.build_parser()
|
|
||||||
.expect("all non-terminals must resolve")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Grammar loading ──────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn grammar_loads() {
|
|
||||||
load_grammar().expect("grammar should parse without error");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parser_builds() {
|
|
||||||
make_parser();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Identifiers ──────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn identifier_simple() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "abc", "identifier").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn identifier_with_underscore() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "my_var", "identifier").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn identifier_single_letter() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "x", "identifier").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Literals ─────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn integer_literal() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "0", "integer-literal").is_some());
|
|
||||||
assert!(parse_as(&p, "999", "integer-literal").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn numeric_literal_decimal() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "3.14", "numeric-literal").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Named constants ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn named_constants() {
|
|
||||||
let p = make_parser();
|
|
||||||
for s in &["*ON", "*OFF", "*BLANK", "*BLANKS", "*ZEROS", "*ZERO",
|
|
||||||
"*HIVAL", "*LOVAL", "*NULL"] {
|
|
||||||
assert!(parse_as(&p, s, "named-constant").is_some(), "{} failed", s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Type specs ────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn type_spec_char() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "CHAR(50)", "type-spec").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn type_spec_packed() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "PACKED(9:2)", "type-spec").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn type_spec_date() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "DATE", "type-spec").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn type_spec_ind() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "IND", "type-spec").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn type_spec_pointer() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "POINTER", "type-spec").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Declarations ─────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn standalone_decl_no_inz() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "DCL-S total PACKED(9:2);", "standalone-decl").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn standalone_decl_with_inz() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "DCL-S counter INT(10) INZ(0);", "standalone-decl").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn constant_decl() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "DCL-C MAX CONST(100);", "constant-decl").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Control spec ──────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ctl_opt_nomain() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "CTL-OPT NOMAIN;", "control-spec").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ctl_opt_dftactgrp_no() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "CTL-OPT DFTACTGRP(*NO);", "control-spec").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Statements ───────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn assign_simple() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "x=1;", "assign-stmt").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn return_with_expr() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "RETURN result;", "return-stmt").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn return_bare() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "RETURN;", "return-stmt").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn leave_stmt() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "LEAVE;", "leave-stmt").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn iter_stmt() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "ITER;", "iter-stmt").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn exsr_stmt() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "EXSR myRoutine;", "exsr-stmt").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn read_stmt() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "READ myFile;", "read-stmt").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn write_stmt() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "WRITE outRec;", "write-stmt").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn chain_stmt() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "CHAIN key myFile;", "chain-stmt").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn setll_start() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "SETLL *START myFile;", "setll-stmt").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn open_close() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "OPEN myFile;", "open-stmt").is_some());
|
|
||||||
assert!(parse_as(&p, "CLOSE myFile;", "close-stmt").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn close_all() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "CLOSE *ALL;", "close-stmt").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Expressions ──────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expression_addition() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "a+b", "expression").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expression_comparison() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "x=1", "expression").is_some());
|
|
||||||
assert!(parse_as(&p, "x<>0", "expression").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expression_not() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "NOT flag", "expression").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expression_parenthesised() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "(x+1)", "expression").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Built-in functions ───────────────────────────────────────────────────
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn builtin_len() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "%LEN(myField)", "built-in-function").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn builtin_trim() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "%TRIM(s)", "built-in-function").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn builtin_eof_no_arg() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "%EOF()", "built-in-function").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn builtin_subst_3arg() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "%SUBST(s:1:5)", "built-in-function").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Date / time formats ──────────────────────────────────────────────────
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn date_formats() {
|
|
||||||
let p = make_parser();
|
|
||||||
for fmt in &["*MDY", "*DMY", "*YMD", "*JUL", "*ISO", "*USA", "*EUR", "*JIS"] {
|
|
||||||
assert!(parse_as(&p, fmt, "date-format").is_some(), "{} failed", fmt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn time_formats() {
|
|
||||||
let p = make_parser();
|
|
||||||
for fmt in &["*HMS", "*ISO", "*USA", "*EUR", "*JIS"] {
|
|
||||||
assert!(parse_as(&p, fmt, "time-format").is_some(), "{} failed", fmt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Qualified names ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn qualified_name_simple() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "myVar", "qualified-name").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn qualified_name_dotted() {
|
|
||||||
let p = make_parser();
|
|
||||||
assert!(parse_as(&p, "ds.field", "qualified-name").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Duration codes ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn duration_codes() {
|
|
||||||
let p = make_parser();
|
|
||||||
for code in &["*YEARS", "*MONTHS", "*DAYS", "*HOURS", "*MINUTES",
|
|
||||||
"*SECONDS", "*MSECONDS"] {
|
|
||||||
assert!(parse_as(&p, code, "duration-code").is_some(), "{} failed", code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Hello World program ───────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// The Hello World source from the README, embedded at compile time so the
|
|
||||||
/// test works regardless of the working directory.
|
|
||||||
const HELLO_RPG: &str = include_str!("../hello.rpg");
|
|
||||||
|
|
||||||
/// Verify that the full hello.rpg program parses against the top-level
|
|
||||||
/// `<program>` grammar rule.
|
|
||||||
#[test]
|
|
||||||
fn hello_rpg_parses() {
|
|
||||||
let p = make_parser();
|
|
||||||
let tree = parse_as(&p, HELLO_RPG.trim(), "program");
|
|
||||||
assert!(
|
|
||||||
tree.is_some(),
|
|
||||||
"hello.rpg did not match the <program> rule\n\nsource:\n{}",
|
|
||||||
HELLO_RPG,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
216
src/main.rs
216
src/main.rs
@@ -2,10 +2,9 @@
|
|||||||
//!
|
//!
|
||||||
//! Full compilation pipeline:
|
//! Full compilation pipeline:
|
||||||
//! source (.rpg)
|
//! source (.rpg)
|
||||||
//! → BNF validation (bnf crate)
|
//! → recursive-descent parser / AST lowering (lower.rs)
|
||||||
//! → AST lowering (lower.rs)
|
//! → LLVM IR / object (codegen.rs via inkwell)
|
||||||
//! → LLVM IR / object (codegen.rs via inkwell)
|
//! → native executable (cc linker + librpgrt.so runtime)
|
||||||
//! → native executable (cc linker + librpgrt.so runtime)
|
|
||||||
//!
|
//!
|
||||||
//! ## Usage
|
//! ## Usage
|
||||||
//!
|
//!
|
||||||
@@ -18,7 +17,6 @@
|
|||||||
//! Options:
|
//! Options:
|
||||||
//! -o <OUTPUT> Output executable path [default: a.out]
|
//! -o <OUTPUT> Output executable path [default: a.out]
|
||||||
//! --emit-ir Print LLVM IR to stdout instead of producing a binary
|
//! --emit-ir Print LLVM IR to stdout instead of producing a binary
|
||||||
//! --emit-tree Print BNF parse tree to stdout instead of compiling
|
|
||||||
//! -O <LEVEL> Optimisation level 0-3 [default: 0]
|
//! -O <LEVEL> Optimisation level 0-3 [default: 0]
|
||||||
//! --no-link Produce a .o object file, skip linking
|
//! --no-link Produce a .o object file, skip linking
|
||||||
//! --runtime <PATH> Path to librpgrt.so [default: auto-detect]
|
//! --runtime <PATH> Path to librpgrt.so [default: auto-detect]
|
||||||
@@ -41,98 +39,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use clap::Parser as ClapParser;
|
use clap::Parser as ClapParser;
|
||||||
use rust_langrpg::{codegen, load_grammar, lower::lower, parse_as};
|
use rust_langrpg::{codegen, lower::lower};
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// BNF pre-processing helper
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// Uppercase all keyword-like tokens in `source` while preserving the content
|
|
||||||
/// of string literals, line comments, and block comments unchanged.
|
|
||||||
///
|
|
||||||
/// This lets the BNF grammar (which uses uppercase terminal literals) validate
|
|
||||||
/// RPG IV source that uses mixed-case keywords such as `Ctl-Opt` or `Dcl-S`.
|
|
||||||
fn uppercase_keywords_for_bnf(source: &str) -> String {
|
|
||||||
let chars: Vec<char> = source.chars().collect();
|
|
||||||
let mut out = String::with_capacity(source.len());
|
|
||||||
let mut i = 0;
|
|
||||||
|
|
||||||
while i < chars.len() {
|
|
||||||
// Line comment // … \n — copy verbatim
|
|
||||||
if i + 1 < chars.len() && chars[i] == '/' && chars[i + 1] == '/' {
|
|
||||||
while i < chars.len() && chars[i] != '\n' {
|
|
||||||
out.push(chars[i]);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block comment /* … */ — copy verbatim
|
|
||||||
if i + 1 < chars.len() && chars[i] == '/' && chars[i + 1] == '*' {
|
|
||||||
out.push(chars[i]);
|
|
||||||
out.push(chars[i + 1]);
|
|
||||||
i += 2;
|
|
||||||
while i + 1 < chars.len() {
|
|
||||||
if chars[i] == '*' && chars[i + 1] == '/' {
|
|
||||||
out.push(chars[i]);
|
|
||||||
out.push(chars[i + 1]);
|
|
||||||
i += 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
out.push(chars[i]);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// String literal '…' — copy verbatim (including '' escape)
|
|
||||||
if chars[i] == '\'' {
|
|
||||||
out.push(chars[i]);
|
|
||||||
i += 1;
|
|
||||||
while i < chars.len() {
|
|
||||||
if chars[i] == '\'' {
|
|
||||||
out.push(chars[i]);
|
|
||||||
i += 1;
|
|
||||||
// '' is an escaped quote — keep going
|
|
||||||
if i < chars.len() && chars[i] == '\'' {
|
|
||||||
out.push(chars[i]);
|
|
||||||
i += 1;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
out.push(chars[i]);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Identifier / keyword — uppercase it so the BNF terminals match
|
|
||||||
if chars[i].is_alphabetic() || chars[i] == '_' || chars[i] == '@' || chars[i] == '#' || chars[i] == '$' {
|
|
||||||
while i < chars.len()
|
|
||||||
&& (chars[i].is_alphanumeric()
|
|
||||||
|| chars[i] == '_'
|
|
||||||
|| chars[i] == '@'
|
|
||||||
|| chars[i] == '#'
|
|
||||||
|| chars[i] == '$'
|
|
||||||
|| (chars[i] == '-'
|
|
||||||
&& i + 1 < chars.len()
|
|
||||||
&& chars[i + 1].is_alphabetic()))
|
|
||||||
{
|
|
||||||
out.push(chars[i].to_ascii_uppercase());
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything else (operators, punctuation, whitespace, digits)
|
|
||||||
out.push(chars[i]);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// CLI definition
|
// CLI definition
|
||||||
@@ -161,10 +68,6 @@ struct Cli {
|
|||||||
#[arg(long = "emit-ir")]
|
#[arg(long = "emit-ir")]
|
||||||
emit_ir: bool,
|
emit_ir: bool,
|
||||||
|
|
||||||
/// Emit the BNF parse tree to stdout instead of compiling.
|
|
||||||
#[arg(long = "emit-tree")]
|
|
||||||
emit_tree: bool,
|
|
||||||
|
|
||||||
/// Optimisation level: 0 = none, 1 = less, 2 = default, 3 = aggressive.
|
/// Optimisation level: 0 = none, 1 = less, 2 = default, 3 = aggressive.
|
||||||
#[arg(short = 'O', default_value = "0", value_name = "LEVEL")]
|
#[arg(short = 'O', default_value = "0", value_name = "LEVEL")]
|
||||||
opt_level: u8,
|
opt_level: u8,
|
||||||
@@ -186,24 +89,6 @@ struct Cli {
|
|||||||
fn main() {
|
fn main() {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
// ── Load and build the BNF grammar ───────────────────────────────────────
|
|
||||||
let grammar = match load_grammar() {
|
|
||||||
Ok(g) => g,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("error: failed to load RPG IV grammar: {e}");
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let bnf_parser = match grammar.build_parser() {
|
|
||||||
Ok(p) => p,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("error: failed to build BNF parser: {e}");
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ── Process each source file ─────────────────────────────────────────────
|
|
||||||
let mut any_error = false;
|
let mut any_error = false;
|
||||||
|
|
||||||
for source_path in &cli.sources {
|
for source_path in &cli.sources {
|
||||||
@@ -216,100 +101,11 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── BNF validation ────────────────────────────────────────────────────
|
|
||||||
// RPG IV keywords are case-insensitive, but the BNF grammar uses
|
|
||||||
// uppercase terminal literals. Normalise the source before checking.
|
|
||||||
let normalised = uppercase_keywords_for_bnf(source_text.trim());
|
|
||||||
let tree_opt = parse_as(&bnf_parser, normalised.trim(), "program")
|
|
||||||
.or_else(|| parse_as(&bnf_parser, normalised.trim(), "source-file"));
|
|
||||||
|
|
||||||
if tree_opt.is_none() {
|
|
||||||
// BNF validation is a structural sanity-check. Emit a warning so
|
|
||||||
// the developer knows something looks off, but continue with the
|
|
||||||
// lowering pass which is more permissive and gives better errors.
|
|
||||||
eprintln!(
|
|
||||||
"warning: '{}' did not fully match the RPG IV grammar — \
|
|
||||||
attempting to compile anyway",
|
|
||||||
source_path.display()
|
|
||||||
);
|
|
||||||
|
|
||||||
// ── Helpful diagnostics ──────────────────────────────────────────
|
|
||||||
// Scan for the first line the BNF cannot classify to give the user
|
|
||||||
// a concrete hint about what caused the mismatch.
|
|
||||||
let top_level_rules = &[
|
|
||||||
"control-spec",
|
|
||||||
"standalone-decl",
|
|
||||||
"constant-decl",
|
|
||||||
"data-structure-decl",
|
|
||||||
"file-decl",
|
|
||||||
"procedure",
|
|
||||||
"subroutine",
|
|
||||||
"statement",
|
|
||||||
];
|
|
||||||
'outer: for (lineno, raw_line) in source_text.lines().enumerate() {
|
|
||||||
let trimmed = raw_line.trim();
|
|
||||||
let norm_check = trimmed.to_ascii_uppercase();
|
|
||||||
// Skip blanks, comments, compiler directives, and lines that
|
|
||||||
// introduce multi-line constructs (DCL-PROC, END-PROC, DCL-DS,
|
|
||||||
// END-DS, DCL-PI, END-PI, BEG-SR, END-SR) — these will never
|
|
||||||
// match a single-line grammar rule and are not errors.
|
|
||||||
if trimmed.is_empty()
|
|
||||||
|| trimmed.starts_with("//")
|
|
||||||
|| trimmed.starts_with("/*")
|
|
||||||
|| trimmed.starts_with("**")
|
|
||||||
|| norm_check.starts_with("DCL-PROC")
|
|
||||||
|| norm_check.starts_with("END-PROC")
|
|
||||||
|| norm_check.starts_with("DCL-DS")
|
|
||||||
|| norm_check.starts_with("END-DS")
|
|
||||||
|| norm_check.starts_with("DCL-PI")
|
|
||||||
|| norm_check.starts_with("END-PI")
|
|
||||||
|| norm_check.starts_with("BEG-SR")
|
|
||||||
|| norm_check.starts_with("END-SR")
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Strip inline line comments before BNF matching so that
|
|
||||||
// `fib(1) = 0; // some comment` doesn't cause a false positive.
|
|
||||||
let trimmed_no_comment = if let Some(idx) = trimmed.find("//") {
|
|
||||||
trimmed[..idx].trim_end()
|
|
||||||
} else {
|
|
||||||
trimmed
|
|
||||||
};
|
|
||||||
let norm_line = uppercase_keywords_for_bnf(trimmed_no_comment);
|
|
||||||
let mut matched = false;
|
|
||||||
for rule in top_level_rules {
|
|
||||||
if parse_as(&bnf_parser, norm_line.trim(), rule).is_some() {
|
|
||||||
matched = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !matched {
|
|
||||||
eprintln!(
|
|
||||||
" hint (line {}): unrecognised grammar construct: {:?}",
|
|
||||||
lineno + 1,
|
|
||||||
if trimmed.len() > 80 { &trimmed[..80] } else { trimmed }
|
|
||||||
);
|
|
||||||
break 'outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Fall through — try lowering anyway.
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── --emit-tree: print parse tree and stop ────────────────────────────
|
|
||||||
if cli.emit_tree {
|
|
||||||
println!("=== {} ===", source_path.display());
|
|
||||||
println!("{}", tree_opt.unwrap());
|
|
||||||
eprintln!("ok: {} (parse tree emitted)", source_path.display());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
eprintln!("ok: {} (BNF valid)", source_path.display());
|
|
||||||
|
|
||||||
// ── Lower to typed AST ────────────────────────────────────────────────
|
// ── Lower to typed AST ────────────────────────────────────────────────
|
||||||
let program = match lower(source_text.trim()) {
|
let program = match lower(source_text.trim()) {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("error: lowering '{}' failed: {e}", source_path.display());
|
eprintln!("error: '{}': {e}", source_path.display());
|
||||||
any_error = true;
|
any_error = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -519,7 +315,7 @@ fn find_runtime(explicit: Option<&std::path::Path>) -> Option<PathBuf> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// Integration smoke test (compile-time only — no process spawning needed)
|
// Integration smoke tests (compile-time only — no process spawning needed)
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
703
src/rpg.bnf
703
src/rpg.bnf
@@ -1,703 +0,0 @@
|
|||||||
<wsc> ::= ' ' | ' ' | '
|
|
||||||
' | '
|
|
||||||
'
|
|
||||||
<ws> ::= <wsc> | <wsc> <ws>
|
|
||||||
<opt-ws> ::= <ws> | ''
|
|
||||||
|
|
||||||
<program> ::= <opt-ws> <program-body> <opt-ws>
|
|
||||||
| <opt-ws> <free-directive> <opt-ws> <program-body> <opt-ws>
|
|
||||||
| <opt-ws> <free-directive> <opt-ws>
|
|
||||||
|
|
||||||
<free-directive> ::= '**FREE'
|
|
||||||
| '**free'
|
|
||||||
| '**Free'
|
|
||||||
|
|
||||||
<program-body> ::= <declaration-section> <opt-ws> <procedure-list>
|
|
||||||
| <declaration-section>
|
|
||||||
| <procedure-list>
|
|
||||||
|
|
||||||
<declaration-section> ::= <declaration> <opt-ws> <declaration-section>
|
|
||||||
| <declaration>
|
|
||||||
|
|
||||||
<declaration> ::= <control-spec>
|
|
||||||
| <file-decl>
|
|
||||||
| <standalone-decl>
|
|
||||||
| <constant-decl>
|
|
||||||
| <data-structure-decl>
|
|
||||||
| <named-constant-decl>
|
|
||||||
| <subroutine>
|
|
||||||
|
|
||||||
<procedure-list> ::= <procedure> <opt-ws> <procedure-list>
|
|
||||||
| <procedure>
|
|
||||||
|
|
||||||
<procedure> ::= 'DCL-PROC' <ws> <identifier> <opt-ws> ';' <opt-ws> <procedure-body> <opt-ws> 'END-PROC' <opt-ws> ';'
|
|
||||||
| 'DCL-PROC' <ws> <identifier> <opt-ws> ';' <opt-ws> <procedure-body> <opt-ws> 'END-PROC' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'DCL-PROC' <ws> <identifier> <ws> <proc-keyword-list> <opt-ws> ';' <opt-ws> <procedure-body> <opt-ws> 'END-PROC' <opt-ws> ';'
|
|
||||||
| 'DCL-PROC' <ws> <identifier> <ws> <proc-keyword-list> <opt-ws> ';' <opt-ws> <procedure-body> <opt-ws> 'END-PROC' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<proc-keyword-list> ::= <proc-keyword> <ws> <proc-keyword-list>
|
|
||||||
| <proc-keyword>
|
|
||||||
|
|
||||||
<proc-keyword> ::= 'EXPORT'
|
|
||||||
| 'EXTPROC' <opt-ws> '(' <opt-ws> <string-literal> <opt-ws> ')'
|
|
||||||
| 'EXTPROC' <opt-ws> '(' <opt-ws> '*CL' <opt-ws> ':' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| 'EXTPROC' <opt-ws> '(' <opt-ws> '*DCLCASE' <opt-ws> ')'
|
|
||||||
|
|
||||||
<procedure-body> ::= <pi-spec> <opt-ws> <declaration-section> <opt-ws> <statement-list>
|
|
||||||
| <pi-spec> <opt-ws> <declaration-section>
|
|
||||||
| <pi-spec> <opt-ws> <statement-list>
|
|
||||||
| <declaration-section> <opt-ws> <statement-list>
|
|
||||||
| <statement-list>
|
|
||||||
| <pi-spec>
|
|
||||||
| ''
|
|
||||||
|
|
||||||
<pi-spec> ::= 'DCL-PI' <ws> <identifier> <opt-ws> ';' <opt-ws> 'END-PI' <opt-ws> ';'
|
|
||||||
| 'DCL-PI' <ws> <identifier> <ws> <type-spec> <opt-ws> ';' <opt-ws> 'END-PI' <opt-ws> ';'
|
|
||||||
| 'DCL-PI' <ws> <identifier> <opt-ws> ';' <opt-ws> <pi-param-list> <opt-ws> 'END-PI' <opt-ws> ';'
|
|
||||||
| 'DCL-PI' <ws> <identifier> <ws> <type-spec> <opt-ws> ';' <opt-ws> <pi-param-list> <opt-ws> 'END-PI' <opt-ws> ';'
|
|
||||||
| 'DCL-PI' <ws> '*N' <opt-ws> ';' <opt-ws> 'END-PI' <opt-ws> ';'
|
|
||||||
| 'DCL-PI' <ws> '*N' <ws> <type-spec> <opt-ws> ';' <opt-ws> 'END-PI' <opt-ws> ';'
|
|
||||||
| 'DCL-PI' <ws> '*N' <opt-ws> ';' <opt-ws> <pi-param-list> <opt-ws> 'END-PI' <opt-ws> ';'
|
|
||||||
| 'DCL-PI' <ws> '*N' <ws> <type-spec> <opt-ws> ';' <opt-ws> <pi-param-list> <opt-ws> 'END-PI' <opt-ws> ';'
|
|
||||||
|
|
||||||
<pi-param-list> ::= <pi-param> <opt-ws> <pi-param-list>
|
|
||||||
| <pi-param>
|
|
||||||
|
|
||||||
<pi-param> ::= <identifier> <ws> <type-spec> <opt-ws> ';'
|
|
||||||
| <identifier> <ws> <type-spec> <ws> <param-keyword-list> <opt-ws> ';'
|
|
||||||
|
|
||||||
<param-keyword-list> ::= <param-keyword> <ws> <param-keyword-list>
|
|
||||||
| <param-keyword>
|
|
||||||
|
|
||||||
<param-keyword> ::= 'VALUE'
|
|
||||||
| 'CONST'
|
|
||||||
| 'OPTIONS' <opt-ws> '(' <opt-ws> '*NOPASS' <opt-ws> ')'
|
|
||||||
| 'OPTIONS' <opt-ws> '(' <opt-ws> '*OMIT' <opt-ws> ')'
|
|
||||||
| 'OPTIONS' <opt-ws> '(' <opt-ws> '*VARSIZE' <opt-ws> ')'
|
|
||||||
|
|
||||||
<subroutine> ::= 'BEG-SR' <ws> <identifier> <opt-ws> ';' <opt-ws> <statement-list> <opt-ws> 'END-SR' <opt-ws> ';'
|
|
||||||
| 'BEG-SR' <ws> <identifier> <opt-ws> ';' <opt-ws> 'END-SR' <opt-ws> ';'
|
|
||||||
|
|
||||||
<control-spec> ::= 'CTL-OPT' <ws> <ctl-keyword-list> <opt-ws> ';'
|
|
||||||
|
|
||||||
<ctl-keyword-list> ::= <ctl-keyword> <ws> <ctl-keyword-list>
|
|
||||||
| <ctl-keyword>
|
|
||||||
|
|
||||||
<ctl-keyword> ::= 'DFTACTGRP' <opt-ws> '(' <opt-ws> '*YES' <opt-ws> ')'
|
|
||||||
| 'DFTACTGRP' <opt-ws> '(' <opt-ws> '*NO' <opt-ws> ')'
|
|
||||||
| 'ACTGRP' <opt-ws> '(' <opt-ws> '*CALLER' <opt-ws> ')'
|
|
||||||
| 'ACTGRP' <opt-ws> '(' <opt-ws> '*NEW' <opt-ws> ')'
|
|
||||||
| 'ACTGRP' <opt-ws> '(' <opt-ws> <string-literal> <opt-ws> ')'
|
|
||||||
| 'BNDDIR' <opt-ws> '(' <opt-ws> <string-literal> <opt-ws> ')'
|
|
||||||
| 'OPTION' <opt-ws> '(' <opt-ws> <option-list> <opt-ws> ')'
|
|
||||||
| 'DATFMT' <opt-ws> '(' <opt-ws> <date-format> <opt-ws> ')'
|
|
||||||
| 'TIMFMT' <opt-ws> '(' <opt-ws> <time-format> <opt-ws> ')'
|
|
||||||
| 'DECEDIT' <opt-ws> '(' <opt-ws> <string-literal> <opt-ws> ')'
|
|
||||||
| 'ALWNULL' <opt-ws> '(' <opt-ws> '*USRCTL' <opt-ws> ')'
|
|
||||||
| 'ALWNULL' <opt-ws> '(' <opt-ws> '*INPUTONLY' <opt-ws> ')'
|
|
||||||
| 'ALWNULL' <opt-ws> '(' <opt-ws> '*NO' <opt-ws> ')'
|
|
||||||
| 'DEBUG' <opt-ws> '(' <opt-ws> '*YES' <opt-ws> ')'
|
|
||||||
| 'DEBUG' <opt-ws> '(' <opt-ws> '*NO' <opt-ws> ')'
|
|
||||||
| 'EXPROPTS' <opt-ws> '(' <opt-ws> '*RESDECPOS' <opt-ws> ')'
|
|
||||||
| 'MAIN' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| 'NOMAIN'
|
|
||||||
| 'COPYRIGHT' <opt-ws> '(' <opt-ws> <string-literal> <opt-ws> ')'
|
|
||||||
| 'STGMDL' <opt-ws> '(' <opt-ws> '*SNGLVL' <opt-ws> ')'
|
|
||||||
| 'STGMDL' <opt-ws> '(' <opt-ws> '*TERASPACE' <opt-ws> ')'
|
|
||||||
| 'STGMDL' <opt-ws> '(' <opt-ws> '*INHERIT' <opt-ws> ')'
|
|
||||||
| 'TEXT' <opt-ws> '(' <opt-ws> <string-literal> <opt-ws> ')'
|
|
||||||
| 'TRUNCNBR' <opt-ws> '(' <opt-ws> '*YES' <opt-ws> ')'
|
|
||||||
| 'TRUNCNBR' <opt-ws> '(' <opt-ws> '*NO' <opt-ws> ')'
|
|
||||||
|
|
||||||
<option-list> ::= <option-item> <opt-ws> ':' <opt-ws> <option-list>
|
|
||||||
| <option-item>
|
|
||||||
|
|
||||||
<option-item> ::= '*SRCSTMT'
|
|
||||||
| '*NODEBUGIO'
|
|
||||||
| '*NOUNREF'
|
|
||||||
| '*NOSHOWCPY'
|
|
||||||
|
|
||||||
<file-decl> ::= 'DCL-F' <ws> <identifier> <ws> <file-keyword-list> <opt-ws> ';'
|
|
||||||
| 'DCL-F' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<file-keyword-list> ::= <file-keyword> <ws> <file-keyword-list>
|
|
||||||
| <file-keyword>
|
|
||||||
|
|
||||||
<file-keyword> ::= 'KEYED'
|
|
||||||
| 'USAGE' <opt-ws> '(' <opt-ws> '*INPUT' <opt-ws> ')'
|
|
||||||
| 'USAGE' <opt-ws> '(' <opt-ws> '*OUTPUT' <opt-ws> ')'
|
|
||||||
| 'USAGE' <opt-ws> '(' <opt-ws> '*UPDATE' <opt-ws> ':' <opt-ws> '*DELETE' <opt-ws> ')'
|
|
||||||
| 'USAGE' <opt-ws> '(' <opt-ws> '*UPDATE' <opt-ws> ')'
|
|
||||||
| 'USAGE' <opt-ws> '(' <opt-ws> '*DELETE' <opt-ws> ')'
|
|
||||||
| 'DISK'
|
|
||||||
| 'PRINTER'
|
|
||||||
| 'SEQ'
|
|
||||||
| 'RENAME' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ':' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| 'QUALIFIED'
|
|
||||||
| 'TEMPLATE'
|
|
||||||
| 'EXTFILE' <opt-ws> '(' <opt-ws> <string-literal> <opt-ws> ')'
|
|
||||||
| 'EXTFILE' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| 'EXTMBR' <opt-ws> '(' <opt-ws> <string-literal> <opt-ws> ')'
|
|
||||||
| 'EXTMBR' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| 'EXTDESC' <opt-ws> '(' <opt-ws> <string-literal> <opt-ws> ')'
|
|
||||||
| 'STATIC'
|
|
||||||
| 'INFDS' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| 'BLOCK' <opt-ws> '(' <opt-ws> '*YES' <opt-ws> ')'
|
|
||||||
| 'BLOCK' <opt-ws> '(' <opt-ws> '*NO' <opt-ws> ')'
|
|
||||||
|
|
||||||
<standalone-decl> ::= 'DCL-S' <ws> <identifier> <ws> <type-spec> <opt-ws> ';'
|
|
||||||
| 'DCL-S' <ws> <identifier> <ws> <type-spec> <ws> <var-keyword-list> <opt-ws> ';'
|
|
||||||
|
|
||||||
<constant-decl> ::= 'DCL-C' <ws> <identifier> <ws> <literal> <opt-ws> ';'
|
|
||||||
| 'DCL-C' <ws> <identifier> <ws> 'CONST' <opt-ws> '(' <opt-ws> <literal> <opt-ws> ')' <opt-ws> ';'
|
|
||||||
|
|
||||||
<named-constant-decl> ::= 'DCL-C' <ws> <identifier> <ws> <named-constant> <opt-ws> ';'
|
|
||||||
|
|
||||||
<named-constant> ::= '*ON'
|
|
||||||
| '*OFF'
|
|
||||||
| '*BLANKS'
|
|
||||||
| '*BLANK'
|
|
||||||
| '*ZEROS'
|
|
||||||
| '*ZERO'
|
|
||||||
| '*HIVAL'
|
|
||||||
| '*LOVAL'
|
|
||||||
| '*NULL'
|
|
||||||
|
|
||||||
<data-structure-decl> ::= 'DCL-DS' <ws> <identifier> <opt-ws> ';' <opt-ws> <ds-field-list> <opt-ws> 'END-DS' <opt-ws> ';'
|
|
||||||
| 'DCL-DS' <ws> <identifier> <ws> <ds-keyword-list> <opt-ws> ';' <opt-ws> <ds-field-list> <opt-ws> 'END-DS' <opt-ws> ';'
|
|
||||||
| 'DCL-DS' <ws> <identifier> <opt-ws> ';' <opt-ws> 'END-DS' <opt-ws> ';'
|
|
||||||
| 'DCL-DS' <ws> <identifier> <ws> <ds-keyword-list> <opt-ws> ';' <opt-ws> 'END-DS' <opt-ws> ';'
|
|
||||||
|
|
||||||
<ds-keyword-list> ::= <ds-keyword> <ws> <ds-keyword-list>
|
|
||||||
| <ds-keyword>
|
|
||||||
|
|
||||||
<ds-keyword> ::= 'QUALIFIED'
|
|
||||||
| 'TEMPLATE'
|
|
||||||
| 'LIKEDS' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| 'LIKEREC' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ':' <opt-ws> <rec-format> <opt-ws> ')'
|
|
||||||
| 'EXTNAME' <opt-ws> '(' <opt-ws> <string-literal> <opt-ws> ':' <opt-ws> <rec-format> <opt-ws> ')'
|
|
||||||
| 'EXTNAME' <opt-ws> '(' <opt-ws> <string-literal> <opt-ws> ')'
|
|
||||||
| 'BASED' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| 'INZ'
|
|
||||||
| 'INZ' <opt-ws> '(' <opt-ws> '*EXTDFT' <opt-ws> ')'
|
|
||||||
| 'DIM' <opt-ws> '(' <opt-ws> <integer-literal> <opt-ws> ')'
|
|
||||||
| 'STATIC'
|
|
||||||
|
|
||||||
<rec-format> ::= '*INPUT'
|
|
||||||
| '*OUTPUT'
|
|
||||||
| '*ALL'
|
|
||||||
| '*KEY'
|
|
||||||
| <identifier>
|
|
||||||
|
|
||||||
<ds-field-list> ::= <ds-field> <opt-ws> <ds-field-list>
|
|
||||||
| <ds-field>
|
|
||||||
|
|
||||||
<ds-field> ::= <identifier> <ws> <type-spec> <opt-ws> ';'
|
|
||||||
| <identifier> <ws> <type-spec> <ws> <var-keyword-list> <opt-ws> ';'
|
|
||||||
| <data-structure-decl>
|
|
||||||
|
|
||||||
<var-keyword-list> ::= <var-keyword> <ws> <var-keyword-list>
|
|
||||||
| <var-keyword>
|
|
||||||
|
|
||||||
<var-keyword> ::= 'INZ'
|
|
||||||
| 'INZ' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| 'INZ' <opt-ws> '(' <opt-ws> <named-constant> <opt-ws> ')'
|
|
||||||
| 'STATIC'
|
|
||||||
| 'BASED' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| 'DIM' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| 'LIKE' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| 'LIKEDS' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| 'ASCEND'
|
|
||||||
| 'DESCEND'
|
|
||||||
| 'ALTSEQ'
|
|
||||||
| 'OPDESC'
|
|
||||||
| 'NOOPT'
|
|
||||||
| 'VOLATILE'
|
|
||||||
|
|
||||||
<type-spec> ::= 'CHAR' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| 'VARCHAR' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| 'GRAPH' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| 'VARGRAPH' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| 'UCS2' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| 'VARUCS2' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| 'PACKED' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| 'ZONED' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| 'BINDEC' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| 'INT' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| 'UNS' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| 'FLOAT' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| 'DATE'
|
|
||||||
| 'DATE' <opt-ws> '(' <opt-ws> <date-format> <opt-ws> ')'
|
|
||||||
| 'TIME'
|
|
||||||
| 'TIME' <opt-ws> '(' <opt-ws> <time-format> <opt-ws> ')'
|
|
||||||
| 'TIMESTAMP'
|
|
||||||
| 'TIMESTAMP' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| 'IND'
|
|
||||||
| 'POINTER'
|
|
||||||
| 'POINTER' <ws> 'PROCPTR'
|
|
||||||
| 'OBJECT' <opt-ws> '(' <opt-ws> '*JAVA' <opt-ws> ':' <opt-ws> <string-literal> <opt-ws> ')'
|
|
||||||
| 'LIKE' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| 'LIKEDS' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| 'LIKEREC' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ':' <opt-ws> <rec-format> <opt-ws> ')'
|
|
||||||
|
|
||||||
<date-format> ::= '*MDY'
|
|
||||||
| '*DMY'
|
|
||||||
| '*YMD'
|
|
||||||
| '*JUL'
|
|
||||||
| '*ISO'
|
|
||||||
| '*USA'
|
|
||||||
| '*EUR'
|
|
||||||
| '*JIS'
|
|
||||||
|
|
||||||
<time-format> ::= '*HMS'
|
|
||||||
| '*ISO'
|
|
||||||
| '*USA'
|
|
||||||
| '*EUR'
|
|
||||||
| '*JIS'
|
|
||||||
|
|
||||||
<statement-list> ::= <statement> <opt-ws> <statement-list>
|
|
||||||
| <statement>
|
|
||||||
|
|
||||||
<statement> ::= <assign-stmt>
|
|
||||||
| <if-stmt>
|
|
||||||
| <dow-stmt>
|
|
||||||
| <dou-stmt>
|
|
||||||
| <for-stmt>
|
|
||||||
| <select-stmt>
|
|
||||||
| <monitor-stmt>
|
|
||||||
| <callp-stmt>
|
|
||||||
| <return-stmt>
|
|
||||||
| <leave-stmt>
|
|
||||||
| <iter-stmt>
|
|
||||||
| <leavesr-stmt>
|
|
||||||
| <exsr-stmt>
|
|
||||||
| <read-stmt>
|
|
||||||
| <readp-stmt>
|
|
||||||
| <reade-stmt>
|
|
||||||
| <readpe-stmt>
|
|
||||||
| <write-stmt>
|
|
||||||
| <update-stmt>
|
|
||||||
| <delete-stmt>
|
|
||||||
| <chain-stmt>
|
|
||||||
| <setll-stmt>
|
|
||||||
| <setgt-stmt>
|
|
||||||
| <open-stmt>
|
|
||||||
| <close-stmt>
|
|
||||||
| <except-stmt>
|
|
||||||
| <exfmt-stmt>
|
|
||||||
| <dsply-stmt>
|
|
||||||
| <reset-stmt>
|
|
||||||
| <clear-stmt>
|
|
||||||
| <sorta-stmt>
|
|
||||||
| <dealloc-stmt>
|
|
||||||
| <dump-stmt>
|
|
||||||
| <force-stmt>
|
|
||||||
| <post-stmt>
|
|
||||||
| <feod-stmt>
|
|
||||||
| <unlock-stmt>
|
|
||||||
| <subroutine>
|
|
||||||
|
|
||||||
<assign-stmt> ::= <lvalue> <opt-ws> '=' <opt-ws> <expression> <opt-ws> ';'
|
|
||||||
| 'EVAL' <ws> <lvalue> <opt-ws> '=' <opt-ws> <expression> <opt-ws> ';'
|
|
||||||
| 'EVAL' <opt-ws> '(' <opt-ws> <eval-option> <opt-ws> ')' <ws> <lvalue> <opt-ws> '=' <opt-ws> <expression> <opt-ws> ';'
|
|
||||||
| 'EVALR' <ws> <lvalue> <opt-ws> '=' <opt-ws> <expression> <opt-ws> ';'
|
|
||||||
| 'EVAL-CORR' <ws> <lvalue> <opt-ws> '=' <opt-ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<eval-option> ::= 'H'
|
|
||||||
| 'T'
|
|
||||||
| 'E'
|
|
||||||
|
|
||||||
<lvalue> ::= <qualified-name>
|
|
||||||
| <qualified-name> <opt-ws> '(' <opt-ws> <index-list> <opt-ws> ')'
|
|
||||||
|
|
||||||
<if-stmt> ::= 'IF' <ws> <expression> <opt-ws> ';' <opt-ws> <statement-list> <opt-ws> <elseif-list> <opt-ws> 'ENDIF' <opt-ws> ';'
|
|
||||||
| 'IF' <ws> <expression> <opt-ws> ';' <opt-ws> <statement-list> <opt-ws> <elseif-list> <opt-ws> <else-clause> <opt-ws> 'ENDIF' <opt-ws> ';'
|
|
||||||
| 'IF' <ws> <expression> <opt-ws> ';' <opt-ws> <statement-list> <opt-ws> 'ENDIF' <opt-ws> ';'
|
|
||||||
| 'IF' <ws> <expression> <opt-ws> ';' <opt-ws> <statement-list> <opt-ws> <else-clause> <opt-ws> 'ENDIF' <opt-ws> ';'
|
|
||||||
| 'IF' <ws> <expression> <opt-ws> ';' <opt-ws> <elseif-list> <opt-ws> 'ENDIF' <opt-ws> ';'
|
|
||||||
| 'IF' <ws> <expression> <opt-ws> ';' <opt-ws> <elseif-list> <opt-ws> <else-clause> <opt-ws> 'ENDIF' <opt-ws> ';'
|
|
||||||
| 'IF' <ws> <expression> <opt-ws> ';' <opt-ws> 'ENDIF' <opt-ws> ';'
|
|
||||||
|
|
||||||
<elseif-list> ::= <elseif-clause> <opt-ws> <elseif-list>
|
|
||||||
| <elseif-clause>
|
|
||||||
|
|
||||||
<elseif-clause> ::= 'ELSEIF' <ws> <expression> <opt-ws> ';' <opt-ws> <statement-list>
|
|
||||||
| 'ELSEIF' <ws> <expression> <opt-ws> ';'
|
|
||||||
|
|
||||||
<else-clause> ::= 'ELSE' <opt-ws> ';' <opt-ws> <statement-list>
|
|
||||||
| 'ELSE' <opt-ws> ';'
|
|
||||||
|
|
||||||
<dow-stmt> ::= 'DOW' <ws> <expression> <opt-ws> ';' <opt-ws> <statement-list> <opt-ws> 'ENDDO' <opt-ws> ';'
|
|
||||||
| 'DOW' <ws> <expression> <opt-ws> ';' <opt-ws> 'ENDDO' <opt-ws> ';'
|
|
||||||
|
|
||||||
<dou-stmt> ::= 'DOU' <ws> <expression> <opt-ws> ';' <opt-ws> <statement-list> <opt-ws> 'ENDDO' <opt-ws> ';'
|
|
||||||
| 'DOU' <ws> <expression> <opt-ws> ';' <opt-ws> 'ENDDO' <opt-ws> ';'
|
|
||||||
|
|
||||||
<for-stmt> ::= 'FOR' <ws> <identifier> <opt-ws> '=' <opt-ws> <expression> <ws> 'TO' <ws> <expression> <opt-ws> ';' <opt-ws> <statement-list> <opt-ws> 'ENDFOR' <opt-ws> ';'
|
|
||||||
| 'FOR' <ws> <identifier> <opt-ws> '=' <opt-ws> <expression> <ws> 'TO' <ws> <expression> <ws> 'BY' <ws> <expression> <opt-ws> ';' <opt-ws> <statement-list> <opt-ws> 'ENDFOR' <opt-ws> ';'
|
|
||||||
| 'FOR' <ws> <identifier> <opt-ws> '=' <opt-ws> <expression> <ws> 'DOWNTO' <ws> <expression> <opt-ws> ';' <opt-ws> <statement-list> <opt-ws> 'ENDFOR' <opt-ws> ';'
|
|
||||||
| 'FOR' <ws> <identifier> <opt-ws> '=' <opt-ws> <expression> <ws> 'DOWNTO' <ws> <expression> <ws> 'BY' <ws> <expression> <opt-ws> ';' <opt-ws> <statement-list> <opt-ws> 'ENDFOR' <opt-ws> ';'
|
|
||||||
| 'FOR' <ws> <identifier> <opt-ws> '=' <opt-ws> <expression> <ws> 'TO' <ws> <expression> <opt-ws> ';' <opt-ws> 'ENDFOR' <opt-ws> ';'
|
|
||||||
| 'FOR' <ws> <identifier> <opt-ws> '=' <opt-ws> <expression> <ws> 'DOWNTO' <ws> <expression> <opt-ws> ';' <opt-ws> 'ENDFOR' <opt-ws> ';'
|
|
||||||
|
|
||||||
<select-stmt> ::= 'SELECT' <opt-ws> ';' <opt-ws> <when-list> <opt-ws> 'ENDSL' <opt-ws> ';'
|
|
||||||
| 'SELECT' <opt-ws> ';' <opt-ws> <when-list> <opt-ws> <other-clause> <opt-ws> 'ENDSL' <opt-ws> ';'
|
|
||||||
| 'SELECT' <opt-ws> ';' <opt-ws> 'ENDSL' <opt-ws> ';'
|
|
||||||
|
|
||||||
<when-list> ::= <when-clause> <opt-ws> <when-list>
|
|
||||||
| <when-clause>
|
|
||||||
|
|
||||||
<when-clause> ::= 'WHEN' <ws> <expression> <opt-ws> ';' <opt-ws> <statement-list>
|
|
||||||
| 'WHEN' <ws> <expression> <opt-ws> ';'
|
|
||||||
|
|
||||||
<other-clause> ::= 'OTHER' <opt-ws> ';' <opt-ws> <statement-list>
|
|
||||||
| 'OTHER' <opt-ws> ';'
|
|
||||||
|
|
||||||
<monitor-stmt> ::= 'MONITOR' <opt-ws> ';' <opt-ws> <statement-list> <opt-ws> <on-error-list> <opt-ws> 'ENDMON' <opt-ws> ';'
|
|
||||||
| 'MONITOR' <opt-ws> ';' <opt-ws> <on-error-list> <opt-ws> 'ENDMON' <opt-ws> ';'
|
|
||||||
|
|
||||||
<on-error-list> ::= <on-error-clause> <opt-ws> <on-error-list>
|
|
||||||
| <on-error-clause>
|
|
||||||
|
|
||||||
<on-error-clause> ::= 'ON-ERROR' <opt-ws> ';' <opt-ws> <statement-list>
|
|
||||||
| 'ON-ERROR' <ws> <error-code-list> <opt-ws> ';' <opt-ws> <statement-list>
|
|
||||||
| 'ON-ERROR' <opt-ws> ';'
|
|
||||||
| 'ON-ERROR' <ws> <error-code-list> <opt-ws> ';'
|
|
||||||
|
|
||||||
<error-code-list> ::= <error-code> <opt-ws> ':' <opt-ws> <error-code-list>
|
|
||||||
| <error-code>
|
|
||||||
|
|
||||||
<error-code> ::= <integer-literal>
|
|
||||||
| '*PROGRAM'
|
|
||||||
| '*FILE'
|
|
||||||
| '*ALL'
|
|
||||||
|
|
||||||
<callp-stmt> ::= 'CALLP' <ws> <identifier> <opt-ws> '(' <opt-ws> <arg-list> <opt-ws> ')' <opt-ws> ';'
|
|
||||||
| 'CALLP' <ws> <identifier> <opt-ws> '(' <opt-ws> ')' <opt-ws> ';'
|
|
||||||
| 'CALLP' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| <identifier> <opt-ws> '(' <opt-ws> <arg-list> <opt-ws> ')' <opt-ws> ';'
|
|
||||||
| <identifier> <opt-ws> '(' <opt-ws> ')' <opt-ws> ';'
|
|
||||||
|
|
||||||
<return-stmt> ::= 'RETURN' <opt-ws> ';'
|
|
||||||
| 'RETURN' <ws> <expression> <opt-ws> ';'
|
|
||||||
|
|
||||||
<leave-stmt> ::= 'LEAVE' <opt-ws> ';'
|
|
||||||
|
|
||||||
<iter-stmt> ::= 'ITER' <opt-ws> ';'
|
|
||||||
|
|
||||||
<leavesr-stmt> ::= 'LEAVESR' <opt-ws> ';'
|
|
||||||
|
|
||||||
<exsr-stmt> ::= 'EXSR' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<read-stmt> ::= 'READ' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'READ' <ws> <identifier> <ws> <indicator-spec> <opt-ws> ';'
|
|
||||||
| 'READ' <opt-ws> '(' <opt-ws> 'N' <opt-ws> ')' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<readp-stmt> ::= 'READP' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'READP' <ws> <identifier> <ws> <indicator-spec> <opt-ws> ';'
|
|
||||||
|
|
||||||
<reade-stmt> ::= 'READE' <ws> <expression> <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'READE' <ws> <expression> <ws> <identifier> <ws> <indicator-spec> <opt-ws> ';'
|
|
||||||
| 'READE' <opt-ws> '(' <opt-ws> 'N' <opt-ws> ')' <ws> <expression> <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<readpe-stmt> ::= 'READPE' <ws> <expression> <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'READPE' <ws> <expression> <ws> <identifier> <ws> <indicator-spec> <opt-ws> ';'
|
|
||||||
|
|
||||||
<write-stmt> ::= 'WRITE' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'WRITE' <ws> <identifier> <ws> <indicator-spec> <opt-ws> ';'
|
|
||||||
| 'WRITE' <opt-ws> '(' <opt-ws> 'E' <opt-ws> ')' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<update-stmt> ::= 'UPDATE' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'UPDATE' <ws> <identifier> <ws> <indicator-spec> <opt-ws> ';'
|
|
||||||
| 'UPDATE' <opt-ws> '(' <opt-ws> 'E' <opt-ws> ')' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<delete-stmt> ::= 'DELETE' <ws> <expression> <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'DELETE' <opt-ws> '(' <opt-ws> 'E' <opt-ws> ')' <ws> <expression> <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'DELETE' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<chain-stmt> ::= 'CHAIN' <ws> <expression> <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'CHAIN' <opt-ws> '(' <opt-ws> 'N' <opt-ws> ')' <ws> <expression> <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'CHAIN' <opt-ws> '(' <opt-ws> 'E' <opt-ws> ')' <ws> <expression> <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<setll-stmt> ::= 'SETLL' <ws> <expression> <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'SETLL' <ws> '*START' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'SETLL' <ws> '*END' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<setgt-stmt> ::= 'SETGT' <ws> <expression> <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'SETGT' <ws> '*START' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'SETGT' <ws> '*END' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<open-stmt> ::= 'OPEN' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'OPEN' <opt-ws> '(' <opt-ws> 'E' <opt-ws> ')' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<close-stmt> ::= 'CLOSE' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'CLOSE' <opt-ws> '(' <opt-ws> 'E' <opt-ws> ')' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'CLOSE' <ws> '*ALL' <opt-ws> ';'
|
|
||||||
|
|
||||||
<except-stmt> ::= 'EXCEPT' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'EXCEPT' <opt-ws> ';'
|
|
||||||
|
|
||||||
<exfmt-stmt> ::= 'EXFMT' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'EXFMT' <opt-ws> '(' <opt-ws> 'E' <opt-ws> ')' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<dsply-stmt> ::= 'DSPLY' <ws> <expression> <opt-ws> ';'
|
|
||||||
| 'DSPLY' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <identifier> <opt-ws> ':' <opt-ws> <identifier> <opt-ws> ')' <opt-ws> ';'
|
|
||||||
| 'DSPLY' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')' <opt-ws> ';'
|
|
||||||
|
|
||||||
<reset-stmt> ::= 'RESET' <ws> <lvalue> <opt-ws> ';'
|
|
||||||
| 'RESET' <ws> '*ALL' <opt-ws> ';'
|
|
||||||
|
|
||||||
<clear-stmt> ::= 'CLEAR' <ws> <lvalue> <opt-ws> ';'
|
|
||||||
|
|
||||||
<sorta-stmt> ::= 'SORTA' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'SORTA' <opt-ws> '(' <opt-ws> 'A' <opt-ws> ')' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'SORTA' <opt-ws> '(' <opt-ws> 'D' <opt-ws> ')' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<dealloc-stmt> ::= 'DEALLOC' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'DEALLOC' <opt-ws> '(' <opt-ws> 'N' <opt-ws> ')' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<dump-stmt> ::= 'DUMP' <opt-ws> ';'
|
|
||||||
| 'DUMP' <opt-ws> '(' <opt-ws> 'A' <opt-ws> ')' <opt-ws> ';'
|
|
||||||
|
|
||||||
<force-stmt> ::= 'FORCE' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<post-stmt> ::= 'POST' <ws> <identifier> <opt-ws> ';'
|
|
||||||
| 'POST' <opt-ws> '(' <opt-ws> 'E' <opt-ws> ')' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<feod-stmt> ::= 'FEOD' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<unlock-stmt> ::= 'UNLOCK' <ws> <identifier> <opt-ws> ';'
|
|
||||||
|
|
||||||
<indicator-spec> ::= <identifier>
|
|
||||||
|
|
||||||
<expression> ::= <or-expr>
|
|
||||||
|
|
||||||
<or-expr> ::= <or-expr> <opt-ws> 'OR' <opt-ws> <and-expr>
|
|
||||||
| <and-expr>
|
|
||||||
|
|
||||||
<and-expr> ::= <and-expr> <opt-ws> 'AND' <opt-ws> <not-expr>
|
|
||||||
| <not-expr>
|
|
||||||
|
|
||||||
<not-expr> ::= 'NOT' <ws> <comparison-expr>
|
|
||||||
| <comparison-expr>
|
|
||||||
|
|
||||||
<comparison-expr> ::= <additive-expr> <opt-ws> '=' <opt-ws> <additive-expr>
|
|
||||||
| <additive-expr> <opt-ws> '<>' <opt-ws> <additive-expr>
|
|
||||||
| <additive-expr> <opt-ws> '>=' <opt-ws> <additive-expr>
|
|
||||||
| <additive-expr> <opt-ws> '<=' <opt-ws> <additive-expr>
|
|
||||||
| <additive-expr> <opt-ws> '>' <opt-ws> <additive-expr>
|
|
||||||
| <additive-expr> <opt-ws> '<' <opt-ws> <additive-expr>
|
|
||||||
| <additive-expr>
|
|
||||||
|
|
||||||
<additive-expr> ::= <additive-expr> <opt-ws> '+' <opt-ws> <multiplicative-expr>
|
|
||||||
| <additive-expr> <opt-ws> '-' <opt-ws> <multiplicative-expr>
|
|
||||||
| <multiplicative-expr>
|
|
||||||
|
|
||||||
<multiplicative-expr> ::= <multiplicative-expr> <opt-ws> '**' <opt-ws> <unary-expr>
|
|
||||||
| <multiplicative-expr> <opt-ws> '*' <opt-ws> <unary-expr>
|
|
||||||
| <multiplicative-expr> <opt-ws> '/' <opt-ws> <unary-expr>
|
|
||||||
| <unary-expr>
|
|
||||||
|
|
||||||
<unary-expr> ::= '-' <opt-ws> <primary-expr>
|
|
||||||
| '+' <opt-ws> <primary-expr>
|
|
||||||
| <primary-expr>
|
|
||||||
|
|
||||||
<primary-expr> ::= <literal>
|
|
||||||
| <named-constant>
|
|
||||||
| <special-value>
|
|
||||||
| <built-in-function>
|
|
||||||
| <identifier> <opt-ws> '(' <opt-ws> <arg-list> <opt-ws> ')'
|
|
||||||
| <identifier> <opt-ws> '(' <opt-ws> ')'
|
|
||||||
| <qualified-name> <opt-ws> '(' <opt-ws> <index-list> <opt-ws> ')'
|
|
||||||
| <qualified-name>
|
|
||||||
| '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
|
|
||||||
<special-value> ::= '*IN' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '*IN'
|
|
||||||
| '*ON'
|
|
||||||
| '*OFF'
|
|
||||||
| '*BLANK'
|
|
||||||
| '*BLANKS'
|
|
||||||
| '*ZERO'
|
|
||||||
| '*ZEROS'
|
|
||||||
| '*HIVAL'
|
|
||||||
| '*LOVAL'
|
|
||||||
| '*NULL'
|
|
||||||
| '*ALL' <string-literal>
|
|
||||||
| '*OMIT'
|
|
||||||
| '*THIS'
|
|
||||||
| '*SAME'
|
|
||||||
| '*START'
|
|
||||||
| '*END'
|
|
||||||
|
|
||||||
<built-in-function> ::= '%ABS' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%ADDR' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%ALLOC' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%BITAND' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%BITNOT' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%BITOR' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%BITXOR' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%CHAR' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <date-format> <opt-ws> ')'
|
|
||||||
| '%CHAR' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%CHECK' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%CHECK' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%CHECKR' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%DATE' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <date-format> <opt-ws> ')'
|
|
||||||
| '%DATE' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%DATE' <opt-ws> '(' <opt-ws> ')'
|
|
||||||
| '%DAYS' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%DEC' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%DECH' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%DECPOS' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%DIFF' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <duration-code> <opt-ws> ')'
|
|
||||||
| '%DIV' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%EDITC' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <string-literal> <opt-ws> ')'
|
|
||||||
| '%EDITFLT' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%EDITW' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <string-literal> <opt-ws> ')'
|
|
||||||
| '%ELEM' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%EOF' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%EOF' <opt-ws> '(' <opt-ws> ')'
|
|
||||||
| '%EQUAL' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%EQUAL' <opt-ws> '(' <opt-ws> ')'
|
|
||||||
| '%ERROR' <opt-ws> '(' <opt-ws> ')'
|
|
||||||
| '%FIELDS' <opt-ws> '(' <opt-ws> <field-list> <opt-ws> ')'
|
|
||||||
| '%FLOAT' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%FOUND' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%FOUND' <opt-ws> '(' <opt-ws> ')'
|
|
||||||
| '%GRAPH' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%HOURS' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%INT' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%INTH' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%KDS' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%KDS' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%LEN' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%MINUTES' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%MONTHS' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%MSECONDS' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%NULLIND' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%OCCUR' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%OPEN' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%PADDR' <opt-ws> '(' <opt-ws> '*DCLCASE' <opt-ws> ':' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%PADDR' <opt-ws> '(' <opt-ws> <string-literal> <opt-ws> ')'
|
|
||||||
| '%REALLOC' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%REM' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%REPLACE' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%REPLACE' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%REPLACE' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%SCAN' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%SCAN' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%SCANR' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%SECONDS' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%SHTDN' <opt-ws> '(' <opt-ws> ')'
|
|
||||||
| '%SIZE' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%SQRT' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%STATUS' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%STATUS' <opt-ws> '(' <opt-ws> ')'
|
|
||||||
| '%STR' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%STR' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%SUBARR' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%SUBARR' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%SUBST' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%SUBST' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%THIS' <opt-ws> '(' <opt-ws> ')'
|
|
||||||
| '%TIME' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%TIME' <opt-ws> '(' <opt-ws> ')'
|
|
||||||
| '%TIMESTAMP' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%TIMESTAMP' <opt-ws> '(' <opt-ws> ')'
|
|
||||||
| '%TRIM' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%TRIML' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%TRIMR' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%UCS2' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%UNS' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%UNSH' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%XFOOT' <opt-ws> '(' <opt-ws> <identifier> <opt-ws> ')'
|
|
||||||
| '%XLATE' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ':' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
| '%YEARS' <opt-ws> '(' <opt-ws> <expression> <opt-ws> ')'
|
|
||||||
|
|
||||||
<duration-code> ::= '*YEARS'
|
|
||||||
| '*MONTHS'
|
|
||||||
| '*DAYS'
|
|
||||||
| '*HOURS'
|
|
||||||
| '*MINUTES'
|
|
||||||
| '*SECONDS'
|
|
||||||
| '*MSECONDS'
|
|
||||||
|
|
||||||
<field-list> ::= <identifier> <opt-ws> ':' <opt-ws> <field-list>
|
|
||||||
| <identifier>
|
|
||||||
|
|
||||||
<arg-list> ::= <arg> <opt-ws> ':' <opt-ws> <arg-list>
|
|
||||||
| <arg>
|
|
||||||
|
|
||||||
<arg> ::= <expression>
|
|
||||||
| '*OMIT'
|
|
||||||
|
|
||||||
<index-list> ::= <expression> <opt-ws> ':' <opt-ws> <index-list>
|
|
||||||
| <expression>
|
|
||||||
|
|
||||||
<qualified-name> ::= <identifier> <opt-ws> '.' <opt-ws> <qualified-name>
|
|
||||||
| <identifier>
|
|
||||||
|
|
||||||
<literal> ::= <string-literal>
|
|
||||||
| <numeric-literal>
|
|
||||||
| <integer-literal>
|
|
||||||
| <hex-literal>
|
|
||||||
| <indicator-literal>
|
|
||||||
|
|
||||||
<string-literal> ::= "'" <string-chars> "'"
|
|
||||||
|
|
||||||
<string-chars> ::= <string-char> <string-chars>
|
|
||||||
| ''
|
|
||||||
|
|
||||||
<string-char> ::= <letter>
|
|
||||||
| <digit>
|
|
||||||
| <special-char>
|
|
||||||
| "''"
|
|
||||||
|
|
||||||
<hex-literal> ::= 'X' "'" <hex-chars> "'"
|
|
||||||
|
|
||||||
<hex-chars> ::= <hex-char> <hex-chars>
|
|
||||||
| <hex-char>
|
|
||||||
|
|
||||||
<hex-char> ::= <digit>
|
|
||||||
| 'A'
|
|
||||||
| 'B'
|
|
||||||
| 'C'
|
|
||||||
| 'D'
|
|
||||||
| 'E'
|
|
||||||
| 'F'
|
|
||||||
|
|
||||||
<numeric-literal> ::= <integer-literal> '.' <digits>
|
|
||||||
| '.' <digits>
|
|
||||||
|
|
||||||
<integer-literal> ::= <digits>
|
|
||||||
|
|
||||||
<digits> ::= <digit> <digits>
|
|
||||||
| <digit>
|
|
||||||
|
|
||||||
<indicator-literal> ::= '*ON'
|
|
||||||
| '*OFF'
|
|
||||||
|
|
||||||
<identifier> ::= <letter> <id-chars>
|
|
||||||
| <letter>
|
|
||||||
|
|
||||||
<id-chars> ::= <id-char> <id-chars>
|
|
||||||
| <id-char>
|
|
||||||
|
|
||||||
<id-char> ::= <letter>
|
|
||||||
| <digit>
|
|
||||||
| '_'
|
|
||||||
|
|
||||||
<letter> ::= 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J'
|
|
||||||
| 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T'
|
|
||||||
| 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z'
|
|
||||||
| 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j'
|
|
||||||
| 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't'
|
|
||||||
| 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
|
|
||||||
| '@' | '#' | '$'
|
|
||||||
|
|
||||||
<digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
|
|
||||||
|
|
||||||
<special-char> ::= ' ' | '!' | '"' | '#' | '$' | '%' | '&' | '('
|
|
||||||
| ')' | '*' | '+' | ',' | '-' | '.' | '/' | ':'
|
|
||||||
| '<' | '=' | '>' | '?' | '@' | '[' | '\\'
|
|
||||||
| ']' | '^' | '_' | '`' | '{' | '|' | '}' | '~'
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Integration tests for the compiler binary against the Hello World program.
|
//! Integration tests for the compiler binary against the Hello World program.
|
||||||
//!
|
//!
|
||||||
//! These tests exercise the full compilation pipeline:
|
//! These tests exercise the full compilation pipeline:
|
||||||
//! hello.rpg → BNF validation → AST lowering → LLVM codegen → native binary
|
//! hello.rpg → recursive-descent parser → AST lowering → LLVM codegen → native binary
|
||||||
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ fn hello_rpg_produces_output_file() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The compiler must print the file name to stderr with an "ok:" prefix when
|
/// The compiler must print the file name to stderr with an "ok:" prefix when
|
||||||
/// BNF validation succeeds.
|
/// compilation succeeds.
|
||||||
#[test]
|
#[test]
|
||||||
fn hello_rpg_reports_ok_on_stderr() {
|
fn hello_rpg_reports_ok_on_stderr() {
|
||||||
let out_path = std::env::temp_dir().join("hello_rpg_reports_ok.out");
|
let out_path = std::env::temp_dir().join("hello_rpg_reports_ok.out");
|
||||||
@@ -121,33 +121,6 @@ fn hello_rpg_emit_ir() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `--emit-tree` must print the BNF parse tree to stdout and exit 0.
|
|
||||||
///
|
|
||||||
/// The tree must mention `program` (the top-level grammar rule).
|
|
||||||
#[test]
|
|
||||||
fn hello_rpg_emit_tree() {
|
|
||||||
let out = run(&["--emit-tree", HELLO_RPG]);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
out.status.success(),
|
|
||||||
"expected exit 0 with --emit-tree\nstderr: {}",
|
|
||||||
String::from_utf8_lossy(&out.stderr),
|
|
||||||
);
|
|
||||||
|
|
||||||
let tree = String::from_utf8_lossy(&out.stdout);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
!tree.trim().is_empty(),
|
|
||||||
"--emit-tree output is empty — expected a parse tree",
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
tree.contains("program"),
|
|
||||||
"--emit-tree output should reference the <program> rule\n{}",
|
|
||||||
&tree[..tree.len().min(1000)],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `--no-link` should produce a `.o` object file and exit 0.
|
/// `--no-link` should produce a `.o` object file and exit 0.
|
||||||
#[test]
|
#[test]
|
||||||
fn hello_rpg_no_link_produces_object() {
|
fn hello_rpg_no_link_produces_object() {
|
||||||
|
|||||||
Reference in New Issue
Block a user