Compare commits
11 Commits
26b2e2f0f7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 832dae44fb | |||
| 8e36afbf67 | |||
| dc9bb41cce | |||
| 46935005f7 | |||
| 6c4118c489 | |||
| 944326f114 | |||
| 31a6c8b91b | |||
| 073c86d784 | |||
| 3498b018e5 | |||
| 90de2206db | |||
| c0951fc9d4 |
434
Cargo.lock
generated
434
Cargo.lock
generated
@@ -3,90 +3,171 @@
|
|||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "allocator-api2"
|
name = "anstream"
|
||||||
version = "0.2.21"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bnf"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "35b77b055f8cb1d566fa4ef55bc699f60eefb17927dc25fa454a05b6fabf7aa4"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"anstyle",
|
||||||
"hashbrown",
|
"anstyle-parse",
|
||||||
"nom",
|
"anstyle-query",
|
||||||
"rand",
|
"anstyle-wincon",
|
||||||
"serde",
|
"colorchoice",
|
||||||
"serde_json",
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "anstyle"
|
||||||
version = "3.20.2"
|
version = "1.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "anstyle-parse"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.102"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.2.56"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
||||||
|
dependencies = [
|
||||||
|
"find-msvc-tools",
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "either"
|
||||||
version = "1.0.2"
|
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 = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foldhash"
|
name = "find-msvc-tools"
|
||||||
version = "0.2.0"
|
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 = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "heck"
|
||||||
version = "0.3.4"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inkwell"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1def4112dfb2ce2993db7027f7acdb43c1f4ee1c70a082a2eef306ed5d0df365"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"inkwell_internals",
|
||||||
"js-sys",
|
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi",
|
"llvm-sys",
|
||||||
"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]]
|
|
||||||
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",
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inkwell_internals"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "63736175c9a30ea123f7018de9f26163e0b39cd6978990ae486b510c4f3bad69"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.183"
|
version = "0.2.183"
|
||||||
@@ -94,18 +175,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "llvm-sys"
|
||||||
version = "2.8.0"
|
version = "211.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
checksum = "108b3ad2b2eaf2a561fc74196273b20e3436e4a688b8b44e250d83974dc1b2e2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom"
|
|
||||||
version = "8.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"anyhow",
|
||||||
|
"cc",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"regex-lite",
|
||||||
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -115,13 +195,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "once_cell_polyfill"
|
||||||
version = "0.2.21"
|
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 = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
dependencies = [
|
|
||||||
"zerocopy",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
@@ -142,95 +219,41 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "r-efi"
|
name = "regex-lite"
|
||||||
version = "5.3.0"
|
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 = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rpgrt"
|
||||||
version = "0.9.2"
|
version = "0.1.0"
|
||||||
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 = "rust-langrpg"
|
name = "rust-langrpg"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bnf",
|
"clap",
|
||||||
|
"either",
|
||||||
|
"inkwell",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "semver"
|
||||||
version = "1.0.22"
|
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 = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "shlex"
|
||||||
version = "1.0.228"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
dependencies = [
|
|
||||||
"serde_core",
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_core"
|
name = "strsim"
|
||||||
version = "1.0.228"
|
version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
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 = "syn"
|
name = "syn"
|
||||||
@@ -243,6 +266,26 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.24"
|
version = "1.0.24"
|
||||||
@@ -250,87 +293,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasip2"
|
name = "utf8parse"
|
||||||
version = "1.0.2+wasi-0.2.9"
|
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 = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wit-bindgen",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[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]]
|
|
||||||
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"
|
|
||||||
|
|||||||
37
Cargo.toml
37
Cargo.toml
@@ -1,7 +1,42 @@
|
|||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
".",
|
||||||
|
"rpgrt",
|
||||||
|
]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Main compiler package
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "rust-langrpg"
|
name = "rust-langrpg"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
default-run = "rust-langrpg"
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Binaries
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "rust-langrpg"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Library (rlib — used by the binaries and tests)
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "rust_langrpg"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
crate-type = ["rlib"]
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Dependencies
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bnf = "0.6"
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
either = "1"
|
||||||
|
inkwell = { version = "0.8", features = ["llvm21-1"] }
|
||||||
|
|||||||
172
README.md
172
README.md
@@ -4,6 +4,174 @@ An implementation of the RPG language from IBM.
|
|||||||
|
|
||||||
Language reference: https://www.ibm.com/docs/en/i/7.5.0?topic=introduction-overview-rpg-iv-programming-language
|
Language reference: https://www.ibm.com/docs/en/i/7.5.0?topic=introduction-overview-rpg-iv-programming-language
|
||||||
|
|
||||||
## Implementation
|
## Usage
|
||||||
|
|
||||||
The RPG language was converted to an BNF, and fed into the bnf crate (https://docs.rs/bnf/latest/bnf/).
|
### Building
|
||||||
|
|
||||||
|
```rust-langrpg/README.md
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiling an RPG IV program
|
||||||
|
|
||||||
|
```rust-langrpg/README.md
|
||||||
|
cargo run --release -- -o hello hello.rpg
|
||||||
|
./hello
|
||||||
|
```
|
||||||
|
|
||||||
|
You will see output similar to:
|
||||||
|
|
||||||
|
```rust-langrpg/README.md
|
||||||
|
DSPLY Hello, World!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hello World in RPG IV
|
||||||
|
|
||||||
|
The following is a complete Hello World program written in RPG IV free-format syntax:
|
||||||
|
|
||||||
|
`hello.rpg`:
|
||||||
|
|
||||||
|
```rust-langrpg/README.md
|
||||||
|
CTL-OPT DFTACTGRP(*NO);
|
||||||
|
|
||||||
|
DCL-S greeting CHAR(25) INZ('Hello, World!');
|
||||||
|
|
||||||
|
DCL-PROC main EXPORT;
|
||||||
|
DSPLY greeting;
|
||||||
|
RETURN;
|
||||||
|
END-PROC;
|
||||||
|
```
|
||||||
|
|
||||||
|
Breaking it down:
|
||||||
|
|
||||||
|
- `CTL-OPT DFTACTGRP(*NO);` — control option spec declaring the program does not run in the default activation group
|
||||||
|
- `DCL-S greeting CHAR(25) INZ('Hello, World!');` — standalone variable declaration: a 25-character field initialised to `'Hello, World!'`
|
||||||
|
- `DCL-PROC main EXPORT; ... END-PROC;` — a procedure named `main`, exported so it can be called as a program entry point
|
||||||
|
- `DSPLY greeting;` — displays the value of `greeting` to the operator message queue
|
||||||
|
- `RETURN;` — returns from the procedure
|
||||||
|
|
||||||
|
### Compiler options
|
||||||
|
|
||||||
|
```
|
||||||
|
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
|
||||||
|
|
||||||
|
The compiler is split across two crates in a Cargo workspace:
|
||||||
|
|
||||||
|
| Crate | Role |
|
||||||
|
|-------|------|
|
||||||
|
| `rust-langrpg` | Compiler front-end, mid-end, and LLVM back-end |
|
||||||
|
| `rpgrt` | C-compatible runtime shared library (`librpgrt.so`) |
|
||||||
|
|
||||||
|
### Compilation pipeline
|
||||||
|
|
||||||
|
```
|
||||||
|
RPG IV source (.rpg)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 1. Parsing + lowering (src/lower.rs) │
|
||||||
|
│ Hand-written tokenizer + │
|
||||||
|
│ recursive-descent parser │
|
||||||
|
│ → typed AST (src/ast.rs) │
|
||||||
|
└────────────────┬────────────────────────┘
|
||||||
|
│ ast::Program
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 2. LLVM code generation (src/codegen.rs│
|
||||||
|
│ inkwell bindings → LLVM IR module │
|
||||||
|
└────────────────┬────────────────────────┘
|
||||||
|
│ .o object file
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 3. Linking (cc + librpgrt.so) │
|
||||||
|
│ Produces a standalone Linux ELF │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stage 1 — Parsing and lowering to a typed AST (`src/lower.rs`)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
The AST covers the full language surface that the compiler handles:
|
||||||
|
|
||||||
|
- **Declarations** — `CTL-OPT`, `DCL-S`, `DCL-C`, `DCL-DS`, `DCL-F`, subroutines
|
||||||
|
- **Procedures** — `DCL-PROC … END-PROC` with `DCL-PI … END-PI` parameter interfaces
|
||||||
|
- **Statements** — assignment, `IF/ELSEIF/ELSE`, `DOW`, `DOU`, `FOR`, `SELECT/WHEN`, `MONITOR/ON-ERROR`, `CALLP`, `DSPLY`, `RETURN`, `LEAVE`, `ITER`, `LEAVESR`, `EXSR`, `CLEAR`, `RESET`, all I/O opcodes
|
||||||
|
- **Expressions** — literals, variables, qualified names (`ds.field`), arithmetic, logical operators, comparisons, built-in functions (`%LEN`, `%TRIM`, `%SUBST`, `%SCAN`, `%EOF`, `%SIZE`, `%ADDR`, `%SQRT`, `%ABS`, `%REM`, `%DIV`, and more)
|
||||||
|
- **Types** — `CHAR`, `VARCHAR`, `INT`, `UNS`, `FLOAT`, `PACKED`, `ZONED`, `BINDEC`, `IND`, `DATE`, `TIME`, `TIMESTAMP`, `POINTER`, `LIKE`, `LIKEDS`
|
||||||
|
|
||||||
|
Unrecognised constructs produce `Statement::Unimplemented` or placeholder declaration variants rather than hard errors, so the compiler continues to lower the parts it understands.
|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|
- Each `DCL-PROC … END-PROC` becomes an LLVM function.
|
||||||
|
- An exported procedure named `main` (or the first exported procedure) is wrapped in a C `main()` entry point so the resulting binary is directly executable.
|
||||||
|
- `DCL-S` standalone variables are allocated as `alloca` stack slots inside their owning function, or as LLVM global variables for module-scope declarations.
|
||||||
|
- String literals are stored as null-terminated byte arrays in `.rodata`.
|
||||||
|
- `DSPLY expr;` is lowered to a call to `rpg_dsply(ptr, len)` (or `rpg_dsply_i64` / `rpg_dsply_f64` for numeric types) provided by the runtime library.
|
||||||
|
- Control-flow constructs (`IF`, `DOW`, `DOU`, `FOR`, `SELECT`) are lowered to LLVM basic blocks and conditional / unconditional branches.
|
||||||
|
- `LEAVE` / `ITER` are lowered to `br` to the loop-exit / loop-header block respectively, tracked via a `FnState` per function.
|
||||||
|
|
||||||
|
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 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`.
|
||||||
|
|
||||||
|
### Runtime library (`rpgrt/`)
|
||||||
|
|
||||||
|
`rpgrt` is a separate Cargo crate built as a `cdylib`, producing `librpgrt.so`. It is written in Rust and exports a C ABI used by compiled RPG programs:
|
||||||
|
|
||||||
|
| Symbol | Signature | Purpose |
|
||||||
|
|--------|-----------|---------|
|
||||||
|
| `rpg_dsply` | `(ptr: *const u8, len: i64)` | Display a fixed-length `CHAR` field (trims trailing spaces) |
|
||||||
|
| `rpg_dsply_cstr` | `(ptr: *const c_char)` | Display a null-terminated C string |
|
||||||
|
| `rpg_dsply_i64` | `(n: i64)` | Display a signed 64-bit integer |
|
||||||
|
| `rpg_dsply_f64` | `(f: f64)` | Display a double-precision float |
|
||||||
|
| `rpg_halt` | `(code: i32)` | Abnormal program termination |
|
||||||
|
| `rpg_memset_char` | `(ptr, len, fill)` | Fill a char buffer with a repeated byte |
|
||||||
|
| `rpg_move_char` | `(dst, dst_len, src, src_len)` | Copy between fixed-length char fields (pad / truncate) |
|
||||||
|
| `rpg_trim` | `(dst, src, src_len) -> i64` | Trim leading and trailing spaces, return trimmed length |
|
||||||
|
| `rpg_len` | `(len: i64) -> i64` | Identity — returns the static `%LEN` of a field |
|
||||||
|
| `rpg_scan` | `(needle, n_len, haystack, h_len, start) -> i64` | `%SCAN` substring search |
|
||||||
|
| `rpg_subst` | `(src, src_len, start, length, dst, dst_len)` | `%SUBST` extraction |
|
||||||
|
|
||||||
|
`DSPLY` output is written to **stdout** and flushed immediately, mirroring IBM i's interactive operator message queue format:
|
||||||
|
|
||||||
|
```/dev/null/example.txt#L1
|
||||||
|
DSPLY Hello, World!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Project layout
|
||||||
|
|
||||||
|
```
|
||||||
|
rust-langrpg/
|
||||||
|
├── src/
|
||||||
|
│ ├── lib.rs — Library root (re-exports ast, lower, codegen)
|
||||||
|
│ ├── ast.rs — Typed AST node definitions
|
||||||
|
│ ├── lower.rs — Tokenizer + recursive-descent parser + lowering pass
|
||||||
|
│ ├── codegen.rs — LLVM IR code generation (inkwell)
|
||||||
|
│ └── main.rs — Compiler CLI (clap) + linker invocation
|
||||||
|
├── rpgrt/
|
||||||
|
│ └── src/
|
||||||
|
│ └── lib.rs — Runtime library (librpgrt.so)
|
||||||
|
├── hello.rpg — Hello World example program
|
||||||
|
└── fib.rpg — Fibonacci sequence example program
|
||||||
|
```
|
||||||
|
|||||||
21
rpgrt/Cargo.toml
Normal file
21
rpgrt/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "rpgrt"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Build as a C-compatible shared library (librpgrt.so) so that RPG IV programs
|
||||||
|
# compiled by rust-langrpg can link against it at runtime.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "rpgrt"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
# cdylib → produces librpgrt.so (loaded by compiled RPG binaries)
|
||||||
|
# rlib → allows `cargo test` to run the unit tests in src/lib.rs
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# No external dependencies — the runtime is intentionally minimal and relies
|
||||||
|
# only on the Rust standard library and libc (linked automatically).
|
||||||
720
rpgrt/src/lib.rs
Normal file
720
rpgrt/src/lib.rs
Normal file
@@ -0,0 +1,720 @@
|
|||||||
|
//! rpgrt.rs — RPG IV runtime library.
|
||||||
|
//!
|
||||||
|
//! This crate is compiled as a C-compatible shared library (`librpgrt.so`) that
|
||||||
|
//! RPG IV programs compiled by `rust-langrpg` link against at runtime.
|
||||||
|
//!
|
||||||
|
//! ## Exported symbols
|
||||||
|
//!
|
||||||
|
//! | Symbol | Signature | Description |
|
||||||
|
//! |---------------------|----------------------------------------|--------------------------------------|
|
||||||
|
//! | `rpg_dsply` | `(ptr: *const u8, len: i64)` | Display a fixed-length char field |
|
||||||
|
//! | `rpg_dsply_cstr` | `(ptr: *const c_char)` | Display a null-terminated C string |
|
||||||
|
//! | `rpg_dsply_i64` | `(n: i64)` | Display a signed 64-bit integer |
|
||||||
|
//! | `rpg_dsply_f64` | `(f: f64)` | Display a double-precision float |
|
||||||
|
//! | `rpg_dsply_read` | `(prompt: *const c_char, response: *mut i64)` | Display null-term prompt & read i64 |
|
||||||
|
//! | `rpg_dsply_read_len`| `(ptr: *const u8, len: i64, response: *mut i64)` | Display length-delimited prompt & read i64 |
|
||||||
|
//! | `rpg_halt` | `(code: i32)` | Abnormal program termination |
|
||||||
|
//! | `rpg_char_i64` | `(n: i64) -> *const c_char` | Format integer as null-term C string |
|
||||||
|
//! | `rpg_concat` | `(a: *const c_char, b: *const c_char) -> *const c_char` | Concatenate two C strings |
|
||||||
|
//!
|
||||||
|
//! ## Building
|
||||||
|
//!
|
||||||
|
//! The runtime is built automatically by `build.rs` as part of the normal
|
||||||
|
//! `cargo build` invocation. The resulting `librpgrt.so` is placed in the
|
||||||
|
//! Cargo output directory and the compiler binary links executables against it.
|
||||||
|
//!
|
||||||
|
//! To build it standalone:
|
||||||
|
//!
|
||||||
|
//! ```sh
|
||||||
|
//! rustc --edition 2024 --crate-type cdylib -o librpgrt.so src/bin/rpgrt.rs
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## DSPLY semantics
|
||||||
|
//!
|
||||||
|
//! In a real IBM i system, `DSPLY` writes a message to the *program message
|
||||||
|
//! queue* (an interactive operator message queue). On a Linux host there is no
|
||||||
|
//! equivalent facility, so we write to **stdout**, mirroring the message format
|
||||||
|
//! IBM i uses:
|
||||||
|
//!
|
||||||
|
//! ```text
|
||||||
|
//! DSPLY Hello, World!
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! The output is flushed immediately after every `DSPLY` call, matching the
|
||||||
|
//! interactive behaviour of the IBM i runtime.
|
||||||
|
//!
|
||||||
|
//! Trailing ASCII spaces (0x20) are stripped from fixed-length `CHAR` fields
|
||||||
|
//! before display, exactly as IBM i does.
|
||||||
|
|
||||||
|
#![allow(clippy::missing_safety_doc)]
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Thread-local scratch buffers used by rpg_char_i64 / rpg_concat
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
/// Backing store for the most recent `rpg_char_i64` result.
|
||||||
|
static CHAR_BUF: RefCell<CString> = RefCell::new(CString::new("").unwrap());
|
||||||
|
|
||||||
|
/// Backing store for the most recent `rpg_concat` result.
|
||||||
|
static CONCAT_BUF: RefCell<CString> = RefCell::new(CString::new("").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_dsply — display a fixed-length character field
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Display the first `len` bytes pointed to by `ptr`, trimming trailing spaces,
|
||||||
|
/// then print a newline and flush stdout.
|
||||||
|
///
|
||||||
|
/// This is the primary entry point called by the LLVM-compiled RPG procedure
|
||||||
|
/// for `DSPLY variable_name;`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// * `ptr` must be valid for at least `len` bytes.
|
||||||
|
/// * `len` must be ≥ 0. A negative `len` is silently treated as 0.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rpg_dsply(ptr: *const u8, len: i64) {
|
||||||
|
let bytes = if ptr.is_null() || len <= 0 {
|
||||||
|
b"" as &[u8]
|
||||||
|
} else {
|
||||||
|
unsafe { slice::from_raw_parts(ptr, len as usize) }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Strip trailing spaces (IBM i CHAR fields are space-padded to their
|
||||||
|
// declared length).
|
||||||
|
let trimmed = rtrim_spaces(bytes);
|
||||||
|
|
||||||
|
// Convert to a lossy UTF-8 string so non-ASCII EBCDIC-origin data at
|
||||||
|
// least renders something printable rather than crashing.
|
||||||
|
let text = String::from_utf8_lossy(trimmed);
|
||||||
|
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut out = stdout.lock();
|
||||||
|
// Mimic IBM i DSPLY prefix.
|
||||||
|
let _ = writeln!(out, "DSPLY {}", text);
|
||||||
|
let _ = out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_dsply_cstr — display a null-terminated C string
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Display a null-terminated C string with a `DSPLY` prefix.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// `ptr` must point to a valid null-terminated C string.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rpg_dsply_cstr(ptr: *const std::os::raw::c_char) {
|
||||||
|
let text = if ptr.is_null() {
|
||||||
|
std::borrow::Cow::Borrowed("")
|
||||||
|
} else {
|
||||||
|
unsafe { CStr::from_ptr(ptr).to_string_lossy() }
|
||||||
|
};
|
||||||
|
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut out = stdout.lock();
|
||||||
|
let _ = writeln!(out, "DSPLY {}", text);
|
||||||
|
let _ = out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_dsply_i64 — display a signed 64-bit integer
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Display the decimal representation of a signed 64-bit integer.
|
||||||
|
///
|
||||||
|
/// Used when the argument to `DSPLY` is an integer expression rather than a
|
||||||
|
/// character variable.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn rpg_dsply_i64(n: i64) {
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut out = stdout.lock();
|
||||||
|
let _ = writeln!(out, "DSPLY {}", n);
|
||||||
|
let _ = out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_dsply_f64 — display a double-precision float
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Display the decimal representation of a 64-bit IEEE 754 float.
|
||||||
|
///
|
||||||
|
/// Matches the numeric formatting IBM i uses for packed-decimal fields when
|
||||||
|
/// displayed via `DSPLY`.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn rpg_dsply_f64(f: f64) {
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut out = stdout.lock();
|
||||||
|
// Format with enough precision to round-trip.
|
||||||
|
let _ = writeln!(out, "DSPLY {}", f);
|
||||||
|
let _ = out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_dsply_read — display a prompt and read an integer response from stdin
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Display `prompt` (a null-terminated C string) with a `DSPLY` prefix, then
|
||||||
|
/// read one line from stdin and parse it as a signed 64-bit integer. The
|
||||||
|
/// parsed value is written through `response`.
|
||||||
|
///
|
||||||
|
/// This implements the three-operand form of the `DSPLY` opcode:
|
||||||
|
/// ```rpg
|
||||||
|
/// Dsply prompt ' ' response_var;
|
||||||
|
/// ```
|
||||||
|
/// where the third operand is the variable that receives the operator's reply.
|
||||||
|
///
|
||||||
|
/// If stdin is exhausted (EOF) or the line cannot be parsed as an integer the
|
||||||
|
/// response is left unchanged.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// * `prompt` must be a valid null-terminated C string (or null, treated as
|
||||||
|
/// an empty string).
|
||||||
|
/// * `response` must be a valid, aligned, writable pointer to an `i64`.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rpg_dsply_read(
|
||||||
|
prompt: *const std::os::raw::c_char,
|
||||||
|
response: *mut i64,
|
||||||
|
) {
|
||||||
|
use std::io::BufRead;
|
||||||
|
|
||||||
|
// Display the prompt.
|
||||||
|
let text = if prompt.is_null() {
|
||||||
|
std::borrow::Cow::Borrowed("")
|
||||||
|
} else {
|
||||||
|
unsafe { CStr::from_ptr(prompt).to_string_lossy() }
|
||||||
|
};
|
||||||
|
{
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut out = stdout.lock();
|
||||||
|
let _ = writeln!(out, "DSPLY {}", text);
|
||||||
|
let _ = out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read one line from stdin and parse it as i64.
|
||||||
|
let stdin = io::stdin();
|
||||||
|
let mut line = String::new();
|
||||||
|
if stdin.lock().read_line(&mut line).is_ok() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if let Ok(n) = trimmed.parse::<i64>() {
|
||||||
|
unsafe { *response = n; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_dsply_read_len — display a length-delimited prompt and read a response
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Three-operand DSPLY where the prompt is a fixed-length (or VarChar) field
|
||||||
|
/// passed as `(ptr, len)` rather than a null-terminated C string.
|
||||||
|
///
|
||||||
|
/// This is called when the prompt operand is a `Char` or `VarChar` variable —
|
||||||
|
/// those are stored as raw byte buffers with no null terminator, so
|
||||||
|
/// `rpg_dsply_read` (which calls `CStr::from_ptr`) cannot be used safely.
|
||||||
|
///
|
||||||
|
/// Trailing ASCII spaces are stripped from the prompt before display, matching
|
||||||
|
/// the behaviour of `rpg_dsply`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// * `ptr` must be valid for at least `len` bytes (or be null).
|
||||||
|
/// * `response` must be a valid, writable `i64` pointer.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rpg_dsply_read_len(
|
||||||
|
ptr: *const u8,
|
||||||
|
len: i64,
|
||||||
|
response: *mut i64,
|
||||||
|
) {
|
||||||
|
use std::io::BufRead;
|
||||||
|
|
||||||
|
// Build the prompt string the same way rpg_dsply does.
|
||||||
|
let bytes = if ptr.is_null() || len <= 0 {
|
||||||
|
b"" as &[u8]
|
||||||
|
} else {
|
||||||
|
unsafe { slice::from_raw_parts(ptr, len as usize) }
|
||||||
|
};
|
||||||
|
let trimmed = rtrim_spaces(bytes);
|
||||||
|
let text = String::from_utf8_lossy(trimmed);
|
||||||
|
|
||||||
|
{
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut out = stdout.lock();
|
||||||
|
let _ = writeln!(out, "DSPLY {}", text);
|
||||||
|
let _ = out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read one line from stdin and parse it as i64.
|
||||||
|
let stdin = io::stdin();
|
||||||
|
let mut line = String::new();
|
||||||
|
if stdin.lock().read_line(&mut line).is_ok() {
|
||||||
|
let trimmed_line = line.trim();
|
||||||
|
if let Ok(n) = trimmed_line.parse::<i64>() {
|
||||||
|
unsafe { *response = n; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_halt — abnormal termination
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Terminate the program with the given exit code after printing an error
|
||||||
|
/// banner to stderr.
|
||||||
|
///
|
||||||
|
/// Maps roughly to the IBM i concept of an *unhandled exception* ending the
|
||||||
|
/// job.
|
||||||
|
#[no_mangle]
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_char_i64 — convert a 64-bit integer to a C string (%CHAR built-in)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Format `n` as a decimal C string and return a pointer to a thread-local
|
||||||
|
/// buffer holding the result.
|
||||||
|
///
|
||||||
|
/// The returned pointer is valid until the next call to `rpg_char_i64` on the
|
||||||
|
/// same thread. Callers must not free it.
|
||||||
|
///
|
||||||
|
/// This implements the RPG IV `%CHAR(numeric-expression)` built-in function.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn rpg_char_i64(n: i64) -> *const std::os::raw::c_char {
|
||||||
|
let s = CString::new(n.to_string()).unwrap_or_else(|_| CString::new("0").unwrap());
|
||||||
|
CHAR_BUF.with(|cell| {
|
||||||
|
*cell.borrow_mut() = s;
|
||||||
|
cell.borrow().as_ptr()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_concat — concatenate two null-terminated C strings ('+' on char)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Concatenate two null-terminated C strings and return a pointer to a
|
||||||
|
/// thread-local buffer holding the result.
|
||||||
|
///
|
||||||
|
/// The returned pointer is valid until the next call to `rpg_concat` on the
|
||||||
|
/// same thread. Callers must not free it.
|
||||||
|
///
|
||||||
|
/// This implements the RPG IV `+` operator when both operands are character
|
||||||
|
/// expressions.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Both `a` and `b` must be valid null-terminated C strings (or null pointers,
|
||||||
|
/// which are treated as empty strings).
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rpg_concat(
|
||||||
|
a: *const std::os::raw::c_char,
|
||||||
|
b: *const std::os::raw::c_char,
|
||||||
|
) -> *const std::os::raw::c_char {
|
||||||
|
let sa = if a.is_null() {
|
||||||
|
std::borrow::Cow::Borrowed("")
|
||||||
|
} else {
|
||||||
|
unsafe { CStr::from_ptr(a).to_string_lossy() }
|
||||||
|
};
|
||||||
|
let sb = if b.is_null() {
|
||||||
|
std::borrow::Cow::Borrowed("")
|
||||||
|
} else {
|
||||||
|
unsafe { CStr::from_ptr(b).to_string_lossy() }
|
||||||
|
};
|
||||||
|
let joined = format!("{}{}", sa, sb);
|
||||||
|
let cs = CString::new(joined).unwrap_or_else(|_| CString::new("").unwrap());
|
||||||
|
CONCAT_BUF.with(|cell| {
|
||||||
|
*cell.borrow_mut() = cs;
|
||||||
|
cell.borrow().as_ptr()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "C" fn rpg_halt(code: i32) {
|
||||||
|
eprintln!("RPG program halted with code {}", code);
|
||||||
|
std::process::exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_memset_char — fill a CHAR field with a repeated byte
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Fill the first `len` bytes at `ptr` with `fill_byte`.
|
||||||
|
///
|
||||||
|
/// Used by `CLEAR` and `RESET` for character fields (fill with space 0x20).
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// `ptr` must be valid for at least `len` bytes and must be writable.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rpg_memset_char(ptr: *mut u8, fill_byte: u8, len: i64) {
|
||||||
|
if ptr.is_null() || len <= 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let slice = unsafe { slice::from_raw_parts_mut(ptr, len as usize) };
|
||||||
|
slice.fill(fill_byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_move_char — move (copy) a CHAR field, padding / truncating as needed
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Copy `src_len` bytes from `src` into a `dst_len`-byte field at `dst`.
|
||||||
|
///
|
||||||
|
/// * If `src_len` < `dst_len` the destination is right-padded with spaces.
|
||||||
|
/// * If `src_len` > `dst_len` only the first `dst_len` bytes of `src` are
|
||||||
|
/// copied (left-truncation rule, matching RPG IV `MOVE` semantics).
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Both `src` and `dst` must be valid for their respective lengths.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rpg_move_char(
|
||||||
|
dst: *mut u8,
|
||||||
|
dst_len: i64,
|
||||||
|
src: *const u8,
|
||||||
|
src_len: i64,
|
||||||
|
) {
|
||||||
|
if dst.is_null() || src.is_null() || dst_len <= 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dst_slice = unsafe { slice::from_raw_parts_mut(dst, dst_len as usize) };
|
||||||
|
let copy_len = (src_len.min(dst_len)) as usize;
|
||||||
|
|
||||||
|
if src_len > 0 {
|
||||||
|
let src_slice = unsafe { slice::from_raw_parts(src, src_len as usize) };
|
||||||
|
dst_slice[..copy_len].copy_from_slice(&src_slice[..copy_len]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad remainder with spaces.
|
||||||
|
if (copy_len as i64) < dst_len {
|
||||||
|
dst_slice[copy_len..].fill(b' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_trim — return pointer + new length for a space-trimmed CHAR field
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Write the trimmed start pointer and trimmed length of a CHAR field into
|
||||||
|
/// `out_ptr` and `out_len` respectively.
|
||||||
|
///
|
||||||
|
/// Leading *and* trailing spaces are stripped (equivalent to `%TRIM`).
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// * `ptr` must be valid for `len` bytes.
|
||||||
|
/// * `out_ptr` and `out_len` must be valid writable pointers.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rpg_trim(
|
||||||
|
ptr: *const u8,
|
||||||
|
len: i64,
|
||||||
|
out_ptr: *mut *const u8,
|
||||||
|
out_len: *mut i64,
|
||||||
|
) {
|
||||||
|
if ptr.is_null() || len <= 0 || out_ptr.is_null() || out_len.is_null() {
|
||||||
|
if !out_ptr.is_null() { unsafe { *out_ptr = ptr; } }
|
||||||
|
if !out_len.is_null() { unsafe { *out_len = 0; } }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = unsafe { slice::from_raw_parts(ptr, len as usize) };
|
||||||
|
let trimmed = bytes
|
||||||
|
.iter()
|
||||||
|
.position(|&b| b != b' ')
|
||||||
|
.map(|start| {
|
||||||
|
let end = bytes.iter().rposition(|&b| b != b' ').unwrap_or(start) + 1;
|
||||||
|
&bytes[start..end]
|
||||||
|
})
|
||||||
|
.unwrap_or(&bytes[0..0]);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
*out_ptr = trimmed.as_ptr();
|
||||||
|
*out_len = trimmed.len() as i64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_len — return the non-space length of a CHAR field (%LEN semantics)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Return the *declared* length of a CHAR field (i.e. `len` itself), not the
|
||||||
|
/// trimmed length. This matches RPG IV `%LEN` which returns the declared size.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// No pointer dereference is performed; this function is trivially safe.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn rpg_len(_ptr: *const u8, len: i64) -> i64 {
|
||||||
|
len
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_scan — %SCAN(search : source [: start])
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Search for `search_ptr[0..search_len]` inside `src_ptr[0..src_len]`
|
||||||
|
/// starting at byte offset `start` (1-based, RPG IV convention).
|
||||||
|
///
|
||||||
|
/// Returns the 1-based position of the first match, or 0 if not found.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Both pointers must be valid for their respective lengths.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rpg_scan(
|
||||||
|
search_ptr: *const u8,
|
||||||
|
search_len: i64,
|
||||||
|
src_ptr: *const u8,
|
||||||
|
src_len: i64,
|
||||||
|
start: i64, // 1-based; 0 means "from beginning" (treated as 1)
|
||||||
|
) -> i64 {
|
||||||
|
if search_ptr.is_null() || src_ptr.is_null()
|
||||||
|
|| search_len <= 0 || src_len <= 0
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let needle = unsafe { slice::from_raw_parts(search_ptr, search_len as usize) };
|
||||||
|
let hay = unsafe { slice::from_raw_parts(src_ptr, src_len as usize) };
|
||||||
|
|
||||||
|
let from = if start <= 0 { 0 } else { (start - 1) as usize };
|
||||||
|
if from >= hay.len() { return 0; }
|
||||||
|
|
||||||
|
hay[from..]
|
||||||
|
.windows(needle.len())
|
||||||
|
.position(|w| w == needle)
|
||||||
|
.map(|p| (from + p + 1) as i64) // convert back to 1-based
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_subst — %SUBST(str : start [: len])
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Write up to `sub_len` bytes from `src_ptr` starting at byte `start`
|
||||||
|
/// (1-based) into `dst_ptr`. Returns the number of bytes written.
|
||||||
|
///
|
||||||
|
/// If `sub_len` is 0 the function copies from `start` to the end of the
|
||||||
|
/// source field (mirrors RPG IV `%SUBST` two-argument form).
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// All pointers must be valid for their respective lengths.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rpg_subst(
|
||||||
|
src_ptr: *const u8,
|
||||||
|
src_len: i64,
|
||||||
|
start: i64, // 1-based
|
||||||
|
sub_len: i64, // 0 = "to end"
|
||||||
|
dst_ptr: *mut u8,
|
||||||
|
dst_len: i64,
|
||||||
|
) -> i64 {
|
||||||
|
if src_ptr.is_null() || dst_ptr.is_null() || src_len <= 0 || dst_len <= 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = unsafe { slice::from_raw_parts(src_ptr, src_len as usize) };
|
||||||
|
let dst = unsafe { slice::from_raw_parts_mut(dst_ptr, dst_len as usize) };
|
||||||
|
|
||||||
|
let from = if start <= 1 { 0 } else { (start - 1) as usize };
|
||||||
|
if from >= src.len() { return 0; }
|
||||||
|
|
||||||
|
let available = src.len() - from;
|
||||||
|
let want = if sub_len <= 0 {
|
||||||
|
available
|
||||||
|
} else {
|
||||||
|
(sub_len as usize).min(available)
|
||||||
|
};
|
||||||
|
let copy = want.min(dst.len());
|
||||||
|
|
||||||
|
dst[..copy].copy_from_slice(&src[from..from + copy]);
|
||||||
|
copy as i64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Helper: trim trailing ASCII spaces
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn rtrim_spaces(bytes: &[u8]) -> &[u8] {
|
||||||
|
let end = bytes
|
||||||
|
.iter()
|
||||||
|
.rposition(|&b| b != b' ')
|
||||||
|
.map(|i| i + 1)
|
||||||
|
.unwrap_or(0);
|
||||||
|
&bytes[..end]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Tests
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
// ── rpg_dsply ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn char_i64_positive() {
|
||||||
|
let ptr = rpg_char_i64(42);
|
||||||
|
let s = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
|
||||||
|
assert_eq!(s, "42");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn char_i64_zero() {
|
||||||
|
let ptr = rpg_char_i64(0);
|
||||||
|
let s = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
|
||||||
|
assert_eq!(s, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn char_i64_negative() {
|
||||||
|
let ptr = rpg_char_i64(-7);
|
||||||
|
let s = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
|
||||||
|
assert_eq!(s, "-7");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── rpg_concat ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_two_strings() {
|
||||||
|
let a = CString::new("Hello, ").unwrap();
|
||||||
|
let b = CString::new("World!").unwrap();
|
||||||
|
let ptr = unsafe { rpg_concat(a.as_ptr(), b.as_ptr()) };
|
||||||
|
let s = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
|
||||||
|
assert_eq!(s, "Hello, World!");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_null_pointers() {
|
||||||
|
let ptr = unsafe { rpg_concat(std::ptr::null(), std::ptr::null()) };
|
||||||
|
let s = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
|
||||||
|
assert_eq!(s, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rtrim_strips_spaces() {
|
||||||
|
assert_eq!(rtrim_spaces(b"hello "), b"hello");
|
||||||
|
assert_eq!(rtrim_spaces(b"hello"), b"hello");
|
||||||
|
assert_eq!(rtrim_spaces(b" "), b"");
|
||||||
|
assert_eq!(rtrim_spaces(b""), b"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rtrim_preserves_internal_spaces() {
|
||||||
|
assert_eq!(rtrim_spaces(b"hello world "), b"hello world");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpg_scan_finds_match() {
|
||||||
|
let hay = b"Hello, World!";
|
||||||
|
let needle = b"World";
|
||||||
|
let pos = unsafe {
|
||||||
|
rpg_scan(
|
||||||
|
needle.as_ptr(), needle.len() as i64,
|
||||||
|
hay.as_ptr(), hay.len() as i64,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
assert_eq!(pos, 8); // 1-based position of 'W'
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpg_scan_not_found() {
|
||||||
|
let hay = b"Hello";
|
||||||
|
let needle = b"XYZ";
|
||||||
|
let pos = unsafe {
|
||||||
|
rpg_scan(
|
||||||
|
needle.as_ptr(), needle.len() as i64,
|
||||||
|
hay.as_ptr(), hay.len() as i64,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
assert_eq!(pos, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpg_subst_copies_correctly() {
|
||||||
|
let src = b"Hello, World!";
|
||||||
|
let mut dst = vec![0u8; 5];
|
||||||
|
let written = unsafe {
|
||||||
|
rpg_subst(
|
||||||
|
src.as_ptr(), src.len() as i64,
|
||||||
|
8, // start at 'W' (1-based)
|
||||||
|
5,
|
||||||
|
dst.as_mut_ptr(), dst.len() as i64,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
assert_eq!(written, 5);
|
||||||
|
assert_eq!(&dst, b"World");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpg_move_char_pads_with_spaces() {
|
||||||
|
let src = b"Hi";
|
||||||
|
let mut dst = vec![0u8; 5];
|
||||||
|
unsafe {
|
||||||
|
rpg_move_char(
|
||||||
|
dst.as_mut_ptr(), dst.len() as i64,
|
||||||
|
src.as_ptr(), src.len() as i64,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(&dst, b"Hi ");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpg_move_char_truncates() {
|
||||||
|
let src = b"Hello, World!";
|
||||||
|
let mut dst = vec![0u8; 5];
|
||||||
|
unsafe {
|
||||||
|
rpg_move_char(
|
||||||
|
dst.as_mut_ptr(), dst.len() as i64,
|
||||||
|
src.as_ptr(), src.len() as i64,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(&dst, b"Hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpg_trim_removes_leading_and_trailing() {
|
||||||
|
let input = b" hello ";
|
||||||
|
let mut out_ptr: *const u8 = std::ptr::null();
|
||||||
|
let mut out_len: i64 = 0;
|
||||||
|
unsafe {
|
||||||
|
rpg_trim(
|
||||||
|
input.as_ptr(), input.len() as i64,
|
||||||
|
&mut out_ptr, &mut out_len,
|
||||||
|
);
|
||||||
|
let result = std::slice::from_raw_parts(out_ptr, out_len as usize);
|
||||||
|
assert_eq!(result, b"hello");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpg_dsply_smoke() {
|
||||||
|
// Just ensure it doesn't panic.
|
||||||
|
let msg = b"Hello, World! ";
|
||||||
|
unsafe { rpg_dsply(msg.as_ptr(), msg.len() as i64) };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpg_dsply_i64_smoke() {
|
||||||
|
rpg_dsply_i64(42);
|
||||||
|
rpg_dsply_i64(-1);
|
||||||
|
rpg_dsply_i64(i64::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpg_dsply_f64_smoke() {
|
||||||
|
rpg_dsply_f64(3.14159);
|
||||||
|
rpg_dsply_f64(-0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
samples/3np1.rpg
Normal file
44
samples/3np1.rpg
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
**FREE
|
||||||
|
Ctl-Opt Main(ThreeNPlusOne);
|
||||||
|
|
||||||
|
Dcl-Proc ThreeNPlusOne;
|
||||||
|
Dcl-S n Packed(10);
|
||||||
|
Dcl-S counter Int(10) Inz(0);
|
||||||
|
Dcl-S input_prompt VarChar(50) Inz('Enter a positive integer (or 0 to exit):');
|
||||||
|
|
||||||
|
// Use an infinite loop, exit when user enters 0
|
||||||
|
Dow (1 = 1);
|
||||||
|
Dsply input_prompt ' ' n;
|
||||||
|
|
||||||
|
If n = 0;
|
||||||
|
Leave;
|
||||||
|
EndIf;
|
||||||
|
|
||||||
|
If n < 0;
|
||||||
|
input_prompt = 'Positive integers only. Enter a number:';
|
||||||
|
Iter;
|
||||||
|
EndIf;
|
||||||
|
|
||||||
|
// Start sequence calculation
|
||||||
|
input_prompt = 'Enter a positive integer (or 0 to exit):'; // Reset prompt
|
||||||
|
counter = 0;
|
||||||
|
Dsply ('Sequence for ' + %Char(n) + ':');
|
||||||
|
|
||||||
|
Dow n > 1;
|
||||||
|
If %Rem(n:2) = 0;
|
||||||
|
// n is even, divide by 2
|
||||||
|
n = n / 2;
|
||||||
|
Else;
|
||||||
|
// n is odd, multiply by 3 and add 1
|
||||||
|
n = (n * 3) + 1;
|
||||||
|
EndIf;
|
||||||
|
|
||||||
|
counter = counter + 1;
|
||||||
|
Dsply %Char(n);
|
||||||
|
EndDo;
|
||||||
|
|
||||||
|
Dsply ('Reached 1 in ' + %Char(counter) + ' iterations.');
|
||||||
|
Dsply ' '; // Add a blank line for readability
|
||||||
|
EndDo;
|
||||||
|
|
||||||
|
End-Proc ThreeNPlusOne;
|
||||||
2
samples/3np1.rpg.stdin
Normal file
2
samples/3np1.rpg.stdin
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
8
|
||||||
|
0
|
||||||
8
samples/3np1.rpg.stdout
Normal file
8
samples/3np1.rpg.stdout
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
DSPLY Enter a positive integer (or 0 to exit):
|
||||||
|
DSPLY Sequence for 8:
|
||||||
|
DSPLY 4
|
||||||
|
DSPLY 2
|
||||||
|
DSPLY 1
|
||||||
|
DSPLY Reached 1 in 3 iterations.
|
||||||
|
DSPLY
|
||||||
|
DSPLY Enter a positive integer (or 0 to exit):
|
||||||
68
samples/dfs.rpg
Normal file
68
samples/dfs.rpg
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
**Free
|
||||||
|
Ctl-Opt DftActGrp(*No) Main(MainLine);
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// Global Graph Data (15 Nodes)
|
||||||
|
// --------------------------------------------------
|
||||||
|
Dcl-S AdjMatrix Ind Dim(15: 15) Inz(*Off);
|
||||||
|
Dcl-S Visited Ind Dim(15) Inz(*Off);
|
||||||
|
Dcl-S Found Ind Inz(*Off);
|
||||||
|
|
||||||
|
Dcl-Proc MainLine;
|
||||||
|
// 1. Setup a simple graph (Node 1 connected to 2 & 3, etc.)
|
||||||
|
AdjMatrix(1: 2) = *On; AdjMatrix(2: 4) = *On; AdjMatrix(4: 8) = *On;
|
||||||
|
AdjMatrix(1: 3) = *On; AdjMatrix(3: 5) = *On; AdjMatrix(5: 9) = *On; // Path to 9
|
||||||
|
AdjMatrix(3: 6) = *On; AdjMatrix(6: 10) = *On;
|
||||||
|
|
||||||
|
Dsply 'Starting DFS to find Node 9...';
|
||||||
|
|
||||||
|
// 2. Start Search from Node 1
|
||||||
|
DFS(1: 9);
|
||||||
|
|
||||||
|
If Not Found;
|
||||||
|
Dsply 'Node 9 was not found.';
|
||||||
|
EndIf;
|
||||||
|
|
||||||
|
Return;
|
||||||
|
End-Proc;
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// Recursive DFS Subprocedure
|
||||||
|
// --------------------------------------------------
|
||||||
|
Dcl-Proc DFS;
|
||||||
|
Dcl-Pi *N;
|
||||||
|
CurrentNode Int(10) Value;
|
||||||
|
TargetNode Int(10) Value;
|
||||||
|
End-Pi;
|
||||||
|
|
||||||
|
Dcl-S Neighbor Int(10);
|
||||||
|
|
||||||
|
// If already found elsewhere, stop exploring
|
||||||
|
If Found;
|
||||||
|
Return;
|
||||||
|
EndIf;
|
||||||
|
|
||||||
|
// Mark and Print current step
|
||||||
|
Visited(CurrentNode) = *On;
|
||||||
|
Dsply ('Visiting: ' + %Char(CurrentNode));
|
||||||
|
|
||||||
|
// Check if this is our target
|
||||||
|
If CurrentNode = TargetNode;
|
||||||
|
Dsply '*** MATCH FOUND! ***';
|
||||||
|
Found = *On;
|
||||||
|
Return;
|
||||||
|
EndIf;
|
||||||
|
|
||||||
|
// Explore Neighbors (1 to 15)
|
||||||
|
For Neighbor = 1 to 15;
|
||||||
|
If AdjMatrix(CurrentNode: Neighbor) And Not Visited(Neighbor);
|
||||||
|
DFS(Neighbor: TargetNode);
|
||||||
|
|
||||||
|
// If the recursive call found it, stop looping here too
|
||||||
|
If Found;
|
||||||
|
Return;
|
||||||
|
EndIf;
|
||||||
|
EndIf;
|
||||||
|
EndFor;
|
||||||
|
|
||||||
|
End-Proc;
|
||||||
9
samples/dfs.rpg.stdout
Normal file
9
samples/dfs.rpg.stdout
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
DSPLY Starting DFS to find Node 9...
|
||||||
|
DSPLY Visiting: 1
|
||||||
|
DSPLY Visiting: 2
|
||||||
|
DSPLY Visiting: 4
|
||||||
|
DSPLY Visiting: 8
|
||||||
|
DSPLY Visiting: 3
|
||||||
|
DSPLY Visiting: 5
|
||||||
|
DSPLY Visiting: 9
|
||||||
|
DSPLY *** MATCH FOUND! ***
|
||||||
27
samples/fib.rpg
Normal file
27
samples/fib.rpg
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
**FREE
|
||||||
|
Ctl-Opt Main(Perform_Fibonacci_Sequence);
|
||||||
|
|
||||||
|
Dcl-Proc Perform_Fibonacci_Sequence;
|
||||||
|
|
||||||
|
Dcl-s i Uns(10);
|
||||||
|
Dcl-s fib Uns(10) Dim(10);
|
||||||
|
|
||||||
|
// Display a title
|
||||||
|
Dsply ('Fibonacci Sequence:');
|
||||||
|
|
||||||
|
// Initialize the first two elements of the array
|
||||||
|
fib(1) = 0; // The sequence usually starts with 0 and 1
|
||||||
|
fib(2) = 1;
|
||||||
|
|
||||||
|
// Loop to calculate the rest of the sequence
|
||||||
|
For i = 3 to %Elem(fib);
|
||||||
|
// Each number is the sum of the two preceding ones
|
||||||
|
fib(i) = fib(i-1) + fib(i-2);
|
||||||
|
Endfor;
|
||||||
|
|
||||||
|
// Loop to display the sequence numbers
|
||||||
|
For i = 1 to %Elem(fib);
|
||||||
|
Dsply (' ' + %Char(fib(i)));
|
||||||
|
Endfor;
|
||||||
|
|
||||||
|
End-Proc Perform_Fibonacci_Sequence;
|
||||||
11
samples/fib.rpg.stdout
Normal file
11
samples/fib.rpg.stdout
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
DSPLY Fibonacci Sequence:
|
||||||
|
DSPLY 0
|
||||||
|
DSPLY 1
|
||||||
|
DSPLY 1
|
||||||
|
DSPLY 2
|
||||||
|
DSPLY 3
|
||||||
|
DSPLY 5
|
||||||
|
DSPLY 8
|
||||||
|
DSPLY 13
|
||||||
|
DSPLY 21
|
||||||
|
DSPLY 34
|
||||||
18
samples/fizzbuzz.rpg
Normal file
18
samples/fizzbuzz.rpg
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
**FREE
|
||||||
|
Ctl-Opt Main(FizzBuzz);
|
||||||
|
|
||||||
|
Dcl-Proc FizzBuzz;
|
||||||
|
Dcl-S num Int(10);
|
||||||
|
|
||||||
|
For num = 1 To 100;
|
||||||
|
If %Rem(num:3) = 0 And %Rem(num:5) = 0;
|
||||||
|
Dsply ('num - ' + %Char(num) + ' FIZZBUZZ');
|
||||||
|
ElseIf %Rem(num:3) = 0;
|
||||||
|
Dsply ('num - ' + %Char(num) + ' FIZZ');
|
||||||
|
ElseIf %Rem(num:5) = 0;
|
||||||
|
Dsply ('num - ' + %Char(num) + ' BUZZ');
|
||||||
|
Else;
|
||||||
|
Dsply ('num - ' + %Char(num));
|
||||||
|
EndIf;
|
||||||
|
EndFor;
|
||||||
|
End-Proc FizzBuzz;
|
||||||
100
samples/fizzbuzz.rpg.stdout
Normal file
100
samples/fizzbuzz.rpg.stdout
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
DSPLY num - 1
|
||||||
|
DSPLY num - 2
|
||||||
|
DSPLY num - 3 FIZZ
|
||||||
|
DSPLY num - 4
|
||||||
|
DSPLY num - 5 BUZZ
|
||||||
|
DSPLY num - 6 FIZZ
|
||||||
|
DSPLY num - 7
|
||||||
|
DSPLY num - 8
|
||||||
|
DSPLY num - 9 FIZZ
|
||||||
|
DSPLY num - 10 BUZZ
|
||||||
|
DSPLY num - 11
|
||||||
|
DSPLY num - 12 FIZZ
|
||||||
|
DSPLY num - 13
|
||||||
|
DSPLY num - 14
|
||||||
|
DSPLY num - 15 FIZZBUZZ
|
||||||
|
DSPLY num - 16
|
||||||
|
DSPLY num - 17
|
||||||
|
DSPLY num - 18 FIZZ
|
||||||
|
DSPLY num - 19
|
||||||
|
DSPLY num - 20 BUZZ
|
||||||
|
DSPLY num - 21 FIZZ
|
||||||
|
DSPLY num - 22
|
||||||
|
DSPLY num - 23
|
||||||
|
DSPLY num - 24 FIZZ
|
||||||
|
DSPLY num - 25 BUZZ
|
||||||
|
DSPLY num - 26
|
||||||
|
DSPLY num - 27 FIZZ
|
||||||
|
DSPLY num - 28
|
||||||
|
DSPLY num - 29
|
||||||
|
DSPLY num - 30 FIZZBUZZ
|
||||||
|
DSPLY num - 31
|
||||||
|
DSPLY num - 32
|
||||||
|
DSPLY num - 33 FIZZ
|
||||||
|
DSPLY num - 34
|
||||||
|
DSPLY num - 35 BUZZ
|
||||||
|
DSPLY num - 36 FIZZ
|
||||||
|
DSPLY num - 37
|
||||||
|
DSPLY num - 38
|
||||||
|
DSPLY num - 39 FIZZ
|
||||||
|
DSPLY num - 40 BUZZ
|
||||||
|
DSPLY num - 41
|
||||||
|
DSPLY num - 42 FIZZ
|
||||||
|
DSPLY num - 43
|
||||||
|
DSPLY num - 44
|
||||||
|
DSPLY num - 45 FIZZBUZZ
|
||||||
|
DSPLY num - 46
|
||||||
|
DSPLY num - 47
|
||||||
|
DSPLY num - 48 FIZZ
|
||||||
|
DSPLY num - 49
|
||||||
|
DSPLY num - 50 BUZZ
|
||||||
|
DSPLY num - 51 FIZZ
|
||||||
|
DSPLY num - 52
|
||||||
|
DSPLY num - 53
|
||||||
|
DSPLY num - 54 FIZZ
|
||||||
|
DSPLY num - 55 BUZZ
|
||||||
|
DSPLY num - 56
|
||||||
|
DSPLY num - 57 FIZZ
|
||||||
|
DSPLY num - 58
|
||||||
|
DSPLY num - 59
|
||||||
|
DSPLY num - 60 FIZZBUZZ
|
||||||
|
DSPLY num - 61
|
||||||
|
DSPLY num - 62
|
||||||
|
DSPLY num - 63 FIZZ
|
||||||
|
DSPLY num - 64
|
||||||
|
DSPLY num - 65 BUZZ
|
||||||
|
DSPLY num - 66 FIZZ
|
||||||
|
DSPLY num - 67
|
||||||
|
DSPLY num - 68
|
||||||
|
DSPLY num - 69 FIZZ
|
||||||
|
DSPLY num - 70 BUZZ
|
||||||
|
DSPLY num - 71
|
||||||
|
DSPLY num - 72 FIZZ
|
||||||
|
DSPLY num - 73
|
||||||
|
DSPLY num - 74
|
||||||
|
DSPLY num - 75 FIZZBUZZ
|
||||||
|
DSPLY num - 76
|
||||||
|
DSPLY num - 77
|
||||||
|
DSPLY num - 78 FIZZ
|
||||||
|
DSPLY num - 79
|
||||||
|
DSPLY num - 80 BUZZ
|
||||||
|
DSPLY num - 81 FIZZ
|
||||||
|
DSPLY num - 82
|
||||||
|
DSPLY num - 83
|
||||||
|
DSPLY num - 84 FIZZ
|
||||||
|
DSPLY num - 85 BUZZ
|
||||||
|
DSPLY num - 86
|
||||||
|
DSPLY num - 87 FIZZ
|
||||||
|
DSPLY num - 88
|
||||||
|
DSPLY num - 89
|
||||||
|
DSPLY num - 90 FIZZBUZZ
|
||||||
|
DSPLY num - 91
|
||||||
|
DSPLY num - 92
|
||||||
|
DSPLY num - 93 FIZZ
|
||||||
|
DSPLY num - 94
|
||||||
|
DSPLY num - 95 BUZZ
|
||||||
|
DSPLY num - 96 FIZZ
|
||||||
|
DSPLY num - 97
|
||||||
|
DSPLY num - 98
|
||||||
|
DSPLY num - 99 FIZZ
|
||||||
|
DSPLY num - 100 BUZZ
|
||||||
13
samples/for.rpg
Normal file
13
samples/for.rpg
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
**FREE
|
||||||
|
Ctl-Opt Main(For);
|
||||||
|
|
||||||
|
Dcl-Proc For;
|
||||||
|
dcl-s num int(10);
|
||||||
|
|
||||||
|
for num = 1 to 3;
|
||||||
|
dsply ('i = ' + %char(num));
|
||||||
|
endfor;
|
||||||
|
for num = 5 downto 1 by 1;
|
||||||
|
dsply ('i = ' + %char(num));
|
||||||
|
endfor;
|
||||||
|
End-Proc For;
|
||||||
8
samples/for.rpg.stdout
Normal file
8
samples/for.rpg.stdout
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
DSPLY i = 1
|
||||||
|
DSPLY i = 2
|
||||||
|
DSPLY i = 3
|
||||||
|
DSPLY i = 5
|
||||||
|
DSPLY i = 4
|
||||||
|
DSPLY i = 3
|
||||||
|
DSPLY i = 2
|
||||||
|
DSPLY i = 1
|
||||||
8
samples/hello.rpg
Normal file
8
samples/hello.rpg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
CTL-OPT DFTACTGRP(*NO);
|
||||||
|
|
||||||
|
DCL-S greeting CHAR(25) INZ('Hello, World!');
|
||||||
|
|
||||||
|
DCL-PROC main EXPORT;
|
||||||
|
DSPLY greeting;
|
||||||
|
RETURN;
|
||||||
|
END-PROC;
|
||||||
1
samples/hello.rpg.stdout
Normal file
1
samples/hello.rpg.stdout
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DSPLY Hello, World!
|
||||||
658
src/ast.rs
Normal file
658
src/ast.rs
Normal file
@@ -0,0 +1,658 @@
|
|||||||
|
//! ast.rs — Typed Abstract Syntax Tree for RPG IV free-format programs.
|
||||||
|
//!
|
||||||
|
//! This module defines the in-memory representation produced by the lowering
|
||||||
|
//! pass (`lower.rs`) and consumed by the LLVM code-generator (`codegen.rs`).
|
||||||
|
//!
|
||||||
|
//! Only the subset of the language that is needed to compile `hello.rpg` (and
|
||||||
|
//! small programs like it) is fully fleshed out. Everything else is kept as
|
||||||
|
//! placeholder variants so the lowering pass can represent the whole parse tree
|
||||||
|
//! without panicking, and the codegen can skip unimplemented nodes gracefully.
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Top-level program
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// A complete RPG IV source file.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Program {
|
||||||
|
/// Zero or more top-level declarations (CTL-OPT, DCL-S, DCL-C, DCL-DS,
|
||||||
|
/// file declarations, subroutines …).
|
||||||
|
pub declarations: Vec<Declaration>,
|
||||||
|
/// Zero or more procedure definitions (`DCL-PROC … END-PROC`).
|
||||||
|
pub procedures: Vec<Procedure>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Declarations
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Declaration {
|
||||||
|
/// `CTL-OPT keyword-list;`
|
||||||
|
ControlSpec(ControlSpec),
|
||||||
|
/// `DCL-S name type [keywords];`
|
||||||
|
Standalone(StandaloneDecl),
|
||||||
|
/// `DCL-C name literal;` or `DCL-C name CONST(literal);`
|
||||||
|
Constant(ConstantDecl),
|
||||||
|
/// `DCL-C name *named-constant;`
|
||||||
|
NamedConstantDecl(NamedConstantDecl),
|
||||||
|
/// `DCL-DS name … END-DS;`
|
||||||
|
DataStructure(DataStructureDecl),
|
||||||
|
/// `DCL-F name …;`
|
||||||
|
File(FileDecl),
|
||||||
|
/// `BEG-SR name; … END-SR;`
|
||||||
|
Subroutine(Subroutine),
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Control spec ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ControlSpec {
|
||||||
|
pub keywords: Vec<CtlKeyword>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum CtlKeyword {
|
||||||
|
DftActGrp(bool), // *YES / *NO
|
||||||
|
NoMain,
|
||||||
|
Main(String),
|
||||||
|
Other(String), // catch-all for keywords we don't generate code for
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Standalone variable ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct StandaloneDecl {
|
||||||
|
pub name: String,
|
||||||
|
pub ty: TypeSpec,
|
||||||
|
pub keywords: Vec<VarKeyword>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Constant declaration ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ConstantDecl {
|
||||||
|
pub name: String,
|
||||||
|
pub value: Literal,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct NamedConstantDecl {
|
||||||
|
pub name: String,
|
||||||
|
pub value: NamedConstant,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Data structure ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DataStructureDecl {
|
||||||
|
pub name: String,
|
||||||
|
pub keywords: Vec<DsKeyword>,
|
||||||
|
pub fields: Vec<DsField>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum DsKeyword {
|
||||||
|
Qualified,
|
||||||
|
Template,
|
||||||
|
Other(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DsField {
|
||||||
|
pub name: String,
|
||||||
|
pub ty: TypeSpec,
|
||||||
|
pub keywords: Vec<VarKeyword>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── File declaration ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FileDecl {
|
||||||
|
pub name: String,
|
||||||
|
pub keywords: Vec<String>, // simplified — not code-gen'd
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Subroutine ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Subroutine {
|
||||||
|
pub name: String,
|
||||||
|
pub body: Vec<Statement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Type specifications
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum TypeSpec {
|
||||||
|
/// `CHAR(n)` — fixed-length character field.
|
||||||
|
Char(Box<Expression>),
|
||||||
|
/// `VARCHAR(n)` — variable-length character.
|
||||||
|
VarChar(Box<Expression>),
|
||||||
|
/// `INT(n)` — signed integer (n = 3, 5, 10, or 20).
|
||||||
|
Int(Box<Expression>),
|
||||||
|
/// `UNS(n)` — unsigned integer.
|
||||||
|
Uns(Box<Expression>),
|
||||||
|
/// `FLOAT(n)` — floating-point.
|
||||||
|
Float(Box<Expression>),
|
||||||
|
/// `PACKED(digits:decimals)`
|
||||||
|
Packed(Box<Expression>, Box<Expression>),
|
||||||
|
/// `ZONED(digits:decimals)`
|
||||||
|
Zoned(Box<Expression>, Box<Expression>),
|
||||||
|
/// `BINDEC(digits:decimals)`
|
||||||
|
Bindec(Box<Expression>, Box<Expression>),
|
||||||
|
/// `IND` — indicator (boolean).
|
||||||
|
Ind,
|
||||||
|
/// `DATE [(*fmt)]`
|
||||||
|
Date,
|
||||||
|
/// `TIME [(*fmt)]`
|
||||||
|
Time,
|
||||||
|
/// `TIMESTAMP`
|
||||||
|
Timestamp,
|
||||||
|
/// `POINTER`
|
||||||
|
Pointer,
|
||||||
|
/// `LIKE(name)`
|
||||||
|
Like(String),
|
||||||
|
/// `LIKEDS(name)`
|
||||||
|
LikeDs(String),
|
||||||
|
/// Unrecognised / not yet implemented type.
|
||||||
|
Unknown(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeSpec {
|
||||||
|
/// Return the number of bytes this type occupies at runtime on a 64-bit
|
||||||
|
/// Linux host. Returns `None` for types whose size is not statically known.
|
||||||
|
pub fn byte_size(&self) -> Option<u64> {
|
||||||
|
match self {
|
||||||
|
TypeSpec::Char(expr) | TypeSpec::VarChar(expr) => {
|
||||||
|
if let Expression::Literal(Literal::Integer(n)) = expr.as_ref() {
|
||||||
|
Some(*n as u64)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TypeSpec::Int(expr) | TypeSpec::Uns(expr) => {
|
||||||
|
if let Expression::Literal(Literal::Integer(n)) = expr.as_ref() {
|
||||||
|
Some(match n {
|
||||||
|
3 => 1,
|
||||||
|
5 => 2,
|
||||||
|
10 => 4,
|
||||||
|
20 => 8,
|
||||||
|
_ => 8, // default to 8 bytes
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TypeSpec::Float(expr) => {
|
||||||
|
if let Expression::Literal(Literal::Integer(n)) = expr.as_ref() {
|
||||||
|
Some(if *n <= 4 { 4 } else { 8 })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TypeSpec::Ind => Some(1),
|
||||||
|
TypeSpec::Pointer => Some(8),
|
||||||
|
TypeSpec::Packed(digits, _) => {
|
||||||
|
if let Expression::Literal(Literal::Integer(n)) = digits.as_ref() {
|
||||||
|
Some((*n as u64 / 2) + 1)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Variable / declaration keywords
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum VarKeyword {
|
||||||
|
/// `INZ` — default initialisation.
|
||||||
|
Inz,
|
||||||
|
/// `INZ(expr)` — explicit initialisation value.
|
||||||
|
InzExpr(Expression),
|
||||||
|
/// `INZ(*named-constant)` — initialise to named constant.
|
||||||
|
InzNamed(NamedConstant),
|
||||||
|
Static,
|
||||||
|
/// `DIM(n)` — declares the variable as a 1-D array with `n` elements.
|
||||||
|
Dim(Expression),
|
||||||
|
/// `DIM(rows: cols)` — declares the variable as a 2-D array.
|
||||||
|
Dim2(Expression, Expression),
|
||||||
|
Other(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Procedures
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Procedure {
|
||||||
|
pub name: String,
|
||||||
|
pub exported: bool,
|
||||||
|
pub pi: Option<PiSpec>,
|
||||||
|
/// Local declarations (DCL-S, DCL-C, etc.) inside the procedure.
|
||||||
|
pub locals: Vec<Declaration>,
|
||||||
|
pub body: Vec<Statement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Procedure Interface specification (`DCL-PI … END-PI`).
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PiSpec {
|
||||||
|
pub name: String,
|
||||||
|
pub return_ty: Option<TypeSpec>,
|
||||||
|
pub params: Vec<PiParam>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PiParam {
|
||||||
|
pub name: String,
|
||||||
|
pub ty: TypeSpec,
|
||||||
|
pub keywords: Vec<ParamKeyword>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ParamKeyword {
|
||||||
|
Value,
|
||||||
|
Const,
|
||||||
|
Other(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Statements
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Statement {
|
||||||
|
/// `lvalue = expr;` or `EVAL lvalue = expr;`
|
||||||
|
Assign(AssignStmt),
|
||||||
|
/// `IF expr; … [ELSEIF …] [ELSE …] ENDIF;`
|
||||||
|
If(IfStmt),
|
||||||
|
/// `DOW expr; … ENDDO;`
|
||||||
|
DoWhile(DoWhileStmt),
|
||||||
|
/// `DOU expr; … ENDDO;`
|
||||||
|
DoUntil(DoUntilStmt),
|
||||||
|
/// `FOR i = start TO/DOWNTO end [BY step]; … ENDFOR;`
|
||||||
|
For(ForStmt),
|
||||||
|
/// `SELECT; WHEN … [OTHER …] ENDSL;`
|
||||||
|
Select(SelectStmt),
|
||||||
|
/// `MONITOR; … ON-ERROR … ENDMON;`
|
||||||
|
Monitor(MonitorStmt),
|
||||||
|
/// `CALLP name(args);` or bare procedure call `name(args);`
|
||||||
|
CallP(CallPStmt),
|
||||||
|
/// `RETURN [expr];`
|
||||||
|
Return(ReturnStmt),
|
||||||
|
/// `LEAVE;`
|
||||||
|
Leave,
|
||||||
|
/// `ITER;`
|
||||||
|
Iter,
|
||||||
|
/// `LEAVESR;`
|
||||||
|
LeaveSr,
|
||||||
|
/// `EXSR name;`
|
||||||
|
ExSr(String),
|
||||||
|
/// `DSPLY expr;`
|
||||||
|
Dsply(DsplyStmt),
|
||||||
|
/// `RESET lvalue;` / `RESET *ALL;`
|
||||||
|
Reset(ResetStmt),
|
||||||
|
/// `CLEAR lvalue;`
|
||||||
|
Clear(LValue),
|
||||||
|
/// Any I/O statement (READ, WRITE, CHAIN, etc.) — kept as opaque for now.
|
||||||
|
Io(IoStatement),
|
||||||
|
/// Catch-all for statements not yet lowered.
|
||||||
|
Unimplemented(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Assignment ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AssignStmt {
|
||||||
|
pub target: LValue,
|
||||||
|
pub value: Expression,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── If / ElseIf / Else ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct IfStmt {
|
||||||
|
pub condition: Expression,
|
||||||
|
pub then_body: Vec<Statement>,
|
||||||
|
pub elseifs: Vec<ElseIf>,
|
||||||
|
pub else_body: Option<Vec<Statement>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ElseIf {
|
||||||
|
pub condition: Expression,
|
||||||
|
pub body: Vec<Statement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── DOW loop ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DoWhileStmt {
|
||||||
|
pub condition: Expression,
|
||||||
|
pub body: Vec<Statement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── DOU loop ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DoUntilStmt {
|
||||||
|
pub condition: Expression,
|
||||||
|
pub body: Vec<Statement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── FOR loop ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ForStmt {
|
||||||
|
pub var: String,
|
||||||
|
pub start: Expression,
|
||||||
|
pub limit: Expression,
|
||||||
|
pub step: Option<Expression>,
|
||||||
|
pub downto: bool,
|
||||||
|
pub body: Vec<Statement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── SELECT / WHEN ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SelectStmt {
|
||||||
|
pub whens: Vec<WhenClause>,
|
||||||
|
pub other: Option<Vec<Statement>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct WhenClause {
|
||||||
|
pub condition: Expression,
|
||||||
|
pub body: Vec<Statement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── MONITOR ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct MonitorStmt {
|
||||||
|
pub body: Vec<Statement>,
|
||||||
|
pub handlers: Vec<OnError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct OnError {
|
||||||
|
pub codes: Vec<ErrorCode>,
|
||||||
|
pub body: Vec<Statement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ErrorCode {
|
||||||
|
Integer(u32),
|
||||||
|
Program,
|
||||||
|
File,
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── CALLP ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CallPStmt {
|
||||||
|
pub name: String,
|
||||||
|
pub args: Vec<Arg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── RETURN ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ReturnStmt {
|
||||||
|
pub value: Option<Expression>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── DSPLY ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DsplyStmt {
|
||||||
|
/// The expression to display.
|
||||||
|
pub expr: Expression,
|
||||||
|
/// Optional message queue identifier (two-operand form).
|
||||||
|
pub msg_q: Option<String>,
|
||||||
|
pub response: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── RESET ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ResetStmt {
|
||||||
|
Target(LValue),
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── I/O (opaque) ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum IoStatement {
|
||||||
|
Read { file: String },
|
||||||
|
ReadP { file: String },
|
||||||
|
Write { record: String },
|
||||||
|
Update { record: String },
|
||||||
|
Delete { key: Expression, file: String },
|
||||||
|
Chain { key: Expression, file: String },
|
||||||
|
SetLL { key: SetKey, file: String },
|
||||||
|
SetGT { key: SetKey, file: String },
|
||||||
|
Open { file: String },
|
||||||
|
Close { file: Option<String> }, // None = *ALL
|
||||||
|
Except { format: Option<String> },
|
||||||
|
ExFmt { format: String },
|
||||||
|
Post { file: String },
|
||||||
|
Feod { file: String },
|
||||||
|
Unlock { file: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SetKey {
|
||||||
|
Expr(Expression),
|
||||||
|
Start,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// L-values
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// An assignable location.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum LValue {
|
||||||
|
/// Simple or dotted name: `myVar` or `ds.field`.
|
||||||
|
Name(QualifiedName),
|
||||||
|
/// Array element: `arr(i)`.
|
||||||
|
Index(QualifiedName, Vec<Expression>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LValue {
|
||||||
|
/// Return the base name (first component of the qualified name).
|
||||||
|
pub fn base_name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
LValue::Name(q) | LValue::Index(q, _) => &q.parts[0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Expressions
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Expression {
|
||||||
|
Literal(Literal),
|
||||||
|
Named(NamedConstant),
|
||||||
|
Special(SpecialValue),
|
||||||
|
Variable(QualifiedName),
|
||||||
|
/// Array / function-style subscript: `name(idx)`.
|
||||||
|
Index(QualifiedName, Vec<Expression>),
|
||||||
|
/// Procedure / built-in call as expression: `name(args)`.
|
||||||
|
Call(String, Vec<Arg>),
|
||||||
|
BuiltIn(BuiltIn),
|
||||||
|
UnaryMinus(Box<Expression>),
|
||||||
|
UnaryPlus(Box<Expression>),
|
||||||
|
BinOp(BinOp, Box<Expression>, Box<Expression>),
|
||||||
|
Not(Box<Expression>),
|
||||||
|
Paren(Box<Expression>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum BinOp {
|
||||||
|
Add, Sub, Mul, Div, Pow,
|
||||||
|
Eq, Ne, Lt, Le, Gt, Ge,
|
||||||
|
And, Or,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Literals
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Literal {
|
||||||
|
String(String),
|
||||||
|
Integer(i64),
|
||||||
|
Float(f64),
|
||||||
|
Hex(Vec<u8>),
|
||||||
|
/// `*ON` / `*OFF` as a literal.
|
||||||
|
Indicator(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Named constants (`*ON`, `*OFF`, `*BLANK`, …)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum NamedConstant {
|
||||||
|
On,
|
||||||
|
Off,
|
||||||
|
Blank,
|
||||||
|
Blanks,
|
||||||
|
Zero,
|
||||||
|
Zeros,
|
||||||
|
HiVal,
|
||||||
|
LoVal,
|
||||||
|
Null,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Special values (`*IN`, `*START`, …)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum SpecialValue {
|
||||||
|
/// `*IN(n)` — indicator by number.
|
||||||
|
In(Box<Expression>),
|
||||||
|
InAll,
|
||||||
|
On,
|
||||||
|
Off,
|
||||||
|
Blank,
|
||||||
|
Blanks,
|
||||||
|
Zero,
|
||||||
|
Zeros,
|
||||||
|
HiVal,
|
||||||
|
LoVal,
|
||||||
|
Null,
|
||||||
|
/// `*ALL'string'`
|
||||||
|
All(String),
|
||||||
|
Omit,
|
||||||
|
This,
|
||||||
|
Same,
|
||||||
|
Start,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Built-in functions
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// The RPG IV `%BUILTIN(…)` functions we actually lower to code.
|
||||||
|
/// All others are wrapped in `Other`.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum BuiltIn {
|
||||||
|
/// `%LEN(identifier)` — byte length of a field.
|
||||||
|
Len(Box<Expression>),
|
||||||
|
/// `%TRIM(expr)` — trim leading and trailing blanks.
|
||||||
|
Trim(Box<Expression>),
|
||||||
|
/// `%TRIML(expr)` — trim leading blanks.
|
||||||
|
TrimL(Box<Expression>),
|
||||||
|
/// `%TRIMR(expr)` — trim trailing blanks.
|
||||||
|
TrimR(Box<Expression>),
|
||||||
|
/// `%CHAR(expr)` — convert to character string.
|
||||||
|
Char(Box<Expression>),
|
||||||
|
/// `%INT(expr)` — convert to integer.
|
||||||
|
Int(Box<Expression>),
|
||||||
|
/// `%DEC(expr:digits:decimals)` — convert to packed decimal.
|
||||||
|
Dec(Box<Expression>, Box<Expression>, Box<Expression>),
|
||||||
|
/// `%ABS(expr)` — absolute value.
|
||||||
|
Abs(Box<Expression>),
|
||||||
|
/// `%SQRT(expr)` — square root.
|
||||||
|
Sqrt(Box<Expression>),
|
||||||
|
/// `%EOF[(file)]`
|
||||||
|
Eof(Option<String>),
|
||||||
|
/// `%FOUND[(file)]`
|
||||||
|
Found(Option<String>),
|
||||||
|
/// `%ERROR()`
|
||||||
|
Error,
|
||||||
|
/// `%SUBST(str:start:len)` or `%SUBST(str:start)`.
|
||||||
|
Subst(Box<Expression>, Box<Expression>, Option<Box<Expression>>),
|
||||||
|
/// `%SCAN(pattern:source[:start])`.
|
||||||
|
Scan(Box<Expression>, Box<Expression>, Option<Box<Expression>>),
|
||||||
|
/// `%SIZE(identifier)`.
|
||||||
|
Size(Box<Expression>),
|
||||||
|
/// `%ADDR(identifier)`.
|
||||||
|
Addr(Box<Expression>),
|
||||||
|
/// `%ALLOC(size)`.
|
||||||
|
Alloc(Box<Expression>),
|
||||||
|
/// `%REM(a:b)`.
|
||||||
|
Rem(Box<Expression>, Box<Expression>),
|
||||||
|
/// `%DIV(a:b)`.
|
||||||
|
Div(Box<Expression>, Box<Expression>),
|
||||||
|
/// `%ELEM(array)` — number of elements in an array.
|
||||||
|
Elem(Box<Expression>),
|
||||||
|
/// Any built-in we haven't individually modelled.
|
||||||
|
Other(String, Vec<Expression>),
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Qualified names and argument lists
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// A dot-separated name: `ds.subDs.leaf`.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct QualifiedName {
|
||||||
|
pub parts: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QualifiedName {
|
||||||
|
pub fn simple(name: impl Into<String>) -> Self {
|
||||||
|
QualifiedName { parts: vec![name.into()] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_simple(&self) -> bool {
|
||||||
|
self.parts.len() == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the leaf (last) component.
|
||||||
|
pub fn leaf(&self) -> &str {
|
||||||
|
self.parts.last().map(|s| s.as_str()).unwrap_or("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for QualifiedName {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.parts.join("."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A call argument.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Arg {
|
||||||
|
Expr(Expression),
|
||||||
|
Omit,
|
||||||
|
}
|
||||||
2309
src/codegen.rs
Normal file
2309
src/codegen.rs
Normal file
File diff suppressed because it is too large
Load Diff
9
src/lib.rs
Normal file
9
src/lib.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
//! rust-langrpg — RPG IV free-format parser library
|
||||||
|
//!
|
||||||
|
//! Provides the typed AST ([`ast`]), recursive-descent parser and lowering
|
||||||
|
//! pass ([`lower`]), and LLVM code-generator ([`codegen`]) used by the
|
||||||
|
//! compiler pipeline.
|
||||||
|
|
||||||
|
pub mod ast;
|
||||||
|
pub mod lower;
|
||||||
|
pub mod codegen;
|
||||||
2908
src/lower.rs
Normal file
2908
src/lower.rs
Normal file
File diff suppressed because it is too large
Load Diff
1014
src/main.rs
1014
src/main.rs
File diff suppressed because it is too large
Load Diff
693
src/rpg.bnf
693
src/rpg.bnf
@@ -1,693 +0,0 @@
|
|||||||
<wsc> ::= ' ' | '\t' | '\n' | '\r'
|
|
||||||
<ws> ::= <wsc> | <wsc> <ws>
|
|
||||||
<opt-ws> ::= <ws> | ''
|
|
||||||
|
|
||||||
<program> ::= <opt-ws> <program-body> <opt-ws>
|
|
||||||
|
|
||||||
<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> <ws> <proc-keyword-list> <opt-ws> ';' <opt-ws> <procedure-body> <opt-ws> 'END-PROC' <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> ::= ' ' | '!' | '"' | '#' | '$' | '%' | '&' | '('
|
|
||||||
| ')' | '*' | '+' | ',' | '-' | '.' | '/' | ':'
|
|
||||||
| '<' | '=' | '>' | '?' | '@' | '[' | '\\'
|
|
||||||
| ']' | '^' | '_' | '`' | '{' | '|' | '}' | '~'
|
|
||||||
178
tests/hello_rpg.rs
Normal file
178
tests/hello_rpg.rs
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
//! Integration tests for the compiler binary against the Hello World program.
|
||||||
|
//!
|
||||||
|
//! These tests exercise the full compilation pipeline:
|
||||||
|
//! hello.rpg → recursive-descent parser → AST lowering → LLVM codegen → native binary
|
||||||
|
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
/// `CARGO_BIN_EXE_rust-langrpg` is injected by Cargo for integration tests and
|
||||||
|
/// always points at the freshly-built binary under `target/`.
|
||||||
|
const BIN: &str = env!("CARGO_BIN_EXE_rust-langrpg");
|
||||||
|
|
||||||
|
/// Absolute path to hello.rpg, resolved at compile time relative to the crate
|
||||||
|
/// root so the test works regardless of the working directory.
|
||||||
|
const HELLO_RPG: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/samples/hello.rpg");
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Helper
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn run(args: &[&str]) -> std::process::Output {
|
||||||
|
Command::new(BIN)
|
||||||
|
.args(args)
|
||||||
|
.output()
|
||||||
|
.unwrap_or_else(|e| panic!("failed to spawn '{}': {e}", BIN))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Tests
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// The compiler should exit 0 when given hello.rpg (no -o flag — the output
|
||||||
|
/// executable is written to a.out but the important thing is no error).
|
||||||
|
#[test]
|
||||||
|
fn hello_rpg_exits_ok() {
|
||||||
|
let out_path = std::env::temp_dir().join("hello_rpg_exits_ok.out");
|
||||||
|
let out = run(&["-o", out_path.to_str().unwrap(), HELLO_RPG]);
|
||||||
|
assert!(
|
||||||
|
out.status.success(),
|
||||||
|
"expected exit 0 for hello.rpg\nstderr: {}",
|
||||||
|
String::from_utf8_lossy(&out.stderr),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When -o is supplied the output file must be created as a non-empty compiled
|
||||||
|
/// artifact (executable binary).
|
||||||
|
#[test]
|
||||||
|
fn hello_rpg_produces_output_file() {
|
||||||
|
let out_path = std::env::temp_dir().join("hello_rpg_test_output.out");
|
||||||
|
|
||||||
|
let out = run(&["-o", out_path.to_str().unwrap(), HELLO_RPG]);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
out.status.success(),
|
||||||
|
"compiler failed with -o flag\nstderr: {}",
|
||||||
|
String::from_utf8_lossy(&out.stderr),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
out_path.exists(),
|
||||||
|
"output file '{}' was not created",
|
||||||
|
out_path.display(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let metadata = std::fs::metadata(&out_path)
|
||||||
|
.unwrap_or_else(|e| panic!("could not stat output file: {e}"));
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
metadata.len() > 0,
|
||||||
|
"output file is empty — expected a compiled artifact",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The compiler must print the file name to stderr with an "ok:" prefix when
|
||||||
|
/// compilation succeeds.
|
||||||
|
#[test]
|
||||||
|
fn hello_rpg_reports_ok_on_stderr() {
|
||||||
|
let out_path = std::env::temp_dir().join("hello_rpg_reports_ok.out");
|
||||||
|
let out = run(&["-o", out_path.to_str().unwrap(), HELLO_RPG]);
|
||||||
|
|
||||||
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||||
|
assert!(
|
||||||
|
stderr.contains("ok:"),
|
||||||
|
"expected stderr to contain 'ok:'\ngot: {stderr}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `--emit-ir` must print LLVM IR to stdout and exit 0.
|
||||||
|
///
|
||||||
|
/// The IR must contain:
|
||||||
|
/// * At least one `define` (a function definition)
|
||||||
|
/// * A reference to `rpg_dsply` (the DSPLY runtime call)
|
||||||
|
/// * A `@main` entry point (the C main wrapper)
|
||||||
|
#[test]
|
||||||
|
fn hello_rpg_emit_ir() {
|
||||||
|
let out = run(&["--emit-ir", HELLO_RPG]);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
out.status.success(),
|
||||||
|
"expected exit 0 with --emit-ir\nstderr: {}",
|
||||||
|
String::from_utf8_lossy(&out.stderr),
|
||||||
|
);
|
||||||
|
|
||||||
|
let ir = String::from_utf8_lossy(&out.stdout);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
ir.contains("define"),
|
||||||
|
"--emit-ir should produce at least one LLVM function definition\nIR:\n{}",
|
||||||
|
&ir[..ir.len().min(2000)],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
ir.contains("rpg_dsply"),
|
||||||
|
"--emit-ir should reference the rpg_dsply runtime symbol\nIR:\n{}",
|
||||||
|
&ir[..ir.len().min(2000)],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
ir.contains("@main"),
|
||||||
|
"--emit-ir should contain a @main entry-point wrapper\nIR:\n{}",
|
||||||
|
&ir[..ir.len().min(2000)],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `--no-link` should produce a `.o` object file and exit 0.
|
||||||
|
#[test]
|
||||||
|
fn hello_rpg_no_link_produces_object() {
|
||||||
|
let obj_path = std::env::temp_dir().join("hello_rpg_test.o");
|
||||||
|
|
||||||
|
let out = run(&["--no-link", "-o", obj_path.to_str().unwrap(), HELLO_RPG]);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
out.status.success(),
|
||||||
|
"expected exit 0 with --no-link\nstderr: {}",
|
||||||
|
String::from_utf8_lossy(&out.stderr),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
obj_path.exists(),
|
||||||
|
"object file '{}' was not created",
|
||||||
|
obj_path.display(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let metadata = std::fs::metadata(&obj_path)
|
||||||
|
.unwrap_or_else(|e| panic!("could not stat object file: {e}"));
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
metadata.len() > 0,
|
||||||
|
"object file is empty — expected compiled LLVM output",
|
||||||
|
);
|
||||||
|
|
||||||
|
// A valid ELF object file starts with the ELF magic bytes 0x7f 'E' 'L' 'F'.
|
||||||
|
let bytes = std::fs::read(&obj_path)
|
||||||
|
.unwrap_or_else(|e| panic!("could not read object file: {e}"));
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
bytes.starts_with(b"\x7fELF"),
|
||||||
|
"expected an ELF object file, got unexpected magic bytes: {:?}",
|
||||||
|
&bytes[..bytes.len().min(4)],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Passing a non-existent file should cause the compiler to exit non-zero and
|
||||||
|
/// print an error to stderr.
|
||||||
|
#[test]
|
||||||
|
fn nonexistent_source_exits_error() {
|
||||||
|
let out = run(&["no_such_file_xyz.rpg"]);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!out.status.success(),
|
||||||
|
"expected non-zero exit for a missing source file",
|
||||||
|
);
|
||||||
|
|
||||||
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||||
|
assert!(
|
||||||
|
stderr.contains("error"),
|
||||||
|
"expected an error message on stderr\ngot: {stderr}",
|
||||||
|
);
|
||||||
|
}
|
||||||
276
tests/samples.rs
Normal file
276
tests/samples.rs
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
//! Integration tests — compile every `.rpg` sample and validate stdout.
|
||||||
|
//!
|
||||||
|
//! Tests are discovered automatically: every file in `samples/` whose name
|
||||||
|
//! ends with `.rpg` becomes a test case. Companion files control behaviour:
|
||||||
|
//!
|
||||||
|
//! | file | purpose |
|
||||||
|
//! |-----------------------|--------------------------------------------|
|
||||||
|
//! | `<name>.rpg.stdout` | expected stdout (required to run the test) |
|
||||||
|
//! | `<name>.rpg.stdin` | bytes piped to stdin (optional) |
|
||||||
|
//!
|
||||||
|
//! # Adding a new sample
|
||||||
|
//!
|
||||||
|
//! 1. Drop `samples/<name>.rpg` into the directory.
|
||||||
|
//! 2. Create `samples/<name>.rpg.stdout` with the expected output.
|
||||||
|
//! 3. Optionally create `samples/<name>.rpg.stdin` with any required input.
|
||||||
|
//!
|
||||||
|
//! No changes to this file are needed.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fs,
|
||||||
|
io::Write as _,
|
||||||
|
path::PathBuf,
|
||||||
|
process::{Command, Stdio},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Path to the freshly-built compiler binary, injected by Cargo.
|
||||||
|
const BIN: &str = env!("CARGO_BIN_EXE_rust-langrpg");
|
||||||
|
|
||||||
|
/// Absolute path to the `samples/` directory, resolved at compile time.
|
||||||
|
const SAMPLES_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/samples");
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Discovery
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Returns a sorted list of `.rpg` file names found in `SAMPLES_DIR`.
|
||||||
|
fn discover_samples() -> Vec<String> {
|
||||||
|
let dir = fs::read_dir(SAMPLES_DIR)
|
||||||
|
.unwrap_or_else(|e| panic!("cannot read samples dir '{}': {e}", SAMPLES_DIR));
|
||||||
|
|
||||||
|
let mut names: Vec<String> = dir
|
||||||
|
.filter_map(|entry| {
|
||||||
|
let entry = entry.expect("dir entry error");
|
||||||
|
let name = entry.file_name().into_string().expect("non-UTF-8 filename");
|
||||||
|
if name.ends_with(".rpg") && !name.contains('.') == false {
|
||||||
|
// Accept only plain `<stem>.rpg` (no extra dots apart from the extension).
|
||||||
|
let stem = &name[..name.len() - 4]; // strip ".rpg"
|
||||||
|
if !stem.contains('.') {
|
||||||
|
return Some(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
names.sort();
|
||||||
|
names
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Driver
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Compile and run one sample, returning `Err(message)` on any failure.
|
||||||
|
fn run_one(sample_name: &str) -> Result<(), String> {
|
||||||
|
let base = PathBuf::from(SAMPLES_DIR);
|
||||||
|
let src = base.join(sample_name);
|
||||||
|
let stdout_path = base.join(format!("{}.stdout", sample_name));
|
||||||
|
let stdin_path = base.join(format!("{}.stdin", sample_name));
|
||||||
|
|
||||||
|
// Skip samples that have no golden file yet — they are works in progress.
|
||||||
|
if !stdout_path.exists() {
|
||||||
|
return Err(format!(
|
||||||
|
"SKIP '{}': no expected-output file '{}'",
|
||||||
|
sample_name,
|
||||||
|
stdout_path.display(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdin_bytes: Vec<u8> = if stdin_path.exists() {
|
||||||
|
fs::read(&stdin_path)
|
||||||
|
.map_err(|e| format!("cannot read stdin file '{}': {e}", stdin_path.display()))?
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── 1. Compile ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
let safe_stem = sample_name
|
||||||
|
.replace('.', "_")
|
||||||
|
.replace(std::path::MAIN_SEPARATOR, "_");
|
||||||
|
let exe = std::env::temp_dir().join(format!("rpg_sample_test_{}.out", safe_stem));
|
||||||
|
|
||||||
|
let compile_out = Command::new(BIN)
|
||||||
|
.args(["-o", exe.to_str().unwrap(), src.to_str().unwrap()])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("failed to spawn compiler for '{}': {e}", sample_name))?;
|
||||||
|
|
||||||
|
if !compile_out.status.success() {
|
||||||
|
return Err(format!(
|
||||||
|
"compilation of '{}' failed (exit {})\nstderr:\n{}",
|
||||||
|
sample_name,
|
||||||
|
compile_out.status,
|
||||||
|
String::from_utf8_lossy(&compile_out.stderr),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exe.exists() {
|
||||||
|
return Err(format!(
|
||||||
|
"compiler exited 0 for '{}' but produced no executable at '{}'",
|
||||||
|
sample_name,
|
||||||
|
exe.display(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 2. Execute ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
let run_out = if stdin_bytes.is_empty() {
|
||||||
|
Command::new(&exe)
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("failed to run '{}': {e}", sample_name))?
|
||||||
|
} else {
|
||||||
|
let mut child = Command::new(&exe)
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| format!("failed to spawn '{}': {e}", sample_name))?;
|
||||||
|
|
||||||
|
child
|
||||||
|
.stdin
|
||||||
|
.take()
|
||||||
|
.expect("stdin was piped")
|
||||||
|
.write_all(&stdin_bytes)
|
||||||
|
.map_err(|e| format!("failed to write stdin for '{}': {e}", sample_name))?;
|
||||||
|
|
||||||
|
child
|
||||||
|
.wait_with_output()
|
||||||
|
.map_err(|e| format!("failed to wait on '{}': {e}", sample_name))?
|
||||||
|
};
|
||||||
|
|
||||||
|
if !run_out.status.success() {
|
||||||
|
return Err(format!(
|
||||||
|
"binary for '{}' exited non-zero ({})\nstdout:\n{}\nstderr:\n{}",
|
||||||
|
sample_name,
|
||||||
|
run_out.status,
|
||||||
|
String::from_utf8_lossy(&run_out.stdout),
|
||||||
|
String::from_utf8_lossy(&run_out.stderr),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 3. Compare against expected output ───────────────────────────────────
|
||||||
|
|
||||||
|
let actual_raw = String::from_utf8_lossy(&run_out.stdout);
|
||||||
|
let expected_raw = fs::read_to_string(&stdout_path)
|
||||||
|
.map_err(|e| format!("could not read '{}': {e}", stdout_path.display()))?;
|
||||||
|
|
||||||
|
// Trim trailing whitespace per line; ignore a lone trailing blank line so
|
||||||
|
// `.rpg.stdout` files don't need a precise final newline.
|
||||||
|
let normalise = |s: &str| -> Vec<String> {
|
||||||
|
let mut lines: Vec<String> = s.lines().map(|l| l.trim_end().to_string()).collect();
|
||||||
|
if lines.last().map(|l| l.is_empty()).unwrap_or(false) {
|
||||||
|
lines.pop();
|
||||||
|
}
|
||||||
|
lines
|
||||||
|
};
|
||||||
|
|
||||||
|
let actual_lines = normalise(&actual_raw);
|
||||||
|
let expected_lines = normalise(&expected_raw);
|
||||||
|
|
||||||
|
if actual_lines == expected_lines {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Build a readable diff-style failure message ───────────────────────────
|
||||||
|
|
||||||
|
let mut msg = format!(
|
||||||
|
"stdout mismatch for '{}'\n\n\
|
||||||
|
── expected ({} line{}) ─────────────────────────────────\n",
|
||||||
|
sample_name,
|
||||||
|
expected_lines.len(),
|
||||||
|
if expected_lines.len() == 1 { "" } else { "s" },
|
||||||
|
);
|
||||||
|
for line in &expected_lines {
|
||||||
|
msg.push_str(" ");
|
||||||
|
msg.push_str(line);
|
||||||
|
msg.push('\n');
|
||||||
|
}
|
||||||
|
msg.push_str(&format!(
|
||||||
|
"\n── actual ({} line{}) ─────────────────────────────────\n",
|
||||||
|
actual_lines.len(),
|
||||||
|
if actual_lines.len() == 1 { "" } else { "s" },
|
||||||
|
));
|
||||||
|
for line in &actual_lines {
|
||||||
|
msg.push_str(" ");
|
||||||
|
msg.push_str(line);
|
||||||
|
msg.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, (exp, act)) in expected_lines.iter().zip(actual_lines.iter()).enumerate() {
|
||||||
|
if exp != act {
|
||||||
|
msg.push_str(&format!(
|
||||||
|
"\nfirst difference at line {}:\n expected: {:?}\n actual: {:?}\n",
|
||||||
|
i + 1,
|
||||||
|
exp,
|
||||||
|
act,
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if actual_lines.len() != expected_lines.len() {
|
||||||
|
msg.push_str(&format!(
|
||||||
|
"\nline count: expected {}, got {}\n",
|
||||||
|
expected_lines.len(),
|
||||||
|
actual_lines.len(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Single test entry-point
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn samples() {
|
||||||
|
let names = discover_samples();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!names.is_empty(),
|
||||||
|
"no .rpg files found in '{}'",
|
||||||
|
SAMPLES_DIR,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut failures: Vec<String> = Vec::new();
|
||||||
|
let mut skipped: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
for name in &names {
|
||||||
|
eprint!(" sample '{}' … ", name);
|
||||||
|
match run_one(name) {
|
||||||
|
Ok(()) => eprintln!("ok"),
|
||||||
|
Err(msg) if msg.starts_with("SKIP") => {
|
||||||
|
eprintln!("skipped");
|
||||||
|
skipped.push(msg);
|
||||||
|
}
|
||||||
|
Err(msg) => {
|
||||||
|
eprintln!("FAILED");
|
||||||
|
failures.push(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !skipped.is_empty() {
|
||||||
|
eprintln!("\n{} sample(s) skipped (no .rpg.stdout file):", skipped.len());
|
||||||
|
for s in &skipped {
|
||||||
|
eprintln!(" {}", s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if failures.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut report = format!(
|
||||||
|
"\n{} of {} sample(s) failed:\n",
|
||||||
|
failures.len(),
|
||||||
|
names.len(),
|
||||||
|
);
|
||||||
|
for (i, f) in failures.iter().enumerate() {
|
||||||
|
report.push_str(&format!("\n── failure {} ──────────────────────────────────────────\n{}\n", i + 1, f));
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("{}", report);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user