diff --git a/Cargo.lock b/Cargo.lock index 16de636b2..35342d420 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "annotate-snippets" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b9d411ecbaf79885c6df4d75fff75858d5995ff25385657a28af47e82f9c36" +dependencies = [ + "unicode-width", + "yansi-term", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -106,6 +116,20 @@ dependencies = [ "toml", ] +[[package]] +name = "assert_cmd" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e" +dependencies = [ + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "atty" version = "0.2.14" @@ -226,12 +250,29 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", +] + [[package]] name = "bumpalo" version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +[[package]] +name = "by_address" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e245704f60eb4eb45810d65cf14eb54d2eb50a6f3715fe2d7cd01ee905c2944f" + [[package]] name = "byte-tools" version = "0.3.1" @@ -356,6 +397,12 @@ dependencies = [ "bitflags", ] +[[package]] +name = "codemap" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" + [[package]] name = "colored" version = "2.0.0" @@ -484,6 +531,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.8.1" @@ -528,6 +581,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "ductile" version = "0.2.0" @@ -630,6 +689,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -709,6 +777,16 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" +[[package]] +name = "goldenfile" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f46e6a4d70c06f0b9a70d36dd8eef4fdeaa1ab657e4f1eaff290f69e48145f2" +dependencies = [ + "difference", + "tempfile", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -788,6 +866,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "is_ci" version = "1.1.1" @@ -803,6 +890,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.2" @@ -1206,6 +1302,33 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "predicates" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" +dependencies = [ + "difflib", + "itertools 0.10.3", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" + +[[package]] +name = "predicates-tree" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "pretty_assertions" version = "0.6.1" @@ -1493,6 +1616,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "regex-syntax" version = "0.6.25" @@ -1900,7 +2029,7 @@ version = "0.5.5" dependencies = [ "anyhow", "bincode", - "itertools", + "itertools 0.8.2", "log", "serde", "static_assertions", @@ -1940,7 +2069,7 @@ dependencies = [ "chashmap", "ductile", "env_logger 0.6.2", - "itertools", + "itertools 0.8.2", "log", "nix 0.17.0", "pretty_assertions", @@ -1969,7 +2098,7 @@ dependencies = [ "askama_derive", "derivative", "glob", - "itertools", + "itertools 0.8.2", "lazy_static", "log", "mime_guess", @@ -1999,6 +2128,25 @@ dependencies = [ "wildmatch", ] +[[package]] +name = "task-maker-iospec" +version = "0.5.4" +dependencies = [ + "annotate-snippets", + "anyhow", + "assert_cmd", + "by_address", + "clap", + "codemap", + "goldenfile", + "num-traits", + "proc-macro2 1.0.38", + "syn 1.0.94", + "tempdir", + "tempfile", + "walkdir", +] + [[package]] name = "task-maker-lang" version = "0.5.5" @@ -2029,7 +2177,7 @@ dependencies = [ "ctrlc", "directories", "env_logger 0.9.0", - "itertools", + "itertools 0.8.2", "lazy_static", "log", "num_cpus", @@ -2044,6 +2192,7 @@ dependencies = [ "task-maker-dag", "task-maker-exec", "task-maker-format", + "task-maker-iospec", "task-maker-lang", "task-maker-store", "tempdir", @@ -2079,6 +2228,20 @@ dependencies = [ "remove_dir_all", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -2109,6 +2272,12 @@ dependencies = [ "libc", ] +[[package]] +name = "termtree" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" + [[package]] name = "textwrap" version = "0.15.0" @@ -2168,7 +2337,7 @@ dependencies = [ "bitflags", "cassowary", "either", - "itertools", + "itertools 0.8.2", "log", "termion", "unicode-segmentation", @@ -2343,6 +2512,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.3.2" @@ -2539,3 +2717,12 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] + +[[package]] +name = "yansi-term" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" +dependencies = [ + "winapi 0.3.9", +] diff --git a/Cargo.toml b/Cargo.toml index d02603aac..fcea603f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ task-maker-cache = { path = "./task-maker-cache" } task-maker-exec = { path = "./task-maker-exec" } task-maker-lang = { path = "./task-maker-lang" } # needed only by typescriptify task-maker-format = { path = "./task-maker-format" } +task-maker-iospec = { path = "./task-maker-iospec" } # Logging and setting up the global logger log = "0.4" diff --git a/src/tools/main.rs b/src/tools/main.rs index 5cf7c3432..2cc252650 100644 --- a/src/tools/main.rs +++ b/src/tools/main.rs @@ -14,6 +14,8 @@ use task_maker_rust::tools::task_info::main_task_info; use task_maker_rust::tools::typescriptify::main_typescriptify; use task_maker_rust::tools::worker::main_worker; +use task_maker_iospec::tools::*; + fn main() { let base_opt = Opt::parse(); base_opt.logger.enable_log(); @@ -30,6 +32,9 @@ fn main() { Tool::Booklet(opt) => main_booklet(opt, base_opt.logger), Tool::FuzzChecker(opt) => main_fuzz_checker(opt), Tool::AddSolutionChecks(opt) => main_add_solution_checks(opt, base_opt.logger), + Tool::IospecCheck(opt) => iospec_check::do_main(opt, &mut std::io::stderr()), + Tool::IospecGen(opt) => iospec_gen::do_main(opt, &mut std::io::stderr()), + Tool::IospecGenAll(opt) => iospec_gen_all::do_main(opt, &mut std::io::stderr()), Tool::InternalSandbox => return task_maker_rust::main_sandbox(), } .nice_unwrap() diff --git a/src/tools/opt.rs b/src/tools/opt.rs index 6f1d8d977..0ce4768ca 100644 --- a/src/tools/opt.rs +++ b/src/tools/opt.rs @@ -12,6 +12,8 @@ use crate::tools::task_info::TaskInfoOpt; use crate::tools::worker::WorkerOpt; use crate::LoggerOpt; +use task_maker_iospec::tools::*; + #[derive(Parser, Debug)] #[clap(name = "task-maker-tools")] pub struct Opt { @@ -49,6 +51,12 @@ pub enum Tool { FuzzChecker(FuzzCheckerOpt), /// Add the @check comments to the solutions. AddSolutionChecks(AddSolutionChecksOpt), + /// Check input/output files against a specification in the `iospec` language. + IospecCheck(iospec_check::Opt), + /// Generate graders or other I/O-related files given a specification in the `iospec` language. + IospecGen(iospec_gen::Opt), + /// Generate standard set of files given an I/O format specification in the `iospec` language. + IospecGenAll(iospec_gen_all::Opt), /// Run the sandbox instead of the normal task-maker. /// /// This option is left as undocumented as it's not part of the public API. diff --git a/task-maker-exec/tmbox b/task-maker-exec/tmbox new file mode 160000 index 000000000..c282fbae9 --- /dev/null +++ b/task-maker-exec/tmbox @@ -0,0 +1 @@ +Subproject commit c282fbae959d34597439f0cba5067c9728620ea9 diff --git a/task-maker-iospec/Cargo.toml b/task-maker-iospec/Cargo.toml new file mode 100644 index 000000000..ef267d8a7 --- /dev/null +++ b/task-maker-iospec/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "task-maker-iospec" +version = "0.5.4" +authors = ["Massimo Cairo "] +edition = "2021" + +[dependencies] +syn = { version = "1.0.92", features = ["extra-traits"] } +proc-macro2 = { version = "1.0.19", features = ["span-locations"] } +annotate-snippets = { version = "0.9.0", features = ["color"] } +codemap = "0.1.3" +num-traits = "0.2.12" +by_address = "1.0.4" +anyhow = "1.0.57" +clap = { version = "3.1", features = ["derive"] } + +[dev-dependencies] +assert_cmd = "2.0.4" +goldenfile = "1.1.0" +tempdir = "0.3.7" +tempfile = "3.3.0" +walkdir = "2.3.2" diff --git a/task-maker-iospec/rustfmt.toml b/task-maker-iospec/rustfmt.toml new file mode 100644 index 000000000..de424b955 --- /dev/null +++ b/task-maker-iospec/rustfmt.toml @@ -0,0 +1,2 @@ +unstable_features = true +imports_granularity = "Item" diff --git a/task-maker-iospec/src/assets/IOSPEC.sample b/task-maker-iospec/src/assets/IOSPEC.sample new file mode 100644 index 000000000..f7f8ff486 --- /dev/null +++ b/task-maker-iospec/src/assets/IOSPEC.sample @@ -0,0 +1,53 @@ +//! Sample I/O specification. +//! The input is a graph, with weights on the nodes. +//! The output is a single value, followed by a value for each node. + +inputln { + /// Number of nodes + item N: i32; + assume 2 <= N < 100_000; + + /// Number of edges + item M: i32; + assume 0 <= M < 500_000; +} + +#[cfg(subtask_name = "quadratic")] +assume N <= 1_000; + +inputln { + for u upto N { + /// Weight of `u` + item W[u]: i32; + assume 0 <= W[u] < 1_000_000_000; + } +} + +for i upto M { + inputln { + /// Tail of `i`-th edge + item A[i]: i32; + /// Head of `i`-th edge + item B[i]: i32; + + assume 0 <= A[i] < N; + assume 0 <= B[i] < N; + + /// No self-loops + assume A[i] != B[i]; + } +} + +#[cfg(grader)] +/// Compute `S` and `X[u]` for every node `u` +@call solve(N = N, M = M, W = W, A = A, B = B, X = &X) -> S; + +outputln { + item S: i32; +} + +outputln { + for u upto N { + item X[u]: i32; + } +} diff --git a/task-maker-iospec/src/assets/iolib.hpp b/task-maker-iospec/src/assets/iolib.hpp new file mode 100644 index 000000000..3cbbbb0f3 --- /dev/null +++ b/task-maker-iospec/src/assets/iolib.hpp @@ -0,0 +1,175 @@ +/// Template library to read/write I/O files. +/// Do not modify. + +#ifndef GENERATOR_HPP +#define GENERATOR_HPP + +#include +#include +#include + +const bool INPUT = 0; +const bool OUTPUT = 1; + +template +void resize_all(IoData const &data) +{ + bool needs_space = false; + process_io( + const_cast(data), + {}, + [](auto stream, auto &value) {}, + [](auto stream) {}, + [](auto stream, auto value) {}, + [](auto &ret, auto f, auto... args) {}, + [](auto f, auto... args) {}, + [](auto stream, auto &value, auto size) + { + value.resize(size); + }); +} + +template +void write_input(IoData const &data, File &file = std::cout) +{ + bool needs_space = false; + process_io( + const_cast(data), + {}, + [&](auto stream, auto &value) + { + if (stream == INPUT) + { + if (needs_space) + file << " "; + file << value; + needs_space = true; + } + }, + [&](auto stream) + { + if (stream == INPUT) + { + file << std::endl; + needs_space = false; + } + }, + [](auto stream, auto value) {}, + [](auto &ret, auto f, auto... args) {}, + [](auto f, auto... args) {}, + [](auto stream, auto &value, auto size) + { + if (stream == INPUT) + { + assert(value.size() == size); + } + value.resize(size); + }); +} + +template +IoData read_input(File &file = std::cin) +{ + IoData data; + + process_io( + data, + {}, + [&](auto stream, auto &value) + { + if (stream == INPUT) + { + file >> value; + } + }, + [](auto stream) {}, + [](auto stream, auto value) {}, + [](auto &ret, auto f, auto... args) {}, + [](auto f, auto... args) {}, + [](auto stream, auto &value, auto size) + { + value.resize(size); + }); + + return data; +} + +template +IoData run_solution(File &file = std::cin) +{ + IoData data; + + process_io( + data, + IoData::global_funs(), + [&](auto stream, auto &value) + { + if (stream == INPUT) + { + file >> value; + } + }, + [](auto stream) {}, + [](auto stream, auto value) {}, + [](auto &ret, auto f, auto... args) + { + ret = f(args...); + }, + [](auto f, auto... args) + { + f(args...); + }, + [](auto stream, auto &value, auto size) + { + value.resize(size); + }); + + return data; +} + +template +IoData read_input_output(IFile &input_file, OFile &output_file) +{ + IoData data; + + process_io( + data, + {}, + [&](auto stream, auto &value) + { + if (stream == INPUT) + { + input_file >> value; + } + if (stream == OUTPUT) + { + output_file >> value; + } + }, + [](auto stream) {}, + [](auto stream, auto value) {}, + [](auto &ret, auto f, auto... args) + { + ret = f(args...); + }, + [](auto f, auto... args) + { + f(args...); + }, + [](auto stream, auto &value, auto size) + { + value.resize(size); + }); + + return data; +} + +#define VALIDATOR_MAIN() \ + int main(int argc, char **argv) \ + { \ + std::ifstream input(argv[1]); \ + run_solution(input); \ + return 0; \ + } + +#endif diff --git a/task-maker-iospec/src/assets/sample.checker.cpp b/task-maker-iospec/src/assets/sample.checker.cpp new file mode 100644 index 000000000..b8776c489 --- /dev/null +++ b/task-maker-iospec/src/assets/sample.checker.cpp @@ -0,0 +1,21 @@ +#include "iolib.hpp" +#include "iospec.hpp" + +#include +#include + +int main(int argc, char** argv) { + std::ifstream input(argv[1]); + std::ifstream correct_output(argv[2]); + std::ifstream submission_output(argv[3]); + + IoData correct_data = read_input_output(input, correct_output); + IoData submission_data = read_input_output(input, submission_output); + + // Check `submission_data` against `correct_data`, e.g.: + // assert(submission_data.S == correct_data.S); + + // TODO: verify output format and assertions in task-maker, before calling the custom checker + + return 0; +} diff --git a/task-maker-iospec/src/assets/sample.generator.cpp b/task-maker-iospec/src/assets/sample.generator.cpp new file mode 100644 index 000000000..e2b9fdbb5 --- /dev/null +++ b/task-maker-iospec/src/assets/sample.generator.cpp @@ -0,0 +1,13 @@ +#include "iolib.hpp" +#include "iospec.hpp" + +int main(int argc, char** argv) { + IoData data; + + // Fill-in `data` with generated values, e.g.: + // data.N = atol(argv[1]); + + write_input(data); + + return 0; +} diff --git a/task-maker-iospec/src/assets/sample.validator.cpp b/task-maker-iospec/src/assets/sample.validator.cpp new file mode 100644 index 000000000..b4fbcc128 --- /dev/null +++ b/task-maker-iospec/src/assets/sample.validator.cpp @@ -0,0 +1,18 @@ +#include "iolib.hpp" +#include "iospec.hpp" + +#include +#include + +int main(int argc, char** argv) { + std::ifstream input(argv[1]); + auto data = read_input(input); + + // Check any non-trivial assumptions here, e.g.: + // assert(is_prime_number(data.N)); + // assert(is_graph_connected(data)); + + // TODO: verify input format and assumptions in task-maker, before calling the custom validator + + return 0; +} diff --git a/task-maker-iospec/src/bin/iospec-check.rs b/task-maker-iospec/src/bin/iospec-check.rs new file mode 100644 index 000000000..2bce66456 --- /dev/null +++ b/task-maker-iospec/src/bin/iospec-check.rs @@ -0,0 +1,5 @@ +use task_maker_iospec::tools::iospec_check::*; + +fn main() -> Result<(), anyhow::Error> { + return do_main(clap::Parser::parse(), &mut std::io::stderr()); +} diff --git a/task-maker-iospec/src/bin/iospec-gen.rs b/task-maker-iospec/src/bin/iospec-gen.rs new file mode 100644 index 000000000..8cb254ca5 --- /dev/null +++ b/task-maker-iospec/src/bin/iospec-gen.rs @@ -0,0 +1,5 @@ +use task_maker_iospec::tools::iospec_gen::*; + +fn main() -> Result<(), anyhow::Error> { + return do_main(clap::Parser::parse(), &mut std::io::stderr()); +} diff --git a/task-maker-iospec/src/lang/c.rs b/task-maker-iospec/src/lang/c.rs new file mode 100644 index 000000000..d5f753142 --- /dev/null +++ b/task-maker-iospec/src/lang/c.rs @@ -0,0 +1,224 @@ +use crate::gen::*; +use crate::ir::*; +use crate::sem; + +use crate::lang::clike::*; + +pub struct C; + +lang_mixin!(C, OuterBlock, CommonMixin); +lang_mixin!(C, InnerBlock, CommonMixin); +lang_mixin!(C, Stmt, CommonMixin); +lang_mixin!(C, StmtKind, CommonMixin); +lang_mixin!(C, MetaStmt, CommonMixin); +lang_mixin!(C, MetaStmtKind, CommonMixin); +lang_mixin!(C, Name, CommonMixin); +lang_mixin!(C, DataDefExpr, CommonMixin); +lang_mixin!(C, DataDefExprKind, CommonMixin); +lang_mixin!(C, Expr, CommonMixin); +lang_mixin!(C, ExprKind, CommonMixin); +lang_mixin!(C, Sign, CommonMixin); +lang_mixin!(C, SumExpr, CommonMixin); +lang_mixin!(C, MulExpr, CommonMixin); +lang_mixin!(C, SubscriptExpr, CommonMixin); +lang_mixin!(C, LitExpr, CommonMixin); +lang_mixin!(C, ParenExpr, CommonMixin); +lang_mixin!(C, RelChainExpr, CommonMixin); +lang_mixin!(C, RelExpr, CommonMixin); +lang_mixin!(C, RelOp, CommonMixin); +lang_mixin!(C, VarExpr, CommonMixin); +lang_mixin!(C, IoStmt, CommonMixin); +lang_mixin!(C, StmtAttr, CommonMixin); +lang_mixin!(C, StmtAttrKind, CommonMixin); +lang_mixin!(C, CfgAttr, CommonMixin); +lang_mixin!(C, DocAttr, CommonMixin); +lang_mixin!(C, ItemStmt, CommonMixin); +lang_mixin!(C, CallArg, CommonMixin); +lang_mixin!(C, CallArgKind, CommonMixin); +lang_mixin!(C, CallByValueArg, CommonMixin); +lang_mixin!(C, CallRet, CommonMixin); +lang_mixin!(C, CallRetExpr, CommonMixin); +lang_mixin!(C, CallRetKind, CommonMixin); +lang_mixin!(C, SingleCallRet, CommonMixin); +lang_mixin!(C, TupleCallRet, CommonMixin); +lang_mixin!(C, CallMetaStmt, CommonMixin); +lang_mixin!(C, InFunDecl<&Spec>, CommonMixin); +lang_mixin!(C, BlockStmt, CommonMixin); + +lang_mixin!(C, ForStmt, CLikeMixin); +lang_mixin!(C, IfStmt, CLikeMixin); +lang_mixin!(C, CheckStmt, CLikeMixin); +lang_mixin!(C, SetMetaStmt, CLikeMixin); +lang_mixin!(C, AtomTy, CLikeMixin); +lang_mixin!(C, InFunDecl<&CallMetaStmt>, CLikeMixin); +lang_mixin!(C, InFunDecl<&CallRet>, CLikeMixin); +lang_mixin!(C, InFunDecl<&CallRetExpr>, CLikeMixin); +lang_mixin!(C, InFunDecl<&SingleCallRet>, CLikeMixin); +lang_mixin!(C, InFunDecl<&CallArg>, CLikeMixin); +lang_mixin!(C, Template<&CallMetaStmt>, CLikeMixin); +lang_mixin!(C, InFunDecl<&Template<&Spec>>, CLikeMixin); + +impl Gen for Spec { + fn gen(&self, ctx: GenContext) -> Result { + let Spec { main, .. } = self; + + gen!(ctx, { + "#include "; + "#include "; + "#include "; + "#include "; + (&InFunDecl(self)); + (); + "int main() {"; + ({ main }); + "}"; + }) + } +} + +impl Gen for CallByReferenceArg { + fn gen(&self, ctx: GenContext) -> Result { + let Self { expr, .. } = self; + match expr.ty.as_ref() { + // Arrays are pointers already + ExprTy::Array { .. } => gen!(ctx, "{}" % expr), + _ => gen!(ctx, "&{}" % (expr)), + } + } +} + +impl Gen for DataExprAlloc { + fn gen(&self, ctx: GenContext) -> Result { + let Self { expr, info } = self; + gen!(ctx, { + "{0} = realloc({0}, {1});" % (expr, info); + }) + } +} + +impl Gen for ResizeMetaStmt { + fn gen(&self, ctx: GenContext) -> Result { + let Self { + array, + item_ty, + size, + .. + } = self; + match item_ty.as_ref() { + Some(item_ty) => gen!(ctx, { + "{0} = realloc({0}, {1});" + % ( + array, + &AllocInfo { + item_ty: item_ty.clone(), + size: size.clone(), + }, + ); + }), + None => gen!(ctx), + } + } +} + +impl Gen for AllocInfo { + fn gen(&self, ctx: GenContext) -> Result { + let Self { item_ty, size } = self; + gen!(ctx, "sizeof({}) * ({})" % (item_ty, size)) + } +} + +impl Gen for ExprTy { + fn gen(&self, ctx: GenContext) -> Result { + match self { + ExprTy::Atom { atom_ty, .. } => gen!(ctx, "{}" % atom_ty), + ExprTy::Array { item, .. } => gen!(ctx, "{}*" % item), + ExprTy::Err => gen!(ctx, "<>"), + } + } +} + +struct Format(pub T); + +impl Gen for InInput<&ItemStmt> { + fn gen(&self, ctx: GenContext) -> Result { + let ItemStmt { expr, .. } = self.0; + gen!(ctx, { + r#"assert(scanf("{}", &{}) == 1);"# % (&InInput(&Format(self.0)), expr); + }) + } +} + +impl Gen for InInput<&Format<&ItemStmt>> { + fn gen(&self, ctx: GenContext) -> Result { + match self.0 .0.ty.sem { + Some(sem::AtomTy::Bool | sem::AtomTy::I32) => gen!(ctx, "%d"), + Some(sem::AtomTy::I64) => gen!(ctx, "%lld"), + _ => gen!(ctx, "<>"), + } + } +} + +impl Gen for InOutput<&ItemStmt> { + fn gen(&self, ctx: GenContext) -> Result { + let ItemStmt { expr, .. } = self.0; + gen!(ctx, { + r#"printf("{}", {});"# % (&InOutput(&Format(self.0)), expr); + }) + } +} + +impl Gen for InOutput<&Format<&ItemStmt>> { + fn gen(&self, ctx: GenContext) -> Result { + match self.0 .0.ty.sem { + Some(sem::AtomTy::Bool | sem::AtomTy::I32) => gen!(ctx, "%d "), + Some(sem::AtomTy::I64) => gen!(ctx, "%lld "), + _ => gen!(ctx, "<>"), + } + } +} + +impl Gen for InOutput<&Endl> { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx, { + r#"printf("\n");"#; + }) + } +} + +impl Gen for InInput<&Endl> { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx) + } +} + +impl Gen for InFunDecl<&CallArgKind> { + fn gen(&self, ctx: GenContext) -> Result { + match &self.0 { + CallArgKind::Value(arg) => gen!(ctx, "{}" % (&arg.expr.ty)), + CallArgKind::Reference(arg) => match arg.expr.ty.as_ref() { + // Arrays are pointers already + ExprTy::Array { .. } => gen!(ctx, "{}" % (&arg.expr.ty)), + _ => gen!(ctx, "{}*" % (&arg.expr.ty)), + }, + } + } +} + +impl Gen for Template<&Spec> { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx, { + "#include "; + (); + (&InFunDecl(self)); + }) + } +} + +impl Gen for DataVar { + fn gen(&self, ctx: GenContext) -> Result { + let Self { name, ty, .. } = self; + gen!(ctx, { + "{} {} = 0;" % (ty, name); + }) + } +} diff --git a/task-maker-iospec/src/lang/clike.rs b/task-maker-iospec/src/lang/clike.rs new file mode 100644 index 000000000..181d8ab12 --- /dev/null +++ b/task-maker-iospec/src/lang/clike.rs @@ -0,0 +1,220 @@ +use crate::gen::*; +use crate::ir::*; +use crate::sem; + +pub struct CLikeMixin<'a, L>(pub &'a L); + +impl Gen> for ForStmt +where + AtomTy: Gen, + Name: Gen, + Expr: Gen, + OuterBlock: Gen, +{ + fn gen(&self, ctx: GenContext>) -> Result { + let ctx = &mut ctx.with_lang(ctx.lang.0); + let Self { range, body, .. } = self; + let Range { index, bound, .. } = range.as_ref(); + let RangeBound { val, ty, .. } = bound.as_ref(); + gen!(ctx, { + "for({0} {1} = 0; {1} < {2}; {1}++) {{" % (ty, index, val); + ({ body }); + "}"; + }) + } +} + +impl Gen> for IfStmt +where + Expr: Gen, + InnerBlock: Gen, +{ + fn gen(&self, ctx: GenContext>) -> Result { + let ctx = &mut ctx.with_lang(ctx.lang.0); + let Self { cond, body, .. } = self; + gen!(ctx, { + "if({}) {{" % cond; + ({ body }); + "}"; + }) + } +} + +impl Gen> for SetMetaStmt +where + Expr: Gen, +{ + fn gen(&self, ctx: GenContext>) -> Result { + let Self { lexpr, rexpr, .. } = self; + let ctx = &mut ctx.with_lang(ctx.lang.0); + gen!(ctx, { + "{} = {};" % (lexpr, rexpr); + }) + } +} + +impl Gen> for AtomTy { + fn gen(&self, ctx: GenContext>) -> Result { + match self.sem { + Some(ty) => match ty { + sem::AtomTy::Bool => gen!(ctx, "bool"), + sem::AtomTy::I32 => gen!(ctx, "int"), + sem::AtomTy::I64 => gen!(ctx, "long long"), + }, + _ => gen!(ctx, "<>"), + } + } +} + +impl Gen> for InFunDecl<&CallMetaStmt> +where + for<'a> InFunDecl<&'a CallRet>: Gen, + for<'a> InFunDecl<&'a CallArg>: Gen, + Name: Gen, +{ + fn gen(&self, ctx: GenContext>) -> Result { + let CallMetaStmt { + ret, name, args, .. + } = self.0; + let ctx = &mut ctx.with_lang(ctx.lang.0); + gen!(ctx, { + "{} {}({});" + % ( + &InFunDecl(ret), + name, + &Punctuated( + args.iter().map(|arg| InFunDecl(arg.as_ref())).collect(), + ", ", + ), + ); + }) + } +} + +impl Gen> for InFunDecl<&CallRet> +where + for<'a> InFunDecl<&'a CallRetExpr>: Gen, + Name: Gen, +{ + fn gen(&self, ctx: GenContext>) -> Result { + match self.0 .0.as_ref() { + Some(ret) => InFunDecl(ret).gen(&mut ctx.with_lang(ctx.lang.0)), + None => gen!(ctx, "void"), + } + } +} + +impl Gen> for InFunDecl<&CallRetExpr> +where + for<'a> InFunDecl<&'a SingleCallRet>: Gen, +{ + fn gen(&self, ctx: GenContext>) -> Result { + let ctx = &mut ctx.with_lang(ctx.lang.0); + match &self.0.kind { + CallRetKind::Single(ret) => InFunDecl(ret).gen(ctx), + CallRetKind::Tuple(_) => todo!(), + } + } +} + +impl Gen> for InFunDecl<&SingleCallRet> +where + ExprTy: Gen, +{ + fn gen(&self, ctx: GenContext>) -> Result { + let SingleCallRet { expr, .. } = self.0; + expr.ty.gen(&mut ctx.with_lang(ctx.lang.0)) + } +} + +impl Gen> for InFunDecl<&CallArg> +where + for<'a> InFunDecl<&'a CallArgKind>: Gen, + Name: Gen, +{ + fn gen(&self, ctx: GenContext>) -> Result { + let CallArg { name, kind, .. } = self.0; + let ctx = &mut ctx.with_lang(ctx.lang.0); + gen!(ctx, "{} {}" % (&InFunDecl(kind), name)) + } +} + +impl Gen> for CheckStmt +where + Expr: Gen, +{ + fn gen(&self, ctx: GenContext>) -> Result { + let Self { cond, .. } = self; + let ctx = &mut ctx.with_lang(ctx.lang.0); + gen!(ctx, { + "assert({});" % cond; + }) + } +} + +impl Gen> for InFunDecl<&Template<&Spec>> +where + for<'a> Template<&'a CallMetaStmt>: Gen, +{ + fn gen(&self, ctx: GenContext>) -> Result { + let Spec { main, .. } = self.0 .0; + let calls = &main.inner.calls; + let ctx = &mut ctx.with_lang(ctx.lang.0); + + let mut needs_empty_line = false; + for call in calls { + if needs_empty_line { + gen!(ctx, { + (); + })?; + } + ctx.gen(&Template(call.as_ref()))?; + needs_empty_line = true; + } + gen!(ctx) + } +} + +impl Gen> for Template<&CallMetaStmt> +where + for<'a> InFunDecl<&'a CallRet>: Gen, + for<'a> InFunDecl<&'a CallArg>: Gen, + Name: Gen, +{ + fn gen(&self, ctx: GenContext>) -> Result { + let CallMetaStmt { + ret, name, args, .. + } = self.0; + let ctx = &mut ctx.with_lang(ctx.lang.0); + + gen!(ctx, { + "{} {}({}) {{" + % ( + &InFunDecl(ret), + name, + &Punctuated( + args.iter().map(|arg| InFunDecl(arg.as_ref())).collect(), + ", ", + ), + ); + ({ + || { + match ret.0.as_ref() { + Some(ret) => match &ret.kind { + CallRetKind::Single(ret) => match ret.expr.ty.as_ref() { + ExprTy::Atom { .. } => gen!(ctx, { + "return 42;"; + })?, + _ => (), + }, + _ => (), + }, + None => (), + }; + gen!(ctx) + }; + }); + "}"; + }) + } +} diff --git a/task-maker-iospec/src/lang/cpp.rs b/task-maker-iospec/src/lang/cpp.rs new file mode 100644 index 000000000..d1e394766 --- /dev/null +++ b/task-maker-iospec/src/lang/cpp.rs @@ -0,0 +1,174 @@ +use crate::gen::*; +use crate::ir::*; + +use crate::lang::clike::*; + +pub struct Cpp; + +lang_mixin!(Cpp, OuterBlock, CommonMixin); +lang_mixin!(Cpp, InnerBlock, CommonMixin); +lang_mixin!(Cpp, Stmt, CommonMixin); +lang_mixin!(Cpp, StmtKind, CommonMixin); +lang_mixin!(Cpp, MetaStmt, CommonMixin); +lang_mixin!(Cpp, MetaStmtKind, CommonMixin); +lang_mixin!(Cpp, Name, CommonMixin); +lang_mixin!(Cpp, DataDefExpr, CommonMixin); +lang_mixin!(Cpp, DataDefExprKind, CommonMixin); +lang_mixin!(Cpp, Expr, CommonMixin); +lang_mixin!(Cpp, ExprKind, CommonMixin); +lang_mixin!(Cpp, Sign, CommonMixin); +lang_mixin!(Cpp, SumExpr, CommonMixin); +lang_mixin!(Cpp, MulExpr, CommonMixin); +lang_mixin!(Cpp, SubscriptExpr, CommonMixin); +lang_mixin!(Cpp, LitExpr, CommonMixin); +lang_mixin!(Cpp, ParenExpr, CommonMixin); +lang_mixin!(Cpp, RelChainExpr, CommonMixin); +lang_mixin!(Cpp, RelExpr, CommonMixin); +lang_mixin!(Cpp, RelOp, CommonMixin); +lang_mixin!(Cpp, VarExpr, CommonMixin); +lang_mixin!(Cpp, IoStmt, CommonMixin); +lang_mixin!(Cpp, StmtAttr, CommonMixin); +lang_mixin!(Cpp, StmtAttrKind, CommonMixin); +lang_mixin!(Cpp, DocAttr, CommonMixin); +lang_mixin!(Cpp, CfgAttr, CommonMixin); +lang_mixin!(Cpp, ItemStmt, CommonMixin); +lang_mixin!(Cpp, BlockStmt, CommonMixin); +lang_mixin!(Cpp, CallArg, CommonMixin); +lang_mixin!(Cpp, CallArgKind, CommonMixin); +lang_mixin!(Cpp, CallByValueArg, CommonMixin); +lang_mixin!(Cpp, CallRet, CommonMixin); +lang_mixin!(Cpp, CallRetExpr, CommonMixin); +lang_mixin!(Cpp, CallRetKind, CommonMixin); +lang_mixin!(Cpp, SingleCallRet, CommonMixin); +lang_mixin!(Cpp, TupleCallRet, CommonMixin); +lang_mixin!(Cpp, CallMetaStmt, CommonMixin); +lang_mixin!(Cpp, InFunDecl<&Spec>, CommonMixin); + +lang_mixin!(Cpp, ForStmt, CLikeMixin); +lang_mixin!(Cpp, IfStmt, CLikeMixin); +lang_mixin!(Cpp, CheckStmt, CLikeMixin); +lang_mixin!(Cpp, SetMetaStmt, CLikeMixin); +lang_mixin!(Cpp, AtomTy, CLikeMixin); +lang_mixin!(Cpp, InFunDecl<&CallMetaStmt>, CLikeMixin); +lang_mixin!(Cpp, InFunDecl<&CallRet>, CLikeMixin); +lang_mixin!(Cpp, InFunDecl<&CallRetExpr>, CLikeMixin); +lang_mixin!(Cpp, InFunDecl<&SingleCallRet>, CLikeMixin); +lang_mixin!(Cpp, InFunDecl<&CallArg>, CLikeMixin); +lang_mixin!(Cpp, Template<&CallMetaStmt>, CLikeMixin); +lang_mixin!(Cpp, InFunDecl<&Template<&Spec>>, CLikeMixin); + +impl Gen for Spec { + fn gen(&self, ctx: GenContext) -> Result { + let Spec { main, .. } = self; + + gen!(ctx, { + "#include "; + "#include "; + "#include "; + (); + "using namespace std;"; + (&InFunDecl(self)); + (); + "int main() {"; + ({ main }); + "}"; + }) + } +} + +impl Gen for DataVar { + fn gen(&self, ctx: GenContext) -> Result { + let Self { name, ty, .. } = self; + gen!(ctx, { + "{} {};" % (ty, name); + }) + } +} + +impl Gen for CallByReferenceArg { + fn gen(&self, ctx: GenContext) -> Result { + let Self { expr, .. } = self; + gen!(ctx, "{}" % expr) + } +} + +impl Gen for ResizeMetaStmt { + fn gen(&self, ctx: GenContext) -> Result { + let Self { array, size, .. } = self; + gen!(ctx, { + "{}.resize({});" % (array, size); + }) + } +} + +impl Gen for DataExprAlloc { + fn gen(&self, ctx: GenContext) -> Result { + let Self { expr, info } = self; + gen!(ctx, { + "{}.resize({});" % (expr, &info.size); + }) + } +} + +impl Gen for ExprTy { + fn gen(&self, ctx: GenContext) -> Result { + match self { + ExprTy::Atom { atom_ty, .. } => gen!(ctx, "{}" % atom_ty), + ExprTy::Array { item, .. } => gen!(ctx, "vector<{}>" % item), + ExprTy::Err => gen!(ctx, "<>"), + } + } +} + +impl Gen for InInput<&ItemStmt> { + fn gen(&self, ctx: GenContext) -> Result { + let ItemStmt { expr, .. } = self.0; + gen!(ctx, { + "std::cin >> {};" % expr; + }) + } +} + +impl Gen for InOutput<&ItemStmt> { + fn gen(&self, ctx: GenContext) -> Result { + let ItemStmt { expr, .. } = self.0; + gen!(ctx, { + r#"std::cout << {} << " ";"# % expr; + }) + } +} + +impl Gen for InOutput<&Endl> { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx, { + "std::cout << std::endl;"; + }) + } +} + +impl Gen for InInput<&Endl> { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx) + } +} + +impl Gen for InFunDecl<&CallArgKind> { + fn gen(&self, ctx: GenContext) -> Result { + match &self.0 { + CallArgKind::Value(arg) => gen!(ctx, "{}" % (&arg.expr.ty)), + CallArgKind::Reference(arg) => gen!(ctx, "{}&" % (&arg.expr.ty)), + } + } +} + +impl Gen for Template<&Spec> { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx, { + "#include "; + (); + "using namespace std;"; + (); + (&InFunDecl(self)); + }) + } +} diff --git a/task-maker-iospec/src/lang/cpp_lib.rs b/task-maker-iospec/src/lang/cpp_lib.rs new file mode 100644 index 000000000..b3557594d --- /dev/null +++ b/task-maker-iospec/src/lang/cpp_lib.rs @@ -0,0 +1,271 @@ +use crate::gen::*; +use crate::ir::*; +use crate::sem; + +use super::clike::CLikeMixin; +use super::cpp::Cpp; + +pub struct CppLib; + +lang_mixin!(CppLib, OuterBlock, CommonMixin); +lang_mixin!(CppLib, InnerBlock, CommonMixin); +lang_mixin!(CppLib, Stmt, CommonMixin); +lang_mixin!(CppLib, StmtKind, CommonMixin); +lang_mixin!(CppLib, IoStmt, CommonMixin); +lang_mixin!(CppLib, BlockStmt, CommonMixin); +lang_mixin!(CppLib, MetaStmt, CommonMixin); +lang_mixin!(CppLib, MetaStmtKind, CommonMixin); +lang_mixin!(CppLib, InFunDecl<&Spec>, CommonMixin); + +lang_mixin!(CppLib, ForStmt, CLikeMixin); +lang_mixin!(CppLib, IfStmt, CLikeMixin); + +lang_same_as!(CppLib, StmtAttr, Cpp); +lang_same_as!(CppLib, DataVar, Cpp); +lang_same_as!(CppLib, DataDefExpr, Cpp); +lang_same_as!(CppLib, Name, Cpp); +lang_same_as!(CppLib, Expr, Cpp); +lang_same_as!(CppLib, ExprTy, Cpp); +lang_same_as!(CppLib, AtomTy, Cpp); +lang_same_as!(CppLib, CallArg, Cpp); +lang_same_as!(CppLib, CallRet, Cpp); +lang_same_as!(CppLib, SetMetaStmt, Cpp); +lang_same_as!(CppLib, InFunDecl<&CallArg>, Cpp); +lang_same_as!(CppLib, InFunDecl<&CallRet>, Cpp); +lang_same_as!(CppLib, InFunDecl<&CallMetaStmt>, Cpp); + +impl Gen for Spec { + fn gen(&self, ctx: GenContext) -> Result { + let Spec { main, .. } = self; + + gen!(ctx, { + "#ifndef IOLIB_HPP"; + "#define IOLIB_HPP"; + (); + "#include "; + "#include "; + (); + "using std::vector;"; + (); + (&InFunDecl(self)); + "struct IoData {"; + ({ + || { + for decl in main.decls.iter() { + gen!(ctx, { + "{} {} = {{}};" % (&decl.ty, &decl.name); + })?; + } + gen!(ctx) + }; + + (); + "struct Funs {"; + ({ + || { + for call in main.inner.calls.iter() { + gen!(ctx, { + "std::function<{}({})> {} = [](auto...) {{{}}};" + % ( + &InFunDecl(&call.ret), + &Punctuated( + call.args + .iter() + .map(|arg| InFunDecl(arg.as_ref())) + .collect(), + ", ", + ), + &call.name, + &Raw(if call.ret.0.is_some() { + " return 0; " + } else { + "" + }), + ); + })?; + } + gen!(ctx) + } + }); + "};"; + (); + "static Funs global_funs() {"; + ({ + "Funs funs;"; + || { + for call in main.inner.calls.iter() { + gen!(ctx, { + "funs.{0} = {0};" % (&call.name); + })?; + } + gen!(ctx) + }; + "return funs;"; + }); + "}"; + }); + "};"; + (); + "template <"; + " typename Item,"; + " typename Endl,"; + " typename Check,"; + " typename InvokeVoid,"; + " typename Invoke,"; + " typename Resize"; + ">"; + "void process_io("; + " IoData& data,"; + " IoData::Funs funs,"; + " Item item,"; + " Endl endl,"; + " Check check,"; + " InvokeVoid invoke,"; + " Invoke invoke_void,"; + " Resize resize"; + ") {"; + ({ + || { + for decl in main.decls.iter() { + gen!(ctx, { + "auto& {0} = data.{0};" % (&decl.name); + })?; + } + for call in main.inner.calls.iter() { + gen!(ctx, { + "auto& {0} = funs.{0};" % (&call.name); + })?; + } + (); + gen!(ctx, { + "const bool INPUT = 0;"; + "const bool OUTPUT = 1;"; + (); + (&main.inner); + }) + } + }); + "}"; + (); + "#endif"; + }) + } +} + +impl Gen for CheckStmt { + fn gen(&self, ctx: GenContext) -> Result { + let Self { kw, cond, .. } = self; + let stream = &kw.to_stream(); + gen!(ctx, { + "check({}, {});" % (stream, cond); + }) + } +} + +impl Gen for ItemStmt { + fn gen(&self, ctx: GenContext) -> Result { + let ItemStmt { expr, stream, .. } = self; + if let Some(stream) = stream { + gen!(ctx, { + "item({}, {});" % (stream, expr); + }) + } else { + gen!(ctx, { + "<>;"; + }) + } + } +} + +impl Gen for CallMetaStmt { + fn gen(&self, ctx: GenContext) -> Result { + let Self { + name, args, ret, .. + } = self; + match ret.0.as_ref() { + Some(ret) => match &ret.kind { + CallRetKind::Single(ret) => gen!(ctx, { + "invoke({}, {}{}{});" + % ( + &ret.expr, + name, + if args.is_empty() { + &Raw("") + } else { + &Raw(", ") + }, + &Punctuated(args.iter().cloned().collect(), ", "), + ); + }), + CallRetKind::Tuple(_) => todo!(), + }, + None => gen!(ctx, { + "invoke_void({}{}{});" + % ( + name, + if args.is_empty() { + &Raw("") + } else { + &Raw(", ") + }, + &Punctuated(args.iter().cloned().collect(), ", "), + ); + }), + } + } +} + +impl Gen for sem::Stream { + fn gen(&self, ctx: GenContext) -> Result { + match self { + sem::Stream::Input => gen!(ctx, "INPUT"), + sem::Stream::Output => gen!(ctx, "OUTPUT"), + } + } +} + +impl Gen for InOutput<&ItemStmt> { + fn gen(&self, ctx: GenContext) -> Result { + let ItemStmt { expr, .. } = self.0; + gen!(ctx, { + "output_item({});" % expr; + }) + } +} + +impl Gen for InInput<&Endl> { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx, { + "endl({});" % (&sem::Stream::Input); + }) + } +} + +impl Gen for InOutput<&Endl> { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx, { + "endl({});" % (&sem::Stream::Output); + }) + } +} + +impl Gen for DataExprAlloc { + fn gen(&self, ctx: GenContext) -> Result { + let Self { expr, info } = self; + gen!(ctx, { + "resize({}, {}, {});" + % ( + &expr.root_var.stream.unwrap_or(sem::Stream::Input), + expr, + &info.size, + ); + }) + } +} + +impl Gen for ResizeMetaStmt { + fn gen(&self, ctx: GenContext) -> Result { + // TODO: should we emit something here? + gen!(ctx) + } +} diff --git a/task-maker-iospec/src/lang/mod.rs b/task-maker-iospec/src/lang/mod.rs new file mode 100644 index 000000000..dbfff5013 --- /dev/null +++ b/task-maker-iospec/src/lang/mod.rs @@ -0,0 +1,5 @@ +pub mod c; +pub mod clike; +pub mod cpp; +pub mod cpp_lib; +// pub mod tex; diff --git a/task-maker-iospec/src/lib.rs b/task-maker-iospec/src/lib.rs new file mode 100644 index 000000000..b08e5e523 --- /dev/null +++ b/task-maker-iospec/src/lib.rs @@ -0,0 +1,44 @@ +mod share; +mod spec; + +pub mod lang; +pub mod tools; + +pub use share::compile; +pub use share::dgns; +pub use share::run; +pub use spec::ast; +pub use spec::mem; +pub use spec::sem; + +pub use codemap; + +pub mod ir { + //! Intermediate Representation (IR) of a spec. + //! + //! The IR has a topology similar to the AST, but it also has links from each node + //! to any other nodes it refers to. + //! E.g., names are resolved by introducing a link to the node where the name is defined. + //! + //! IR nodes only link to nodes which occur *before* in a post-order traversal of the AST. + //! Hence, IR nodes result in a directed acyclic graph (DAG). + //! To represent links, nodes are wrapped in `std::rc:Rc` pointers. + //! Since there are no cycles, no `std::rc:Weak` reference is needed. + //! + //! The IR contains references to all the *tokens* in the original AST, and all the information + //! needed to reconstruct the AST tree, but does not keep any reference to the tree itself. + + use super::*; + + pub use share::ir::*; + pub use spec::ir::*; +} + +pub mod gen { + //! Code generation. + + use super::*; + + pub use share::gen::*; + pub use spec::gen::*; +} diff --git a/task-maker-iospec/src/share/compile/env.rs b/task-maker-iospec/src/share/compile/env.rs new file mode 100644 index 000000000..c1c069d53 --- /dev/null +++ b/task-maker-iospec/src/share/compile/env.rs @@ -0,0 +1,145 @@ +use crate::ir::*; +use crate::sem; + +use super::DiagnosticContext; + +#[derive(Clone, Default)] +pub struct Env { + refs: Vec>, + outer: Option>, + + pub cfg: Ir, + pub cur_io: Option>, + pub loc: Ir, +} + +impl Env { + pub fn root(cfg: sem::Cfg) -> Self { + Self { + cfg: Ir::new(cfg), + ..Default::default() + } + } + + pub fn declare(self: &mut Self, var: &Ir, dgns: &mut DiagnosticContext) { + match self.maybe_resolve(&var.name) { + None => { + self.refs.push(var.clone()); + } + Some(other_var) => dgns.error( + &format!("variable `{}` already defined", var.name.ident.to_string()), + vec![ + dgns.error_ann( + "cannot re-define a variable in scope", + var.name.ident.span(), + ), + dgns.info_ann("was defined here", other_var.name.ident.span()), + ], + vec![], + ), + } + } + + pub fn declare_expr(self: &mut Self, expr: &Ir, dgns: &mut DiagnosticContext) { + let var = &expr.root_var; + self.declare( + &Ir::new(Var { + kind: VarKind::Data { def: var.clone() }, + ty: var.ty.clone(), + name: var.name.clone(), + }), + dgns, + ) + } + + pub fn resolve(self: &Self, name: &Ir, dgns: &mut DiagnosticContext) -> Ir { + match self.maybe_resolve(name) { + Some(var) => var, + None => { + dgns.error( + &format!( + "no variable named `{}` found in the current scope", + name.ident.to_string() + ), + vec![dgns.error_ann("not found in this scope", name.ident.span())], + vec![], + ); + Ir::new(Var { + name: name.clone(), + ty: Ir::new(ExprTy::Err), + kind: VarKind::Err, + }) + } + } + } + + fn maybe_resolve(self: &Self, name: &Ir) -> Option> { + self.refs + .iter() + .find(|r| r.name.ident == name.ident) + .map(|r| r.clone()) + .or(self.outer.as_ref().and_then(|s| s.maybe_resolve(name))) + } + + pub fn for_body(self: &Self, range: Ir) -> Self { + Self { + refs: vec![Ir::new(Var { + name: range.index.clone(), + ty: range.bound.val.ty.clone(), + kind: VarKind::Index { + range: range.clone(), + }, + })], + outer: Some(Box::new(self.clone())), + loc: Ir::new(Loc::For { + range: range.clone(), + parent: self.loc.clone(), + }), + cur_io: self.cur_io.clone(), + cfg: self.cfg.clone(), + } + } + + pub fn io(self: &Self, step: &Ir) -> Self { + Self { + outer: Some(Box::new(self.clone())), + loc: self.loc.clone(), + cur_io: Some(step.clone()), + cfg: self.cfg.clone(), + ..Default::default() + } + } + + pub fn data_env(self: &Self, ty: &Ir) -> DataDefEnv { + DataDefEnv { + outer: Box::new(self.clone()), + ty: Ir::new(ExprTy::Atom { + atom_ty: ty.clone(), + }), + loc: self.loc.clone(), + } + } +} + +#[derive(Clone)] +pub enum Loc { + Main, + For { range: Ir, parent: Ir }, +} + +impl Default for Loc { + fn default() -> Self { + Self::Main + } +} + +/// Special environment used when defining new variables. +/// +/// E.g., the environment in which `A[i]` is compiled, +/// in the statement `item A[i][j]: i32;`. +#[derive(Clone)] +pub struct DataDefEnv { + pub outer: Box, + pub ty: Ir, + pub loc: Ir, +} diff --git a/task-maker-iospec/src/share/compile/mod.rs b/task-maker-iospec/src/share/compile/mod.rs new file mode 100644 index 000000000..b37fc90c5 --- /dev/null +++ b/task-maker-iospec/src/share/compile/mod.rs @@ -0,0 +1,20 @@ +//! Implements compilation from AST to IR. + +mod env; +mod traits; +mod util; + +pub use crate::dgns::*; +use crate::sem; + +pub use env::*; +pub use traits::*; +pub use util::*; + +pub fn compile( + ast: &crate::ast::Spec, + dgns: &mut DiagnosticContext, + cfg: sem::Cfg, +) -> Result { + ast.compile(&env::Env::root(cfg), dgns) +} diff --git a/task-maker-iospec/src/share/compile/traits.rs b/task-maker-iospec/src/share/compile/traits.rs new file mode 100644 index 000000000..af3518103 --- /dev/null +++ b/task-maker-iospec/src/share/compile/traits.rs @@ -0,0 +1,64 @@ +use crate::dgns::DiagnosticContext; +use crate::ir::*; + +use super::*; + +#[derive(Clone, Copy)] +pub struct CompileStop; + +pub type Result = std::result::Result; + +pub trait CompileFrom +where + Self: Sized, +{ + fn compile(ast: &T, env: &E, dgns: &mut DiagnosticContext) -> Result; +} + +impl CompileFrom for Ir +where + T: CompileInto, +{ + fn compile(ast: &T, env: &E, dgns: &mut DiagnosticContext) -> Result { + Ok(Ir::new(ast.compile(env, dgns)?)) + } +} + +pub trait CompileInto { + fn compile(&self, env: &E, dgns: &mut DiagnosticContext) -> Result; +} + +impl CompileInto for T +where + U: CompileFrom, +{ + fn compile(&self, env: &E, dgns: &mut DiagnosticContext) -> Result { + U::compile(self, env, dgns) + } +} + +pub trait AnalyzeFrom { + fn analyze(ir: &T, dgns: &mut DiagnosticContext) -> Self; +} + +impl AnalyzeFrom> for U +where + T: AnalyzeInto, +{ + fn analyze(ir: &Ir, dgns: &mut DiagnosticContext) -> Self { + ir.as_ref().analyze(dgns) + } +} + +pub trait AnalyzeInto { + fn analyze(&self, dgns: &mut DiagnosticContext) -> T; +} + +impl AnalyzeInto for T +where + U: AnalyzeFrom, +{ + fn analyze(&self, dgns: &mut DiagnosticContext) -> U { + U::analyze(self, dgns) + } +} diff --git a/task-maker-iospec/src/share/compile/util.rs b/task-maker-iospec/src/share/compile/util.rs new file mode 100644 index 000000000..f526ab5b8 --- /dev/null +++ b/task-maker-iospec/src/share/compile/util.rs @@ -0,0 +1,25 @@ +use crate::gen::Gen; +use crate::gen::Inspect; +use crate::share::gen::gen_string; + +pub fn unzip_punctuated(p: syn::punctuated::Punctuated) -> (Vec, Vec) { + let mut args = Vec::new(); + let mut puncts = Vec::new(); + for p in p.into_pairs() { + let (a, p) = p.into_tuple(); + args.push(a); + if let Some(p) = p { + puncts.push(p) + } + } + (args, puncts) +} + +// pub fn quote_hir>(ir: T) -> String { +// let tokens = quote!(#ir); +// tokens.to_string().unwrap() +// } + +pub fn quote_hir>(ir: &T) -> String { + gen_string(ir, &Inspect) +} diff --git a/task-maker-iospec/src/share/dgns.rs b/task-maker-iospec/src/share/dgns.rs new file mode 100644 index 000000000..61aac0e79 --- /dev/null +++ b/task-maker-iospec/src/share/dgns.rs @@ -0,0 +1,102 @@ +//! Utilities to generate diagnostic messages. + +use std::io::Write; +use std::sync::Arc; + +use annotate_snippets::display_list::DisplayList; +use annotate_snippets::display_list::FormatOptions; +use annotate_snippets::snippet::*; +use anyhow::Context; +use codemap::File; +use proc_macro2::LineColumn; + +pub use proc_macro2::Span; + +pub trait HasSpan { + fn span(self: &Self) -> Span; +} +pub trait TryHasSpan { + fn try_span(self: &Self) -> Option; +} + +pub struct DiagnosticContext<'a> { + pub spec_file: Arc, + pub stderr: &'a mut dyn Write, + pub color: bool, +} + +impl DiagnosticContext<'_> { + pub fn error( + self: &mut Self, + message: &str, + annotations: Vec, + footer: Vec, + ) { + self.stderr + .write_fmt(format_args!( + "{}\n", + DisplayList::from(Snippet { + title: Some(Annotation { + id: None, + label: Some(message), + annotation_type: AnnotationType::Error, + }), + footer, + slices: vec![Slice { + source: self.spec_file.source(), + line_start: 1, + origin: Some(self.spec_file.name()), + fold: true, + annotations, + }], + opt: FormatOptions { + color: self.color, + ..Default::default() + }, + }), + )) + .context("while writing a diagnostic message") + .unwrap(); + } + + pub fn footer<'a>( + self: &Self, + annotation_type: AnnotationType, + message: &'a str, + ) -> Annotation<'a> { + Annotation { + annotation_type, + label: Some(message), + id: None, + } + } + + pub fn note_footer<'a>(self: &Self, message: &'a str) -> Annotation<'a> { + self.footer(AnnotationType::Note, message) + } + + pub fn help_footer<'a>(self: &Self, message: &'a str) -> Annotation<'a> { + self.footer(AnnotationType::Help, message) + } + + pub fn error_ann<'a>(self: &Self, label: &'a str, span: Span) -> SourceAnnotation<'a> { + SourceAnnotation { + annotation_type: AnnotationType::Error, + label, + range: (self.pos(span.start()), self.pos(span.end())), + } + } + + pub fn info_ann<'a>(self: &Self, label: &'a str, span: Span) -> SourceAnnotation<'a> { + SourceAnnotation { + annotation_type: AnnotationType::Info, + label, + range: (self.pos(span.start()), self.pos(span.end())), + } + } + + fn pos(self: &Self, lc: LineColumn) -> usize { + let line_start = self.spec_file.line_span(lc.line - 1).low() - self.spec_file.span.low(); + line_start as usize + lc.column + } +} diff --git a/task-maker-iospec/src/share/gen/gen_macro.rs b/task-maker-iospec/src/share/gen/gen_macro.rs new file mode 100644 index 000000000..2ea0efec6 --- /dev/null +++ b/task-maker-iospec/src/share/gen/gen_macro.rs @@ -0,0 +1,83 @@ +macro_rules! gen { + ($ctx:expr) => { + { + { + // Pretend to use the arg + let _ctx = &$ctx; + } + Ok(()) + } + }; + ($ctx:expr, ) => { + { + gen!($ctx) + } + }; + ($ctx:expr, ; $($rest:tt)* ) => { + { + $ctx.endl()?; + gen!($ctx, $($rest)*) + } + }; + ($ctx:expr, $lit:literal % ( $($expr:expr),* $(,)? ) $( ; $($rest:tt)* )?) => { + { + let GenBuffer { lang, indent, .. } = *$ctx; + $ctx.append(format_args!($lit, $( GenToken { lang, indent, inner: $expr } ),*))?; + gen!($ctx, $( ; $($rest)* )?) + } + }; + ($ctx:expr, $lit:literal % $expr:expr $( ; $($rest:tt)* )?) => { + { + gen!($ctx, $lit % ($expr))?; + gen!($ctx, $( ; $($rest)* )?) + } + }; + ($ctx:expr, $lit:literal $( ; $($rest:tt)* )?) => { + { + $ctx.append($lit)?; + gen!($ctx, $( ; $($rest)* )?) + } + }; + ($ctx:expr, $ident:ident $( ; $($rest:tt)* )?) => { + { + gen!($ctx, ( $ident ) $( ; $($rest)* )?) + } + }; + ($ctx:expr, || $body:expr $( ; $($rest:tt)* )?) => { + { + { + let _: Result = $body; + } + gen!($ctx, $( $($rest)* )?) + } + }; + ($ctx:expr, ({ $($body:tt)* }) ; $($rest:tt)*) => { + { + $ctx.block_begin()?; + gen!($ctx, $($body)*)?; + $ctx.block_end()?; + gen!($ctx, $($rest)*) + } + }; + ($ctx:expr, () $( ; $($rest:tt)* )?) => { + { + gen!($ctx, $( ; $($rest)* )?) + } + }; + ($ctx:expr, ( $( $expr:expr ),* ) $( ; $($rest:tt)* )?) => { + { + $( + $ctx.gen($expr)?; + )* + gen!($ctx, $( $($rest)* )?) + } + }; + ($ctx:expr, { $($body:tt)* } $($rest:tt)*) => { + { + gen!($ctx, $($body)*)?; + gen!($ctx, $($rest)*) + } + }; +} + +pub(crate) use gen; diff --git a/task-maker-iospec/src/share/gen/mod.rs b/task-maker-iospec/src/share/gen/mod.rs new file mode 100644 index 000000000..33df95970 --- /dev/null +++ b/task-maker-iospec/src/share/gen/mod.rs @@ -0,0 +1,217 @@ +use std::fmt::Display; +use std::fmt::Write; + +mod gen_macro; +pub(crate) use gen_macro::*; + +pub struct Inspect; + +pub struct CommonMixin<'a, L>(pub &'a L); + +macro_rules! lang_mixin { + ($lang_ty:ty, $target:ty, $mixin_ty:expr) => { + impl Gen<$lang_ty> for $target { + fn gen(&self, ctx: GenContext<$lang_ty>) -> Result { + let mixin = $mixin_ty(ctx.lang); + let ctx = &mut ctx.with_lang(&mixin); + self.gen(ctx) + } + } + }; +} + +pub(crate) use lang_mixin; + +macro_rules! lang_same_as { + ($lang_ty:ty, $target:ty, $other_lang:expr) => { + impl Gen<$lang_ty> for $target { + fn gen(&self, ctx: GenContext<$lang_ty>) -> Result { + let mut ctx2 = ctx.with_lang(&$other_lang); + self.gen(&mut ctx2) + } + } + }; +} + +pub(crate) use lang_same_as; + +pub use std::fmt::Result; + +pub type GenContext<'a, 'b, L> = &'a mut GenBuffer<'b, L>; + +pub struct GenBuffer<'a, L> { + pub lang: &'a L, + pub fmt: &'a mut dyn Write, + pub needs_indent: &'a mut bool, + pub indent: u8, +} + +pub struct GenOutput(W); + +impl Write for GenOutput { + fn write_str(&mut self, s: &str) -> Result { + self.0.write_all(s.as_bytes()).map_err(|_| std::fmt::Error) + } +} + +pub trait Gen { + fn gen(&self, ctx: GenContext) -> Result; +} + +pub struct GenToken<'a, L, T> { + pub lang: &'a L, + pub inner: &'a T, + pub indent: u8, +} + +impl> Display for GenToken<'_, L, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result { + let Self { + indent, + lang, + inner, + } = self; + inner.gen(&mut GenBuffer { + lang, + fmt: f, + needs_indent: &mut false, + indent: *indent, + }) + } +} + +const INDENT_ONE: &'static str = " "; + +impl GenBuffer<'_, L> { + pub fn append(&mut self, token: T) -> Result { + if *self.needs_indent { + for _ in 0..self.indent { + write!(self.fmt, "{}", INDENT_ONE)?; + } + *self.needs_indent = false; + } + write!(self.fmt, "{}", token) + } + + pub fn gen>(&mut self, token: &T) -> Result { + token.gen(self) + } + + pub fn endl(&mut self) -> Result { + writeln!(self.fmt, "")?; + *self.needs_indent = true; + Ok(()) + } + + pub fn indent(&mut self) { + self.indent += 1; + } + + pub fn dedent(&mut self) { + self.indent -= 1; + } + + pub fn block_begin(&mut self) -> Result { + self.indent(); + Ok(()) + } + + pub fn block_end(&mut self) -> Result { + self.dedent(); + Ok(()) + } + + pub fn with_lang<'b, M>(&'b mut self, lang: &'b M) -> GenBuffer<'b, M> { + return GenBuffer { + lang, + fmt: self.fmt, + needs_indent: self.needs_indent, + indent: self.indent, + }; + } +} + +pub fn gen_string>(item: &T, lang: &L) -> String { + let mut str = String::new(); + + let mut ctx = GenBuffer { + lang, + fmt: &mut str, + indent: 0, + needs_indent: &mut true, + }; + + item.gen(&mut ctx).unwrap(); + + str +} + +#[cfg(test)] +mod test { + use super::*; + + pub struct MyLang; + + impl Gen for i32 { + fn gen(&self, ctx: &mut GenBuffer) -> Result { + ctx.append(self) + } + } + + #[test] + fn a() { + assert_eq!("1", gen_string(&1, &MyLang)) + } +} + +impl Gen for crate::ir::Ir +where + T: Gen, +{ + fn gen(&self, ctx: GenContext) -> Result { + self.as_ref().gen(ctx) + } +} + +pub struct Punctuated(pub Vec, pub P); + +impl Gen for Punctuated +where + T: Gen, + P: Display, +{ + fn gen(&self, ctx: GenContext) -> Result { + let mut first = true; + let Self(items, punct) = self; + + for item in items { + if !first { + { + ctx.append(&punct)?; + } + } + first = false; + gen!(ctx, item)?; + } + + gen!(ctx) + } +} + +/// Newtype for items that are generated verbatim +pub struct Raw(pub T); + +impl Gen for Raw +where + T: Display, +{ + fn gen(&self, ctx: GenContext) -> Result { + ctx.append(&self.0) + } +} + +impl Gen for () { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx) + } +} diff --git a/task-maker-iospec/src/share/ir.rs b/task-maker-iospec/src/share/ir.rs new file mode 100644 index 000000000..5aac41438 --- /dev/null +++ b/task-maker-iospec/src/share/ir.rs @@ -0,0 +1,38 @@ +use std::ops::Deref; +use std::rc::Rc; + +#[derive(Debug, Default)] +pub struct Ir(Rc); + +impl AsRef for Ir { + fn as_ref(&self) -> &T { + self.0.as_ref() + } +} + +impl Deref for Ir { + type Target = as Deref>::Target; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +// TODO: can this be a derive? +impl Clone for Ir { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Ir { + pub fn new(inner: T) -> Self { + Self(Rc::new(inner)) + } +} + +impl Ir { + pub fn same(this: &Self, other: &Self) -> bool { + return Rc::ptr_eq(&this.0, &other.0); + } +} diff --git a/task-maker-iospec/src/share/mod.rs b/task-maker-iospec/src/share/mod.rs new file mode 100644 index 000000000..d2015aec3 --- /dev/null +++ b/task-maker-iospec/src/share/mod.rs @@ -0,0 +1,7 @@ +//! Types not specific to a language construct. + +pub mod compile; +pub mod dgns; +pub mod gen; +pub mod ir; +pub mod run; diff --git a/task-maker-iospec/src/share/run/alloc.rs b/task-maker-iospec/src/share/run/alloc.rs new file mode 100644 index 000000000..e13e3f002 --- /dev/null +++ b/task-maker-iospec/src/share/run/alloc.rs @@ -0,0 +1,29 @@ +use crate::ir::*; +use crate::mem::*; + +impl<'a> ExprValMut<'a> { + pub fn alloc(self: &mut Self, expr: &Ir, len: usize) { + match (self, expr.ty.as_ref()) { + (ExprValMut::Aggr(aggr), ExprTy::Array { item, .. }) => { + debug_assert!(matches!(aggr, ArrayVal::Empty)); + + match item.as_ref() { + ExprTy::Atom { atom_ty } => { + **aggr = ArrayVal::AtomArray(atom_ty.sem.unwrap().array(len)) + } + ExprTy::Array { .. } => { + **aggr = ArrayVal::AggrArray({ + let mut vec = Vec::with_capacity(len); + for _ in 0..len { + vec.push(ArrayVal::Empty) + } + vec + }) + } + ExprTy::Err => unreachable!(), + } + } + _ => unreachable!(), + } + } +} diff --git a/task-maker-iospec/src/share/run/ctx.rs b/task-maker-iospec/src/share/run/ctx.rs new file mode 100644 index 000000000..4169fa84a --- /dev/null +++ b/task-maker-iospec/src/share/run/ctx.rs @@ -0,0 +1,9 @@ +use crate::compile::DiagnosticContext; + +use super::io::*; + +pub struct Context<'a> { + pub input_source: IoSource, + pub output_source: Option, + pub dgns: DiagnosticContext<'a>, +} diff --git a/task-maker-iospec/src/share/run/io.rs b/task-maker-iospec/src/share/run/io.rs new file mode 100644 index 000000000..8254a478b --- /dev/null +++ b/task-maker-iospec/src/share/run/io.rs @@ -0,0 +1,42 @@ +use std::error::Error; +use std::io::BufRead; +use std::io::Read; +use std::str::FromStr; + +use crate::sem::*; + +pub struct IoSource(pub Box); + +impl IoSource { + pub fn next_atom(self: &mut Self, _ty: &AtomTy) -> Result, Box> { + let val = self.next_token()?; + + Ok(if val.is_empty() { + None + } else { + Some(i64::from_str(&String::from_utf8(val)?)?) + }) + } + + pub fn next_token(self: &mut Self) -> Result, Box> { + let val = self + .0 + .by_ref() + .bytes() + .skip_while(|b| match b { + Ok(b) => b.is_ascii_whitespace(), + _ => false, + }) + .take_while(|b| match b { + Ok(b) => !b.is_ascii_whitespace(), + _ => false, + }) + .collect::, _>>()?; + + Ok(val) + } + + pub fn check_eof(self: &mut Self) -> bool { + self.next_token().unwrap().is_empty() + } +} diff --git a/task-maker-iospec/src/share/run/mod.rs b/task-maker-iospec/src/share/run/mod.rs new file mode 100644 index 000000000..f5edb118c --- /dev/null +++ b/task-maker-iospec/src/share/run/mod.rs @@ -0,0 +1,33 @@ +mod alloc; +mod ctx; +mod io; +mod state; +mod traits; + +pub use alloc::*; +pub use ctx::*; +pub use io::*; +pub use state::*; +pub use traits::*; + +use anyhow::Error; + +pub enum Stop { + Done, + Error(Error), +} + +impl> From for Stop { + fn from(error: T) -> Self { + Stop::Error(error.into()) + } +} + +impl Stop { + pub fn as_result(self) -> Result<(), Error> { + match self { + Stop::Done => Ok(()), + Stop::Error(error) => Err(error), + } + } +} diff --git a/task-maker-iospec/src/share/run/state.rs b/task-maker-iospec/src/share/run/state.rs new file mode 100644 index 000000000..d7e7a9133 --- /dev/null +++ b/task-maker-iospec/src/share/run/state.rs @@ -0,0 +1,25 @@ +use std::collections::HashMap; + +use by_address::ByAddress; + +use crate::ir::*; +use crate::mem::*; + +#[derive(Default, Debug)] +pub struct State { + pub env: HashMap>, NodeVal>, + pub indexes: HashMap>, usize>, +} + +impl State { + pub fn decl(self: &mut Self, var: &Ir) { + debug_assert!(!self.env.contains_key(&var.clone().into())); + self.env.insert( + var.clone().into(), + match var.ty.as_ref() { + ExprTy::Atom { atom_ty } => NodeVal::Atom(atom_ty.sem.unwrap().cell()), + _ => NodeVal::Array(ArrayVal::Empty), + }, + ); + } +} diff --git a/task-maker-iospec/src/share/run/traits.rs b/task-maker-iospec/src/share/run/traits.rs new file mode 100644 index 000000000..889d2c5b1 --- /dev/null +++ b/task-maker-iospec/src/share/run/traits.rs @@ -0,0 +1,18 @@ +use crate::mem::*; +use crate::run::*; + +pub trait Run { + fn run(self: &Self, state: &mut State, ctx: &mut Context) -> Result<(), Stop>; +} + +pub trait Eval { + fn eval<'a>(self: &Self, state: &'a State, ctx: &mut Context) -> Result, Stop>; +} + +pub trait EvalMut { + fn eval_mut<'a>( + self: &Self, + state: &'a mut State, + ctx: &mut Context, + ) -> Result, Stop>; +} diff --git a/task-maker-iospec/src/spec/atom_ty.rs b/task-maker-iospec/src/spec/atom_ty.rs new file mode 100644 index 000000000..6dfa9dae2 --- /dev/null +++ b/task-maker-iospec/src/spec/atom_ty.rs @@ -0,0 +1,303 @@ +pub mod ir { + use super::sem; + use crate::ir::*; + + /// IR of the type of an atomic value + #[derive(Default, Debug)] + pub struct AtomTy { + pub kind: AtomTyKind, + pub sem: Option, + } + + #[derive(Debug)] + pub enum AtomTyKind { + Name { + name: Ir, + }, + Lit { + token: syn::LitInt, + }, + /// Result of a comparison + Rel { + rels: Vec, + }, + Err, + } + + impl Default for AtomTyKind { + fn default() -> Self { + Self::Err + } + } + + pub struct ExprList<'a>(pub &'a Vec>); +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + use crate::sem; + + impl Gen> for AtomTy { + fn gen(&self, ctx: GenContext>) -> Result { + match &self.sem { + Some(ty) => match ty { + sem::AtomTy::Bool => gen!(ctx, "bool"), + sem::AtomTy::I32 => gen!(ctx, "i32"), + sem::AtomTy::I64 => gen!(ctx, "i64"), + }, + _ => gen!(ctx, "<>"), + } + } + } + + lang_mixin!(Inspect, AtomTy, CommonMixin); +} + +pub mod sem { + use std::fmt; + use std::str::FromStr; + + /// Semantics of the type of an atomic value + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum AtomTy { + Bool, + I32, + I64, + } + + use AtomTy::*; + + impl AtomTy { + pub fn all() -> Vec { + vec![Bool, I32, I64] + } + + pub fn name(self: Self) -> String { + match self { + Bool => "bool".into(), + I32 => "i32".into(), + I64 => "i64".into(), + } + } + + pub fn value_range(self: Self) -> (i64, i64) { + match self { + Bool => (0, 1), + I32 => (i32::min_value() as i64 + 1, i32::max_value() as i64), + I64 => (i64::min_value() as i64 + 1, i64::max_value() as i64), + } + } + } + + impl FromStr for AtomTy { + type Err = (); + + fn from_str(s: &str) -> Result { + Self::all().into_iter().find(|k| &k.name() == s).ok_or(()) + } + } + + impl fmt::Display for AtomTy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.name()) + } + } +} + +pub mod mem { + use num_traits::Bounded; + use num_traits::Num; + use num_traits::NumCast; + use std::fmt::Debug; + + use crate::sem; + + pub type AtomVal = sem::AtomVal; + + pub trait Empty { + fn empty() -> Self; + } + + impl Empty for T { + fn empty() -> Self { + Self::min_value() + } + } + + /// Compact representation of an atom, to use in array cells + trait AtomMem: Clone + Copy + Debug + Num + Empty + NumCast {} + + impl AtomMem for u8 {} + impl AtomMem for i32 {} + impl AtomMem for i64 {} + + pub trait AtomCell: Debug { + fn get(self: &Self, ty: sem::AtomTy) -> Option; + fn set(self: &mut Self, value: AtomVal); + } + + impl AtomCell for T { + fn get(self: &Self, ty: sem::AtomTy) -> Option { + if *self == Self::empty() { + None + } else { + Some(AtomVal::new(ty, (*self).to_i64().unwrap())) + } + } + + fn set(self: &mut Self, value: AtomVal) { + *self = ::from(value.value_i64()).unwrap() + } + } + + pub trait AtomArray: Debug { + fn at(self: &Self, index: usize) -> &dyn AtomCell; + fn at_mut(self: &mut Self, index: usize) -> &mut dyn AtomCell; + } + + impl AtomArray for Vec { + fn at(self: &Self, index: usize) -> &dyn AtomCell { + &self[index] + } + + fn at_mut(self: &mut Self, index: usize) -> &mut dyn AtomCell { + &mut self[index] + } + } + + impl sem::AtomTy { + pub fn cell(self: &Self) -> Box { + match self { + sem::AtomTy::Bool => Box::new(u8::empty()), + sem::AtomTy::I32 => Box::new(i32::empty()), + sem::AtomTy::I64 => Box::new(i64::empty()), + } + } + + pub fn array(self: &Self, len: usize) -> Box { + match self { + sem::AtomTy::Bool => Box::new(vec![u8::empty(); len]), + sem::AtomTy::I32 => Box::new(vec![i32::empty(); len]), + sem::AtomTy::I64 => Box::new(vec![i64::empty(); len]), + } + } + } +} + +mod compile { + use std::str::FromStr; + + use crate::ast; + use crate::compile::*; + use crate::ir::*; + use crate::sem; + + impl CompileFrom for AtomTy { + fn compile(ast: &ast::Name, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let name: Ir = ast.compile(env, dgns)?; + let sem = sem::AtomTy::from_str(&name.ident.to_string()); + + if sem.is_err() { + dgns.error( + &format!("invalid scalar type `{}`", name.ident.to_string()), + vec![dgns.error_ann("invalid type", name.span())], + vec![dgns.help_footer(&format!( + "supported types are {}", + sem::AtomTy::all() + .iter() + .map(|ty| ty.to_string()) + .collect::>() + .join(", ") + ))], + ); + } + + Ok(Self { + sem: sem.ok(), + kind: AtomTyKind::Name { name }, + }) + } + } + + impl AnalyzeFrom for Option> { + fn analyze(ir: &Expr, dgns: &mut DiagnosticContext) -> Self { + match ir.ty.as_ref() { + ExprTy::Atom { atom_ty } => Some(atom_ty.clone()), + _ => { + dgns.error( + &format!("expected a scalar type, got `{}`", quote_hir(&ir.ty),), + vec![dgns.error_ann("not a scalar", ir.span())], + vec![], + ); + Default::default() + } + } + } + } + + impl AnalyzeFrom> for Option> { + fn analyze(ir: &ExprList, dgns: &mut DiagnosticContext) -> Self { + let scalars: Vec<_> = + ir.0.iter() + .flat_map(|factor| { + let ty: Option> = factor.analyze(dgns); + ty.and_then(|ty| ty.sem.map(|ty_sem| (factor, ty, ty_sem))) + }) + .collect(); + + let (first, ty, ty_sem) = match scalars.first() { + Some(x) => x, + _ => { + return Default::default(); + } + }; + + let mismatched_type = scalars.iter().find(|(_, _, ty_sem2)| ty_sem2 != ty_sem); + + match mismatched_type { + Some((expr, actual_ty, _)) => { + dgns.error( + &format!( + "expected type `{}`, got `{}`", + quote_hir(ty), + quote_hir(actual_ty) + ), + vec![ + dgns.error_ann(&format!("expected `{}`", quote_hir(ty)), expr.span()), + dgns.info_ann(&format!("this is a `{}`", quote_hir(ty)), first.span()), + dgns.info_ann("actual type here", actual_ty.span()), + dgns.info_ann("expected type here", ty.span()), + ], + vec![], + ); + Default::default() + } + None => Some(ty.clone()), + } + } + } +} + +mod dgns { + use super::ir::*; + use crate::dgns::*; + + impl HasSpan for AtomTy { + fn span(self: &Self) -> Span { + match &self.kind { + AtomTyKind::Name { name } => name.span(), + AtomTyKind::Lit { token } => token.span(), + // FIXME: duplicate code + AtomTyKind::Rel { rels } => rels + .first() + .unwrap() + .0 + .span() + .join(rels.last().unwrap().2.span()) + .unwrap(), + AtomTyKind::Err => panic!(), + } + } + } +} diff --git a/task-maker-iospec/src/spec/attr.rs b/task-maker-iospec/src/spec/attr.rs new file mode 100644 index 000000000..3008fe32f --- /dev/null +++ b/task-maker-iospec/src/spec/attr.rs @@ -0,0 +1,166 @@ +pub mod ast { + use syn::token::Bracket; + use syn::Token; + + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct StmtAttr { + pub pound: Token![#], + pub bracket: Bracket, + pub kind: StmtAttrKind, + } + + #[derive(Debug, Clone)] + pub enum StmtAttrKind { + Doc(DocAttr), + Cfg(CfgAttr), + Unknown, + } + + #[derive(Debug, Clone)] + pub struct SpecAttr { + pub pound: Token![#], + pub bang: Token![!], + pub bracket: Bracket, + pub kind: SpecAttrKind, + } + + #[derive(Debug, Clone)] + pub enum SpecAttrKind { + Doc(DocAttr), + Unknown, + } +} + +mod parse { + use syn::bracketed; + use syn::parse::*; + + use crate::ast::*; + + impl Parse for StmtAttr { + fn parse(input: ParseStream) -> Result { + let bracket_input; + Ok(Self { + pound: input.parse()?, + bracket: bracketed!(bracket_input in input), + kind: bracket_input.parse()?, + }) + } + } + + impl Parse for StmtAttrKind { + fn parse(input: ParseStream) -> Result { + let la = input.lookahead1(); + Ok(if la.peek(kw::doc) { + Self::Doc(input.parse()?) + } else if la.peek(kw::cfg) { + Self::Cfg(input.parse()?) + } else { + Err(la.error())? + }) + } + } + + impl Parse for SpecAttr { + fn parse(input: ParseStream) -> Result { + let bracket_input; + Ok(Self { + pound: input.parse()?, + bang: input.parse()?, + bracket: bracketed!(bracket_input in input), + kind: bracket_input.parse()?, + }) + } + } + + impl Parse for SpecAttrKind { + fn parse(input: ParseStream) -> Result { + let la = input.lookahead1(); + Ok(if la.peek(kw::doc) { + Self::Doc(input.parse()?) + } else { + Err(la.error())? + }) + } + } +} + +pub mod ir { + use crate::ast; + + pub type StmtAttr = ast::StmtAttr; + pub type StmtAttrKind = ast::StmtAttrKind; + + pub type SpecAttr = ast::SpecAttr; + pub type SpecAttrKind = ast::SpecAttrKind; +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for StmtAttr + where + StmtAttrKind: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + self.kind.gen(&mut ctx.with_lang(ctx.lang.0)) + } + } + + impl Gen for StmtAttr { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx, { + "#[ <> ]"; + }) + } + } + + impl Gen> for StmtAttrKind + where + CfgAttr: Gen, + DocAttr: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + match self { + StmtAttrKind::Cfg(attr) => attr.gen(&mut ctx.with_lang(ctx.lang.0)), + StmtAttrKind::Doc(attr) => attr.gen(&mut ctx.with_lang(ctx.lang.0)), + StmtAttrKind::Unknown => gen!(ctx, "<>"), + } + } + } + + impl Gen> for SpecAttr + where + SpecAttrKind: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + self.kind.gen(&mut ctx.with_lang(ctx.lang.0)) + } + } + + impl Gen for SpecAttr { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx, { + "#![ <> ]"; + }) + } + } + + impl Gen> for SpecAttrKind + where + CfgAttr: Gen, + DocAttr: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + match self { + SpecAttrKind::Doc(attr) => attr.gen(&mut ctx.with_lang(ctx.lang.0)), + SpecAttrKind::Unknown => gen!(ctx, "<>"), + } + } + } + + lang_mixin!(Inspect, SpecAttrKind, CommonMixin); +} diff --git a/task-maker-iospec/src/spec/attr_kind/cfg.rs b/task-maker-iospec/src/spec/attr_kind/cfg.rs new file mode 100644 index 000000000..20bb1c9c5 --- /dev/null +++ b/task-maker-iospec/src/spec/attr_kind/cfg.rs @@ -0,0 +1,54 @@ +pub mod kw { + syn::custom_keyword!(cfg); +} +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct CfgAttr { + pub kw: kw::cfg, + pub paren: syn::token::Paren, + pub expr: CfgExpr, + } +} + +mod parse { + use syn::parse::*; + + use crate::ast::*; + + impl Parse for CfgAttr { + fn parse(input: ParseStream) -> Result { + let paren_input; + + Ok(Self { + kw: input.parse()?, + paren: syn::parenthesized!(paren_input in input), + expr: paren_input.parse()?, + }) + } + } +} + +pub mod ir { + use crate::ast; + + pub type CfgAttr = ast::CfgAttr; +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for CfgAttr + where + DataDefExpr: Gen, + AtomTy: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + gen!(ctx) + } + } + + lang_mixin!(Inspect, CfgAttr, CommonMixin); +} diff --git a/task-maker-iospec/src/spec/attr_kind/doc.rs b/task-maker-iospec/src/spec/attr_kind/doc.rs new file mode 100644 index 000000000..77ea76bad --- /dev/null +++ b/task-maker-iospec/src/spec/attr_kind/doc.rs @@ -0,0 +1,57 @@ +pub mod kw { + syn::custom_keyword!(doc); +} + +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct DocAttr { + pub kw: kw::doc, + pub eq: syn::Token![=], + pub str: syn::LitStr, + } +} + +mod parse { + use syn::parse::*; + + use crate::ast::*; + + impl Parse for DocAttr { + fn parse(input: ParseStream) -> Result { + Ok(Self { + kw: input.parse()?, + eq: input.parse()?, + str: input.parse()?, + }) + } + } +} + +pub mod ir { + use crate::ast; + + pub type DocAttr = ast::DocAttr; +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for DocAttr + where + DataDefExpr: Gen, + AtomTy: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let Self { str, .. } = self; + let str = str.value(); + gen!(ctx, { + "/**{} */" % (&Raw(str)); + }) + } + } + + lang_mixin!(Inspect, DocAttr, CommonMixin); +} diff --git a/task-maker-iospec/src/spec/attr_kind/mod.rs b/task-maker-iospec/src/spec/attr_kind/mod.rs new file mode 100644 index 000000000..0c0756f44 --- /dev/null +++ b/task-maker-iospec/src/spec/attr_kind/mod.rs @@ -0,0 +1,28 @@ +mod cfg; +mod doc; + +pub mod ast { + use super::*; + + pub use cfg::ast::*; + pub use doc::ast::*; +} + +pub mod kw { + pub use super::cfg::kw::*; + pub use super::doc::kw::*; +} + +pub mod ir { + use super::*; + + pub use cfg::ir::*; + pub use doc::ir::*; +} + +pub mod gen { + use super::*; + + pub use cfg::gen::*; + pub use doc::gen::*; +} diff --git a/task-maker-iospec/src/spec/cfg_expr.rs b/task-maker-iospec/src/spec/cfg_expr.rs new file mode 100644 index 000000000..da437f8ca --- /dev/null +++ b/task-maker-iospec/src/spec/cfg_expr.rs @@ -0,0 +1,241 @@ +pub mod kw { + syn::custom_keyword!(not); + syn::custom_keyword!(any); + syn::custom_keyword!(all); +} + +pub mod ast { + use crate::ast::*; + + /// AST of, e.g., `not(lang = "cpp")` in `#[cfg(not(lang = "cpp"))]`. + #[derive(Debug, Clone)] + pub struct CfgExpr { + pub kind: CfgExprKind, + } + + #[derive(Debug, Clone)] + pub enum CfgExprKind { + IsDef(CfgIsOnExpr), + Is(CfgIsExpr), + Not(CfgNotExpr), + Any(CfgAnyExpr), + All(CfgAllExpr), + } + + /// AST of, e.g., `grader` in `#[cfg(grader)]`. + #[derive(Debug, Clone)] + pub struct CfgIsOnExpr { + pub name: Name, + } + + /// AST of, e.g., `lang = "C"` in `#[cfg(lang = "C")]`. + #[derive(Debug, Clone)] + pub struct CfgIsExpr { + pub name: Name, + pub eq: syn::Token![=], + pub val: syn::LitStr, + } + + /// AST of, e.g., `not(...)` in `#[cfg(not(...))]`. + #[derive(Debug, Clone)] + pub struct CfgNotExpr { + pub kw: kw::not, + pub paren: syn::token::Paren, + pub arg: Box, + } + + /// AST of, e.g., `any(...)` in `#[cfg(any(...))]`. + #[derive(Debug, Clone)] + pub struct CfgAnyExpr { + pub kw: kw::any, + pub paren: syn::token::Paren, + pub args: syn::punctuated::Punctuated, + } + + /// AST of, e.g., `all(...)` in `#[cfg(all(...))]`. + #[derive(Debug, Clone)] + pub struct CfgAllExpr { + pub kw: kw::all, + pub paren: syn::token::Paren, + pub args: syn::punctuated::Punctuated, + } +} + +mod parse { + use syn::parse::*; + + use crate::ast::*; + + impl Parse for CfgExpr { + fn parse(input: ParseStream) -> Result { + Ok(Self { + kind: input.parse()?, + }) + } + } + + impl Parse for CfgExprKind { + fn parse(input: ParseStream) -> Result { + let la = input.lookahead1(); + Ok(if la.peek(kw::not) { + Self::Not(input.parse()?) + } else if la.peek(kw::any) { + Self::Any(input.parse()?) + } else if la.peek(kw::all) { + Self::All(input.parse()?) + } else { + let name: Name = input.parse()?; + if input.peek(syn::Token![=]) { + Self::Is(CfgIsExpr { + name, + eq: input.parse()?, + val: input.parse()?, + }) + } else { + Self::IsDef(CfgIsOnExpr { name }) + } + }) + } + } + + impl Parse for CfgNotExpr { + fn parse(input: ParseStream) -> Result { + let paren_input; + Ok(Self { + kw: input.parse()?, + paren: syn::parenthesized!(paren_input in input), + arg: paren_input.parse()?, + }) + } + } + + impl Parse for CfgAnyExpr { + fn parse(input: ParseStream) -> Result { + let paren_input; + Ok(Self { + kw: input.parse()?, + paren: syn::parenthesized!(paren_input in input), + args: syn::punctuated::Punctuated::parse_terminated(&paren_input)?, + }) + } + } + + impl Parse for CfgAllExpr { + fn parse(input: ParseStream) -> Result { + let paren_input; + Ok(Self { + kw: input.parse()?, + paren: syn::parenthesized!(paren_input in input), + args: syn::punctuated::Punctuated::parse_terminated(&paren_input)?, + }) + } + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + + impl CompileFrom for bool { + fn compile(ast: &ast::CfgExpr, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::CfgExpr { kind, .. } = ast; + Ok(kind.compile(env, dgns)?) + } + } + + impl CompileFrom for bool { + fn compile( + ast: &ast::CfgExprKind, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + Ok(match ast { + ast::CfgExprKind::IsDef(expr) => expr.compile(env, dgns)?, + ast::CfgExprKind::Is(expr) => expr.compile(env, dgns)?, + ast::CfgExprKind::Not(expr) => expr.compile(env, dgns)?, + ast::CfgExprKind::Any(expr) => expr.compile(env, dgns)?, + ast::CfgExprKind::All(expr) => expr.compile(env, dgns)?, + }) + } + } + + impl CompileFrom for bool { + fn compile(ast: &ast::CfgIsExpr, env: &Env, _dgns: &mut DiagnosticContext) -> Result { + Ok(env.cfg.is(&ast.name.ident.to_string(), &ast.val.value())) + } + } + + impl CompileFrom for bool { + fn compile( + ast: &ast::CfgIsOnExpr, + env: &Env, + _dgns: &mut DiagnosticContext, + ) -> Result { + Ok(env.cfg.is_on(&ast.name.ident.to_string())) + } + } + + impl CompileFrom for bool { + fn compile(ast: &ast::CfgNotExpr, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::CfgNotExpr { arg, .. } = ast; + Ok(!arg.as_ref().compile(env, dgns)?) + } + } + + impl CompileFrom for bool { + fn compile(ast: &ast::CfgAnyExpr, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::CfgAnyExpr { args, .. } = ast; + + Ok(args + .into_iter() + .map(|e| e.compile(env, dgns)) + .collect::>>()? + .into_iter() + .any(|x| x)) + } + } + + impl CompileFrom for bool { + fn compile(ast: &ast::CfgAllExpr, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::CfgAllExpr { args, .. } = ast; + + Ok(args + .into_iter() + .map(|e| e.compile(env, dgns)) + .collect::>>()? + .into_iter() + .all(|x| x)) + } + } +} + +pub mod sem { + #[derive(Default)] + pub struct Cfg(pub Vec); + + impl Cfg { + pub fn is(&self, key: &str, val: &str) -> bool { + self.0 + .iter() + .rev() // Later options have higher priority + .find_map(|x| { + if let Some(pos) = x.find('=') { + if x[..pos] == *key { + Some(x[(pos + 1)..] == *val) + } else { + None + } + } else if x == key { + Some(val == "1") // Option without argument == option with "=1" + } else { + None + } + }) + .unwrap_or(val == "") // No option == option with empty string + } + + pub fn is_on(&self, key: &str) -> bool { + self.is(key, "1") + } + } +} diff --git a/task-maker-iospec/src/spec/data_def_expr.rs b/task-maker-iospec/src/spec/data_def_expr.rs new file mode 100644 index 000000000..ff4642f00 --- /dev/null +++ b/task-maker-iospec/src/spec/data_def_expr.rs @@ -0,0 +1,352 @@ +pub mod ir { + use crate::ir::*; + + /// IR of the definition a value (either atomic or aggregate) in input/output data. + /// E.g., `A`, `A[i]` and `A[i][j]`, in `item A[i][j]: n32;`. + #[derive(Debug, Clone)] + pub struct DataDefExpr { + pub kind: DataDefExprKind, + pub ty: Ir, + pub root_var: Ir, + pub var: Option>, + pub alloc: Option, + } + + #[derive(Debug, Clone)] + pub enum DataDefExprKind { + Var { + var: Ir, + }, + Subscript { + array: Ir, + bracket: syn::token::Bracket, + index: Ir, + }, + Err, + } + + impl Default for DataDefExprKind { + fn default() -> Self { + Self::Err + } + } + + #[derive(Debug, Clone)] + pub struct DataExprAlloc { + pub expr: Ir, + pub info: AllocInfo, + } + + #[derive(Debug, Clone)] + pub struct AllocInfo { + pub item_ty: Ir, + pub size: Ir, + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for DataDefExpr { + fn compile( + ast: &ast::Expr, + env: &DataDefEnv, + dgns: &mut DiagnosticContext, + ) -> Result { + let kind: DataDefExprKind = ast.compile(env, dgns)?; + + Ok(Self { + ty: env.ty.clone(), + root_var: match &kind { + DataDefExprKind::Var { var } => var.clone(), + DataDefExprKind::Subscript { array, .. } => array.root_var.clone(), + DataDefExprKind::Err => Err(CompileStop)?, + }, + var: match &kind { + DataDefExprKind::Var { var } => Some(var.clone()), + _ => None, + }, + alloc: match env.ty.as_ref() { + ExprTy::Array { item, range } => Some(AllocInfo { + item_ty: item.clone(), + size: range.bound.val.clone(), + }), + _ => None, + }, + kind, + }) + } + } + + impl CompileFrom for DataDefExprKind { + fn compile( + ast: &ast::Expr, + env: &DataDefEnv, + dgns: &mut DiagnosticContext, + ) -> Result { + Ok(match ast { + ast::Expr::Var(expr) => expr.compile(env, dgns)?, + ast::Expr::Subscript(expr) => expr.compile(env, dgns)?, + other => { + dgns.error( + "invalid expression in definition", + vec![dgns.error_ann("invalid expression", HasSpan::span(other))], + vec![dgns.note_footer("only variables and subscripts are allowed")], + ); + Default::default() + } + }) + } + } + + impl CompileFrom for DataDefExprKind { + fn compile( + ast: &ast::VarExpr, + env: &DataDefEnv, + dgns: &mut DiagnosticContext, + ) -> Result { + let ast::VarExpr { name } = ast; + + Ok(DataDefExprKind::Var { + var: name.compile(env, dgns)?, + }) + } + } + + impl CompileFrom for DataDefExprKind { + fn compile( + ast: &ast::SubscriptExpr, + env: &DataDefEnv, + dgns: &mut DiagnosticContext, + ) -> Result { + let ast::SubscriptExpr { + array, + bracket, + index, + } = ast; + + let index: Ir = index.as_ref().compile(env.outer.as_ref(), dgns)?; + + Ok(match env.loc.as_ref() { + Loc::For { + range: expected_range, + parent, + } => match &index.kind { + ExprKind::Var(VarExpr { name, var }) => match &var.kind { + VarKind::Index { + range: actual_range, + } => { + if Ir::same(&expected_range, &actual_range) { + Self::Subscript { + array: array.as_ref().compile( + &DataDefEnv { + outer: env.outer.clone(), + ty: Ir::new(ExprTy::Array { + item: env.ty.clone(), + range: expected_range.clone(), + }), + loc: parent.clone(), + }, + dgns, + )?, + bracket: bracket.clone(), + index, + } + } else { + let message = format!("subscript must match an enclosing `for` index, expecting `{}`, got `{}`", quote_hir(expected_range.index.as_ref()), quote_hir(name.as_ref())); + dgns.error( + &message, + vec![ + dgns.error_ann( + "does not match enclosing `for` index", + name.span(), + ), + dgns.info_ann( + "must match this index", + expected_range.index.span(), + ), + ], + vec![], + ); + Default::default() + } + } + _ => { + let message = format!("subscript must match an enclosing `for` index, expecting `{}`, got `{}`", quote_hir(expected_range.index.as_ref()), quote_hir(name.as_ref())); + dgns.error( + &message, + vec![ + dgns.error_ann( + "does not match enclosing `for` index", + name.span(), + ), + dgns.info_ann( + "must match this index", + expected_range.index.span(), + ), + ], + vec![], + ); + Default::default() + } + }, + _ => { + let message = format!( + "subscript must match an enclosing `for` index, expecting `{}`, got an expression", + quote_hir(expected_range.index.as_ref()), + ); + dgns.error( + &message, + vec![ + dgns.error_ann( + "complex expressions not allowed here", + index.span(), + ), + dgns.info_ann("must match this index", expected_range.index.span()), + ], + vec![], + ); + Default::default() + } + }, + _ => { + dgns.error( + "subscript must match an enclosing `for` index, but none was found", + vec![dgns.error_ann("subscript without a matching `for`", index.span())], + vec![], + ); + Default::default() + } + }) + } + } +} + +mod dgns { + use super::ir::*; + use crate::dgns::*; + + impl TryHasSpan for DataDefExpr { + fn try_span(self: &Self) -> Option { + self.kind.try_span() + } + } + + impl TryHasSpan for DataDefExprKind { + fn try_span(self: &Self) -> Option { + match self { + DataDefExprKind::Var { var } => Some(var.span()), + DataDefExprKind::Subscript { array, bracket, .. } => { + array.try_span().map(|x| x.join(bracket.span).unwrap()) + } + DataDefExprKind::Err => None, + } + } + } +} + +pub mod mem { + use crate::mem::*; + + #[derive(Debug)] + pub enum NodeVal { + Atom(Box), + Array(ArrayVal), + } +} + +mod run { + use crate::ir::*; + use crate::mem::*; + use crate::run::*; + + impl EvalMut for DataDefExpr { + fn eval_mut<'a>( + self: &Self, + state: &'a mut State, + ctx: &mut Context, + ) -> Result, Stop> { + Ok(match &self.kind { + DataDefExprKind::Var { var } => { + match state.env.get_mut(&var.clone().into()).unwrap() { + NodeVal::Atom(atom) => ExprValMut::Atom(&mut **atom), + NodeVal::Array(aggr) => ExprValMut::Aggr(aggr), + } + } + DataDefExprKind::Subscript { array, index, .. } => { + let index = index.eval(state, ctx)?; + let index = match index { + ExprVal::Atom(index) => index, + _ => unreachable!(), + }; + let index = index.value_i64() as usize; + + // FIXME: should evaluate before `index`, but the borrow checker is not happy about it + let array = array.eval_mut(state, ctx)?; + + match array { + ExprValMut::Aggr(array) => match array { + ArrayVal::AtomArray(array) => ExprValMut::Atom(array.at_mut(index)), + ArrayVal::AggrArray(array) => ExprValMut::Aggr(&mut array[index]), + ArrayVal::Empty => unreachable!("unallocated array"), + }, + _ => unreachable!(), + } + } + DataDefExprKind::Err => unreachable!(), + }) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for DataDefExpr + where + DataDefExprKind: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + self.kind.gen(&mut ctx.with_lang(ctx.lang.0)) + } + } + + impl Gen> for DataDefExprKind + where + Name: Gen, + Expr: Gen, + DataDefExpr: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let ctx = &mut ctx.with_lang(ctx.lang.0); + + match self { + Self::Var { var } => gen!(ctx, "{}" % (&var.name)), + Self::Subscript { array, index, .. } => gen!(ctx, "{}[{}]" % (array, index)), + Self::Err => gen!(ctx, "<>"), + } + } + } + + lang_mixin!(Inspect, DataDefExpr, CommonMixin); + lang_mixin!(Inspect, DataDefExprKind, CommonMixin); + + impl Gen for DataExprAlloc { + fn gen(&self, ctx: GenContext) -> Result { + let Self { expr, info } = self; + gen!(ctx, { + "<>" % (expr, info); + }) + } + } + + impl Gen for AllocInfo { + fn gen(&self, ctx: GenContext) -> Result { + let Self { item_ty, size } = self; + gen!(ctx, "size {} of {}" % (size, item_ty)) + } + } +} diff --git a/task-maker-iospec/src/spec/data_var.rs b/task-maker-iospec/src/spec/data_var.rs new file mode 100644 index 000000000..99606a546 --- /dev/null +++ b/task-maker-iospec/src/spec/data_var.rs @@ -0,0 +1,58 @@ +pub mod ir { + use crate::ir::*; + use crate::sem; + + /// IR a variable containing input/output data. + /// E.g., `A` in `... read A[i][j]: n32; ...`. + #[derive(Debug)] + pub struct DataVar { + pub name: Ir, + pub ty: Ir, + pub stream: Option, + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for DataVar { + fn compile( + ast: &ast::Name, + env: &DataDefEnv, + dgns: &mut DiagnosticContext, + ) -> Result { + Ok(Self { + name: ast.compile(env.outer.as_ref(), dgns)?, + ty: env.ty.clone(), + stream: env.outer.cur_io.as_ref().map(|kw| kw.to_stream()), + }) + } + } +} + +mod dgns { + use super::ir::*; + use crate::dgns::*; + + impl HasSpan for DataVar { + fn span(self: &Self) -> Span { + self.name.span() + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen for DataVar { + fn gen(&self, ctx: GenContext) -> Result { + let Self { name, ty, .. } = self; + gen!(ctx, { + "<>" % (name, ty); + }) + } + } +} diff --git a/task-maker-iospec/src/spec/expr.rs b/task-maker-iospec/src/spec/expr.rs new file mode 100644 index 000000000..8070f1537 --- /dev/null +++ b/task-maker-iospec/src/spec/expr.rs @@ -0,0 +1,506 @@ +pub mod ast { + use crate::ast::*; + + /// AST of an expression. + + #[derive(Debug, Clone)] + pub enum Expr { + IntLit(IntLitExpr), + Var(VarExpr), + Subscript(SubscriptExpr), + Paren(ParenExpr), + Mul(MulExpr), + Sum(SumExpr), + RelChain(RelChainExpr), + } +} + +mod parse { + use syn::parse::*; + + use crate::ast::*; + + impl Parse for Expr { + fn parse(input: ParseStream) -> Result { + Self::parse_rel(input) + } + } + + impl Expr { + fn parse_atomic(input: ParseStream) -> Result { + let mut current = if input.peek(syn::token::Paren) { + let inner_input; + + Self::Paren(ParenExpr { + paren: syn::parenthesized!(inner_input in input), + inner: Box::new(inner_input.parse()?), + }) + } else if input.peek(syn::Lit) { + let token: syn::LitInt = input.parse()?; + let value_i64 = token.base10_parse()?; + Self::IntLit(IntLitExpr { token, value_i64 }) + } else { + Self::Var(VarExpr { + name: input.parse()?, + }) + }; + + while input.peek(syn::token::Bracket) { + let index_input; + current = Self::Subscript(SubscriptExpr { + array: Box::new(current), + bracket: syn::bracketed!(index_input in input), + index: index_input.parse()?, + }); + } + Ok(current) + } + + fn parse_mul(input: ParseStream) -> Result { + let first: Self = Self::parse_atomic(input)?; + Ok(if input.peek(syn::Token![*]) { + let mut chain = syn::punctuated::Punctuated::::new(); + chain.push_value(first); + while input.peek(syn::Token![*]) { + chain.push_punct(input.parse()?); + chain.push_value(Self::parse_atomic(input)?); + } + Self::Mul(MulExpr { factors: chain }) + } else { + first + }) + } + + fn parse_sum(input: ParseStream) -> Result { + let first_sign: Option = if Self::peek_sign(input) { + Some(input.parse()?) + } else { + None + }; + + let first: Self = Self::parse_mul(input)?; + + Ok(if first_sign.is_some() || Self::peek_sign(input) { + let mut chain = syn::punctuated::Punctuated::::new(); + chain.push_value(first); + while Self::peek_sign(input) { + chain.push_punct(input.parse()?); + chain.push_value(Self::parse_mul(input)?); + } + Self::Sum(SumExpr { + first_sign, + terms: chain, + }) + } else { + first + }) + } + + fn parse_rel(input: ParseStream) -> Result { + let first: Self = Self::parse_sum(input)?; + + Ok(if Self::peek_rel_op(input) { + let mut chain = syn::punctuated::Punctuated::::new(); + chain.push_value(first); + while Self::peek_rel_op(input) { + chain.push_punct(input.parse()?); + chain.push_value(Self::parse_sum(input)?); + } + Self::RelChain(RelChainExpr { chain }) + } else { + first + }) + } + + fn peek_sign(input: ParseStream) -> bool { + input.peek(syn::Token![+]) || input.peek(syn::Token![-]) + } + + fn peek_rel_op(input: ParseStream) -> bool { + [ + input.peek(syn::Token![==]), + input.peek(syn::Token![!=]), + input.peek(syn::Token![<=]), + input.peek(syn::Token![>=]), + input.peek(syn::Token![<]), + input.peek(syn::Token![>]), + ] + .iter() + .any(|b| *b) + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn rel_chain() { + let _: Expr = syn::parse_str("a == 1").unwrap(); + let _: Expr = syn::parse_str("1 <= 2 <= 3").unwrap(); + } + } +} + +pub mod ir { + use crate::ir::*; + + /// IR of a value (rvalue, atomic or aggregate) defined by an expression. + /// E.g., `A[B[i]]` in `... for i upto A[B[j]][k] { ... } ...`. + #[derive(Debug)] + pub struct Expr { + pub kind: ExprKind, + pub ty: Ir, + } + + #[derive(Debug)] + pub enum ExprKind { + Lit(LitExpr), + Var(VarExpr), + Subscript(SubscriptExpr), + Paren(ParenExpr), + Mul(MulExpr), + Sum(SumExpr), + RelChain(RelChainExpr), + Err, + } + + impl Default for ExprKind { + fn default() -> Self { + Self::Err + } + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + use crate::sem; + + impl CompileFrom for ExprKind { + fn compile(ast: &ast::Expr, env: &Env, dgns: &mut DiagnosticContext) -> Result { + Ok(match ast { + ast::Expr::IntLit(expr) => expr.compile(env, dgns)?, + ast::Expr::Var(expr) => expr.compile(env, dgns)?, + ast::Expr::Subscript(expr) => expr.compile(env, dgns)?, + ast::Expr::Paren(expr) => expr.compile(env, dgns)?, + ast::Expr::Mul(expr) => expr.compile(env, dgns)?, + ast::Expr::Sum(expr) => expr.compile(env, dgns)?, + ast::Expr::RelChain(expr) => expr.compile(env, dgns)?, + }) + } + } + + impl CompileFrom for Expr { + fn compile(ast: &ast::Expr, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let kind: ExprKind = ast.compile(env, dgns)?; + + Ok(Expr { + ty: match &kind { + ExprKind::Var(VarExpr { var, .. }) => var.ty.clone(), + ExprKind::Subscript(SubscriptExpr { array, index, .. }) => { + match array.ty.as_ref() { + ExprTy::Array { item, range } => { + match index.ty.as_ref() { + ExprTy::Atom { atom_ty } + if atom_ty.sem == range.bound.ty.sem => + { + () + } + _ => dgns.error( + &format!( + "expected index of type `{}`, got `{}`", + quote_hir(range.bound.ty.as_ref()), + quote_hir(index.ty.as_ref()), + ), + vec![ + dgns.error_ann( + &format!( + "invalid index type `{}`", + quote_hir(index.ty.as_ref()) + ), + index.span(), + ), + dgns.info_ann("array range", range.span()), + dgns.info_ann("expected type", range.bound.ty.span()), + ] + .into_iter() + .chain(match index.ty.as_ref() { + ExprTy::Atom { atom_ty } => { + Some(dgns.info_ann("got this type", atom_ty.span())) + } + _ => None, + }) + .collect(), + vec![], + ), + } + item.clone() + } + ExprTy::Err => Default::default(), + _ => { + dgns.error( + &format!( + "cannot index into a value of non-array type `{}`", + quote_hir(array.ty.as_ref()), + ), + vec![dgns.error_ann("must be an array", array.span())], + vec![], + ); + + Default::default() + } + } + } + ExprKind::Lit(LitExpr { ty, .. }) + | ExprKind::Mul(MulExpr { ty, .. }) + | ExprKind::Sum(SumExpr { ty, .. }) => Ir::new(ExprTy::Atom { + atom_ty: ty.clone(), + }), + ExprKind::RelChain(RelChainExpr { rels, .. }) => Ir::new(ExprTy::Atom { + atom_ty: Ir::new(AtomTy { + sem: Some(sem::AtomTy::Bool), + kind: AtomTyKind::Rel { + rels: (*rels).clone(), + }, + }), + }), + ExprKind::Paren(ParenExpr { inner, .. }) => inner.ty.clone(), + ExprKind::Err => Default::default(), + }, + kind, + }) + } + } +} + +mod dgns { + use syn::spanned::Spanned; + + use crate::ast; + use crate::dgns::*; + use crate::ir::*; + + impl HasSpan for Expr { + fn span(self: &Self) -> Span { + self.kind.span() + } + } + + impl HasSpan for ExprKind { + fn span(self: &Self) -> Span { + match self { + Self::Var(VarExpr { name, .. }) => name.ident.span(), + Self::Subscript(SubscriptExpr { array, bracket, .. }) => { + array.span().join(bracket.span).unwrap() + } + Self::Lit(LitExpr { token, .. }) => token.span(), + Self::Paren(ParenExpr { paren, .. }) => paren.span, + Self::Mul(MulExpr { factors, .. }) => factors + .first() + .unwrap() + .span() + .join(factors.last().unwrap().span()) + .unwrap(), + Self::Err => panic!(), + Self::Sum(SumExpr { terms, .. }) => { + let extrema: Vec<_> = [terms.first(), terms.last()] + .iter() + .map(|t| t.unwrap()) + .map(|(sign, term)| { + let sign_span = match sign { + Sign::Plus(Some(op)) => Some(op.span()), + Sign::Minus(op) => Some(op.span()), + Sign::Plus(None) => None, + }; + if let Some(span) = sign_span { + term.span().join(span).unwrap() + } else { + term.span() + } + }) + .collect(); + extrema[0].join(extrema[1]).unwrap() + } + Self::RelChain(RelChainExpr { rels, .. }) => rels + .first() + .unwrap() + .0 + .span() + .join(rels.last().unwrap().2.span()) + .unwrap(), + } + } + } + + impl HasSpan for ast::Expr { + fn span(self: &Self) -> Span { + match self { + Self::Var(ast::VarExpr { name, .. }) => name.ident.span(), + Self::Subscript(ast::SubscriptExpr { array, bracket, .. }) => { + array.span().join(bracket.span).unwrap() + } + Self::IntLit(ast::IntLitExpr { token, .. }) => token.span(), + Self::Paren(ast::ParenExpr { paren, .. }) => paren.span, + Self::Mul(ast::MulExpr { factors, .. }) => factors + .first() + .unwrap() + .span() + .join(factors.last().unwrap().span()) + .unwrap(), + Self::Sum(ast::SumExpr { + first_sign, terms, .. + }) => first_sign + .as_ref() + .map(|s| s.span()) + .unwrap_or(terms.first().unwrap().span()) + .span() + .join(terms.last().unwrap().span()) + .unwrap(), + Self::RelChain(ast::RelChainExpr { chain }) => chain + .first() + .unwrap() + .span() + .join(chain.last().unwrap().span()) + .unwrap(), + } + } + } +} + +pub mod mem { + use crate::mem::*; + + #[derive(Debug)] + pub enum ExprVal<'a> { + Atom(AtomVal), + Array(&'a ArrayVal), + } + + #[derive(Debug)] + pub enum ArrayVal { + AtomArray(Box), + AggrArray(Vec), + Empty, + } + + impl<'a> ExprVal<'a> { + pub fn unwrap_value_i64(&self) -> i64 { + match self { + ExprVal::Atom(atom) => atom.value_i64(), + _ => unreachable!(), + } + } + } + + #[derive(Debug)] + pub enum ExprValMut<'a> { + ConstAtom(AtomVal), + Atom(&'a mut dyn AtomCell), + Aggr(&'a mut ArrayVal), + } +} + +pub mod sem { + use crate::sem::*; + + #[derive(Debug, Clone, Copy)] + pub struct AtomVal { + ty: crate::sem::AtomTy, + value: i64, + } + + impl AtomVal { + pub fn new(ty: AtomTy, value: i64) -> AtomVal { + Self::try_new(ty, value).unwrap() + } + + pub fn try_new(ty: AtomTy, value: i64) -> Result { + let (min, max) = ty.value_range(); + + if min <= value && value <= max { + Ok(AtomVal { ty, value }) + } else { + Err(AtomTypeError { ty, actual: value }) + } + } + + pub fn value_i64(self: &Self) -> i64 { + self.value + } + + pub fn ty(self: &Self) -> AtomTy { + self.ty + } + } + + #[derive(Debug)] + pub struct AtomTypeError { + pub ty: AtomTy, + pub actual: i64, + } +} + +mod run { + use crate::ir::*; + use crate::mem::*; + use crate::run::*; + + impl Eval for Expr { + fn eval<'a>(self: &Self, state: &'a State, ctx: &mut Context) -> Result, Stop> { + match &self.kind { + ExprKind::Lit(expr) => expr.eval(state, ctx), + ExprKind::Var(expr) => expr.eval(state, ctx), + ExprKind::Subscript(expr) => expr.eval(state, ctx), + ExprKind::Paren(expr) => expr.eval(state, ctx), + ExprKind::Mul(expr) => expr.eval(state, ctx), + ExprKind::Sum(expr) => expr.eval(state, ctx), + ExprKind::RelChain(expr) => expr.eval(state, ctx), + ExprKind::Err => todo!(), + } + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for Expr + where + ExprKind: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + self.kind.gen(&mut ctx.with_lang(ctx.lang.0)) + } + } + + lang_mixin!(Inspect, Expr, CommonMixin); + + impl Gen> for ExprKind + where + VarExpr: Gen, + SubscriptExpr: Gen, + LitExpr: Gen, + ParenExpr: Gen, + MulExpr: Gen, + SumExpr: Gen, + RelChainExpr: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + match self { + Self::Var(expr) => expr.gen(&mut ctx.with_lang(ctx.lang.0)), + Self::Subscript(expr) => expr.gen(&mut ctx.with_lang(ctx.lang.0)), + Self::Lit(expr) => expr.gen(&mut ctx.with_lang(ctx.lang.0)), + Self::Paren(expr) => expr.gen(&mut ctx.with_lang(ctx.lang.0)), + Self::Mul(expr) => expr.gen(&mut ctx.with_lang(ctx.lang.0)), + Self::Sum(expr) => expr.gen(&mut ctx.with_lang(ctx.lang.0)), + Self::RelChain(expr) => expr.gen(&mut ctx.with_lang(ctx.lang.0)), + Self::Err => gen!(ctx, "<>"), + } + } + } + + lang_mixin!(Inspect, ExprKind, CommonMixin); +} diff --git a/task-maker-iospec/src/spec/expr_kind/lit.rs b/task-maker-iospec/src/spec/expr_kind/lit.rs new file mode 100644 index 000000000..be39e185d --- /dev/null +++ b/task-maker-iospec/src/spec/expr_kind/lit.rs @@ -0,0 +1,119 @@ +pub mod ast { + #[derive(Debug, Clone)] + pub struct IntLitExpr { + pub token: syn::LitInt, + pub value_i64: i64, + } +} + +pub mod ir { + use crate::ir::*; + use crate::sem; + + #[derive(Debug)] + pub struct LitExpr { + pub token: syn::LitInt, + pub value: sem::AtomVal, + pub ty: Ir, + } +} + +mod compile { + use std::str::FromStr; + + use crate::ast; + use crate::compile::*; + use crate::ir::*; + use crate::sem; + + impl CompileFrom for ExprKind { + fn compile( + ast: &ast::IntLitExpr, + _env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let ast::IntLitExpr { token, value_i64 } = ast; + + let suffix = token.suffix(); + let ty = if suffix.is_empty() { + Some(sem::AtomTy::I32) + } else { + match sem::AtomTy::from_str(suffix) { + Ok(ty) => Some(ty), + Err(_) => { + dgns.error( + &format!("invalid literal suffix `{}`", suffix), + vec![dgns.error_ann("invalid suffix", token.span())], + vec![], + ); + + return Ok(Default::default()); + } + } + }; + + let value = match ty { + Some(ty) => match sem::AtomVal::try_new(ty, *value_i64) { + Ok(value) => Some(value), + Err(_) => None, + }, + _ => None, + }; + + Ok(if let Some(value) = value { + ExprKind::Lit(LitExpr { + value, + ty: Ir::new(AtomTy { + sem: Some(value.ty()), + kind: AtomTyKind::Lit { + token: token.clone(), + }, + }), + token: token.clone(), + }) + } else { + dgns.error( + &format!("invalid literal",), + vec![if ty.is_none() { + dgns.error_ann("invalid suffix", token.span()) + } else { + dgns.error_ann("value outside range", token.span()) + }], + vec![], + ); + Default::default() + }) + } + } +} + +mod run { + use crate::ir::*; + use crate::mem::*; + use crate::run::*; + + impl Eval for LitExpr { + fn eval<'a>( + self: &Self, + _state: &'a State, + _ctx: &mut Context, + ) -> Result, Stop> { + Ok(ExprVal::Atom(self.value)) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for LitExpr { + fn gen(&self, ctx: GenContext>) -> Result { + let LitExpr { value, .. } = self; + let value = value.value_i64(); + ctx.append(value) + } + } + + lang_mixin!(Inspect, LitExpr, CommonMixin); +} diff --git a/task-maker-iospec/src/spec/expr_kind/mod.rs b/task-maker-iospec/src/spec/expr_kind/mod.rs new file mode 100644 index 000000000..79b189ccd --- /dev/null +++ b/task-maker-iospec/src/spec/expr_kind/mod.rs @@ -0,0 +1,42 @@ +mod lit; +mod mul; +mod paren; +mod rel; +mod subscript; +mod sum; +mod var; + +pub mod ast { + use super::*; + + pub use lit::ast::*; + pub use mul::ast::*; + pub use paren::ast::*; + pub use rel::ast::*; + pub use subscript::ast::*; + pub use sum::ast::*; + pub use var::ast::*; +} + +pub mod ir { + use super::*; + + pub use lit::ir::*; + pub use mul::ir::*; + pub use paren::ir::*; + pub use rel::ir::*; + pub use subscript::ir::*; + pub use sum::ir::*; + pub use var::ir::*; +} + +pub mod gen { + use super::*; + + pub use mul::gen::*; + pub use paren::gen::*; + pub use rel::gen::*; + pub use subscript::gen::*; + pub use sum::gen::*; + pub use var::gen::*; +} diff --git a/task-maker-iospec/src/spec/expr_kind/mul.rs b/task-maker-iospec/src/spec/expr_kind/mul.rs new file mode 100644 index 000000000..f96599a17 --- /dev/null +++ b/task-maker-iospec/src/spec/expr_kind/mul.rs @@ -0,0 +1,90 @@ +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct MulExpr { + pub factors: syn::punctuated::Punctuated, + } +} + +pub mod ir { + use crate::ir::*; + + #[derive(Debug)] + pub struct MulExpr { + pub factors: Vec>, + pub ops: Vec, + pub ty: Ir, + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for ExprKind { + fn compile(ast: &ast::MulExpr, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::MulExpr { factors } = ast; + let (factors, ops) = unzip_punctuated(factors.clone()); + let factors: Vec> = factors + .iter() + .map(|f| f.compile(env, dgns)) + .collect::>()?; + + let ty: Option> = ExprList(&factors).analyze(dgns); + + Ok(match ty { + Some(ty) => ExprKind::Mul(MulExpr { + ty: ty.clone(), + factors, + ops, + }), + _ => Default::default(), + }) + } + } +} + +mod run { + use crate::ir::*; + use crate::mem::*; + use crate::run::*; + use crate::sem; + + impl Eval for MulExpr { + fn eval<'a>(self: &Self, state: &'a State, ctx: &mut Context) -> Result, Stop> { + let ty = self.ty.sem.unwrap(); + let mut cur = sem::AtomVal::new(ty, 1); + + for factor in &self.factors { + let factor = factor.eval(state, ctx)?.unwrap_value_i64(); + + cur = cur + .value_i64() + .checked_mul(factor) + .and_then(|val| sem::AtomVal::try_new(ty, val).ok()) + .ok_or_else(|| anyhow::anyhow!("mul too big (TODO: handle this)"))?; + } + Ok(ExprVal::Atom(cur)) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for MulExpr + where + Expr: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let Self { factors, .. } = self; + let ctx = &mut ctx.with_lang(ctx.lang.0); + gen!(ctx, "{}" % (&Punctuated(factors.to_vec(), " * "))) + } + } + + lang_mixin!(Inspect, MulExpr, CommonMixin); +} diff --git a/task-maker-iospec/src/spec/expr_kind/paren.rs b/task-maker-iospec/src/spec/expr_kind/paren.rs new file mode 100644 index 000000000..7b87d9b05 --- /dev/null +++ b/task-maker-iospec/src/spec/expr_kind/paren.rs @@ -0,0 +1,66 @@ +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct ParenExpr { + pub paren: syn::token::Paren, + pub inner: Box, + } +} + +pub mod ir { + use crate::ir::*; + + #[derive(Debug)] + pub struct ParenExpr { + pub paren: syn::token::Paren, + pub inner: Ir, + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for ExprKind { + fn compile(ast: &ast::ParenExpr, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::ParenExpr { paren, inner } = ast; + + Ok(ExprKind::Paren(ParenExpr { + paren: paren.clone(), + inner: inner.as_ref().compile(env, dgns)?, + })) + } + } +} + +mod run { + use crate::ir::*; + use crate::mem::*; + use crate::run::*; + + impl Eval for ParenExpr { + fn eval<'a>(self: &Self, state: &'a State, ctx: &mut Context) -> Result, Stop> { + self.inner.eval(state, ctx) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for ParenExpr + where + Expr: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let Self { inner, .. } = self; + let ctx = &mut ctx.with_lang(ctx.lang.0); + gen!(ctx, "({})" % inner) + } + } + + lang_mixin!(Inspect, ParenExpr, CommonMixin); +} diff --git a/task-maker-iospec/src/spec/expr_kind/rel.rs b/task-maker-iospec/src/spec/expr_kind/rel.rs new file mode 100644 index 000000000..64f11e973 --- /dev/null +++ b/task-maker-iospec/src/spec/expr_kind/rel.rs @@ -0,0 +1,199 @@ +pub mod ast { + use crate::ast::*; + + #[derive(Copy, Clone, Debug)] + pub enum RelOp { + Eq(syn::Token![==]), + Ne(syn::Token![!=]), + Lt(syn::Token![<]), + Le(syn::Token![<=]), + Gt(syn::Token![>]), + Ge(syn::Token![>=]), + } + + #[derive(Debug, Clone)] + pub struct RelChainExpr { + pub chain: syn::punctuated::Punctuated, + } +} + +mod parse { + use crate::ast::*; + + use syn::parse::*; + + impl Parse for RelOp { + fn parse(input: ParseStream) -> Result { + let la = input.lookahead1(); + Ok(if la.peek(syn::Token![==]) { + Self::Eq(input.parse()?) + } else if la.peek(syn::Token![!=]) { + Self::Ne(input.parse()?) + } else if la.peek(syn::Token![<=]) { + Self::Le(input.parse()?) + } else if la.peek(syn::Token![>=]) { + Self::Ge(input.parse()?) + } else if la.peek(syn::Token![<]) { + Self::Lt(input.parse()?) + } else if la.peek(syn::Token![>]) { + Self::Gt(input.parse()?) + } else { + Err(la.error())? + }) + } + } +} + +pub mod ir { + use crate::ir::*; + + pub type RelOp = super::ast::RelOp; + + #[derive(Debug, Clone)] + pub struct RelExpr(pub Ir, pub RelOp, pub Ir); + + #[derive(Debug)] + pub struct RelChainExpr { + pub first: Ir, + pub rest_chain: Vec<(RelOp, Ir)>, + pub rels: Vec, + } +} +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for ExprKind { + fn compile( + ast: &ast::RelChainExpr, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let ast::RelChainExpr { chain } = ast; + + let (values, ops) = unzip_punctuated(chain.clone()); + let values: Vec> = values + .iter() + .map(|v| v.compile(env, dgns)) + .collect::>()?; + + let first = values.first().unwrap().clone(); + let rest_chain = ops + .iter() + .cloned() + .zip(values.iter().skip(1).cloned()) + .collect(); + + let rels: Vec<_> = values + .iter() + .skip(1) + .zip(values.iter()) + .zip(ops.into_iter()) + .map(|((right, left), op)| RelExpr(left.clone(), op, right.clone())) + .collect(); + + // TODO: type + + Ok(ExprKind::RelChain(RelChainExpr { + first, + rest_chain, + rels, + })) + } + } +} + +mod run { + use crate::ir::*; + use crate::mem::*; + use crate::run::*; + use crate::sem; + + impl RelOp { + pub fn apply(self: &Self, left: i64, right: i64) -> bool { + match self { + RelOp::Eq(_) => left == right, + RelOp::Ne(_) => left != right, + RelOp::Lt(_) => left < right, + RelOp::Le(_) => left <= right, + RelOp::Gt(_) => left > right, + RelOp::Ge(_) => left >= right, + } + } + } + + impl Eval for RelChainExpr { + fn eval<'a>(self: &Self, state: &'a State, ctx: &mut Context) -> Result, Stop> { + Ok(ExprVal::Atom(sem::AtomVal::new( + sem::AtomTy::Bool, + match self + .rels + .iter() + .map::, _>(|RelExpr(left, op, right)| { + let left = left.eval(state, ctx)?.unwrap_value_i64(); + let right = right.eval(state, ctx)?.unwrap_value_i64(); + + Ok(op.apply(left, right)) + }) + .find(|r| if let Ok(false) = r { true } else { false }) + { + Some(res) => { + res?; + 0 + } + None => 1, + }, + ))) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for RelOp { + fn gen(&self, ctx: GenContext>) -> Result { + match self { + Self::Eq(_) => gen!(ctx, "=="), + Self::Ne(_) => gen!(ctx, "!="), + Self::Lt(_) => gen!(ctx, "<"), + Self::Le(_) => gen!(ctx, "<="), + Self::Gt(_) => gen!(ctx, ">"), + Self::Ge(_) => gen!(ctx, ">="), + } + } + } + + lang_mixin!(Inspect, RelOp, CommonMixin); + + impl Gen> for RelChainExpr + where + RelExpr: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let Self { rels, .. } = self; + let ctx = &mut ctx.with_lang(ctx.lang.0); + gen!( + ctx, + "{}" % (&Punctuated(rels.iter().cloned().collect(), " && ")) + ) + } + } + + impl Gen> for RelExpr + where + Expr: Gen, + RelOp: Gen, + ExprKind: Gen, // FIXME: should not be needed + { + fn gen(&self, ctx: GenContext>) -> Result { + let Self(lexpr, op, rexpr) = self; + gen!(ctx, "{} {} {}" % (lexpr, op, rexpr)) + } + } + + lang_mixin!(Inspect, RelChainExpr, CommonMixin); + lang_mixin!(Inspect, RelExpr, CommonMixin); +} diff --git a/task-maker-iospec/src/spec/expr_kind/subscript.rs b/task-maker-iospec/src/spec/expr_kind/subscript.rs new file mode 100644 index 000000000..961462c1d --- /dev/null +++ b/task-maker-iospec/src/spec/expr_kind/subscript.rs @@ -0,0 +1,97 @@ +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct SubscriptExpr { + pub array: Box, + pub bracket: syn::token::Bracket, + pub index: Box, + } +} + +pub mod ir { + use crate::ir::*; + + #[derive(Debug)] + pub struct SubscriptExpr { + pub array: Ir, + pub bracket: syn::token::Bracket, + pub index: Ir, + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for ExprKind { + fn compile( + ast: &ast::SubscriptExpr, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let ast::SubscriptExpr { + array, + bracket, + index, + } = ast; + + Ok(ExprKind::Subscript(SubscriptExpr { + array: array.as_ref().compile(env, dgns)?, + index: index.as_ref().compile(env, dgns)?, + bracket: bracket.clone(), + })) + } + } +} + +mod run { + use crate::ir::*; + use crate::mem::*; + use crate::run::*; + + impl Eval for SubscriptExpr { + fn eval<'a>(self: &Self, state: &'a State, ctx: &mut Context) -> Result, Stop> { + let index = self.index.eval(state, ctx)?.unwrap_value_i64() as usize; + + Ok( + match (self.array.ty.as_ref(), self.array.eval(state, ctx)?) { + (ExprTy::Array { item, .. }, ExprVal::Array(aggr)) => { + match (item.as_ref(), aggr) { + (ExprTy::Atom { atom_ty }, ArrayVal::AtomArray(array)) => { + ExprVal::Atom( + array + .at(index) + .get(atom_ty.sem.unwrap()) + .expect("TODO: handle empty"), + ) + } + (_, ArrayVal::AggrArray(array)) => ExprVal::Array(&array[index]), + _ => todo!(), + } + } + _ => todo!(), + }, + ) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for SubscriptExpr + where + Expr: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let Self { array, index, .. } = self; + let ctx = &mut ctx.with_lang(ctx.lang.0); + gen!(ctx, "{}[{}]" % (array, index)) + } + } + + lang_mixin!(Inspect, SubscriptExpr, CommonMixin); +} diff --git a/task-maker-iospec/src/spec/expr_kind/sum.rs b/task-maker-iospec/src/spec/expr_kind/sum.rs new file mode 100644 index 000000000..b0ba153ca --- /dev/null +++ b/task-maker-iospec/src/spec/expr_kind/sum.rs @@ -0,0 +1,171 @@ +pub mod ast { + use crate::ast::*; + + /// Plus or minus + #[derive(Debug, Clone)] + pub enum Sign { + Plus(Option), + Minus(syn::Token![-]), + } + + #[derive(Debug, Clone)] + pub struct SumExpr { + pub first_sign: Option, + pub terms: syn::punctuated::Punctuated, + } +} + +mod parse { + use super::ast; + use syn::parse::*; + + impl Parse for ast::Sign { + fn parse(input: ParseStream) -> Result { + let la = input.lookahead1(); + Ok(if la.peek(syn::Token![+]) { + Self::Plus(input.parse()?) + } else if la.peek(syn::Token![-]) { + Self::Minus(input.parse()?) + } else { + Err(la.error())? + }) + } + } +} + +pub mod ir { + use crate::ir::*; + + pub type Sign = super::ast::Sign; + pub type TermExpr = (Sign, Ir); + + #[derive(Debug)] + pub struct SumExpr { + pub terms: Vec, + pub ty: Ir, + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for ExprKind { + fn compile(ast: &ast::SumExpr, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::SumExpr { first_sign, terms } = ast; + + let (terms, ops) = unzip_punctuated(terms.clone()); + + let terms = terms + .iter() + .map(|f| f.compile(env, dgns)) + .collect::>>()?; + + let terms: Vec = + std::iter::once(first_sign.as_ref().cloned().unwrap_or(Sign::Plus(None))) + .chain(ops.into_iter()) + .zip(terms.into_iter()) + .collect(); + + let ty = terms + .first() + .as_ref() + .unwrap() + .1 + .ty + .clone() + .to_atom_ty() + .unwrap(); // FIXME: type diagnostics + + Ok(ExprKind::Sum(SumExpr { terms, ty })) + } + } +} + +mod dgns { + use syn::spanned::Spanned; + + use super::ast::*; + use crate::dgns::*; + + impl HasSpan for Sign { + fn span(self: &Self) -> Span { + match self { + Sign::Plus(token) => token.span(), + Sign::Minus(token) => token.span(), + } + } + } +} + +mod run { + use crate::ir::*; + use crate::mem::*; + use crate::run::*; + use crate::sem; + + impl Eval for SumExpr { + fn eval<'a>(self: &Self, state: &'a State, ctx: &mut Context) -> Result, Stop> { + let ty = self.ty.sem.unwrap(); + let mut cur = sem::AtomVal::new(ty, 0); + + for (sign, term) in &self.terms { + let term = term.eval(state, ctx)?.unwrap_value_i64(); + let term = match sign { + Sign::Plus(_) => term, + Sign::Minus(_) => term.checked_neg().ok_or_else(|| { + anyhow::anyhow!("Invalid subtraction, number too big (TODO: handle)") + })?, + }; + + cur = cur + .value_i64() + .checked_add(term) + .and_then(|val| sem::AtomVal::try_new(ty, val).ok()) + .ok_or_else(|| { + anyhow::anyhow!("Invalid summation, numbers too big (TODO: handle)") + })?; + } + + Ok(ExprVal::Atom(cur)) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for Sign + where + Expr: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + match self { + Sign::Plus(None) => gen!(ctx, ""), + Sign::Plus(_) => gen!(ctx, " + "), + Sign::Minus(_) => gen!(ctx, " - "), + } + } + } + + lang_mixin!(Inspect, Sign, CommonMixin); + + impl Gen> for SumExpr + where + Expr: Gen, + Sign: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let Self { terms, .. } = self; + let ctx = &mut ctx.with_lang(ctx.lang.0); + for (sign, term) in terms { + gen!(ctx, "{}{}" % (sign, term))?; + } + gen!(ctx) + } + } + + lang_mixin!(Inspect, SumExpr, CommonMixin); +} diff --git a/task-maker-iospec/src/spec/expr_kind/var.rs b/task-maker-iospec/src/spec/expr_kind/var.rs new file mode 100644 index 000000000..3844bca72 --- /dev/null +++ b/task-maker-iospec/src/spec/expr_kind/var.rs @@ -0,0 +1,63 @@ +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct VarExpr { + pub name: Name, + } +} + +pub mod ir { + use crate::ir::*; + + #[derive(Debug)] + pub struct VarExpr { + pub var: Ir, + pub name: Ir, + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for ExprKind { + fn compile(ast: &ast::VarExpr, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::VarExpr { name } = ast; + + let name = name.compile(env, dgns)?; + let var = env.resolve(&name, dgns); + + Ok(ExprKind::Var(VarExpr { var, name })) + } + } +} + +mod run { + use crate::ir::*; + use crate::mem::*; + use crate::run::*; + + impl Eval for VarExpr { + fn eval<'a>(self: &Self, state: &'a State, ctx: &mut Context) -> Result, Stop> { + self.var.eval(state, ctx) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for VarExpr + where + Name: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + self.name.gen(&mut ctx.with_lang(ctx.lang.0)) + } + } + + lang_mixin!(Inspect, VarExpr, CommonMixin); +} diff --git a/task-maker-iospec/src/spec/expr_ty.rs b/task-maker-iospec/src/spec/expr_ty.rs new file mode 100644 index 000000000..ce6cc4d49 --- /dev/null +++ b/task-maker-iospec/src/spec/expr_ty.rs @@ -0,0 +1,41 @@ +pub mod ir { + use crate::ir::*; + + /// IR of the type of a value (either atomic or aggregate) + #[derive(Debug)] + pub enum ExprTy { + Atom { atom_ty: Ir }, + Array { item: Ir, range: Ir }, + Err, + } + + impl Default for ExprTy { + fn default() -> Self { + Self::Err + } + } + + impl ExprTy { + pub fn to_atom_ty(&self) -> Option> { + match self { + ExprTy::Atom { atom_ty } => Some(atom_ty.clone()), + _ => None, + } + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen for ExprTy { + fn gen(&self, ctx: GenContext) -> Result { + match self { + ExprTy::Atom { atom_ty, .. } => gen!(ctx, "{}" % atom_ty), + ExprTy::Array { item, .. } => gen!(ctx, "array of {}" % item), + ExprTy::Err => gen!(ctx, "<>"), + } + } + } +} diff --git a/task-maker-iospec/src/spec/meta_stmt.rs b/task-maker-iospec/src/spec/meta_stmt.rs new file mode 100644 index 000000000..b1c4ba9ef --- /dev/null +++ b/task-maker-iospec/src/spec/meta_stmt.rs @@ -0,0 +1,164 @@ +pub mod ast { + use syn::Token; + + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct MetaStmt { + pub at_sign: Token![@], + pub kind: MetaStmtKind, + } + + #[derive(Debug, Clone)] + pub enum MetaStmtKind { + Set(SetMetaStmt), + Call(CallMetaStmt), + Resize(ResizeMetaStmt), + } +} + +mod parse { + use syn::parse::*; + + use crate::ast::*; + + impl Parse for MetaStmt { + fn parse(input: ParseStream) -> Result { + Ok(Self { + at_sign: input.parse()?, + kind: input.parse()?, + }) + } + } + + impl Parse for MetaStmtKind { + fn parse(input: ParseStream) -> Result { + let la = input.lookahead1(); + Ok(if la.peek(kw::set) { + Self::Set(input.parse()?) + } else if la.peek(kw::call) { + Self::Call(input.parse()?) + } else if la.peek(kw::resize) { + Self::Resize(input.parse()?) + } else { + Err(la.error())? + }) + } + } +} + +pub mod ir { + use crate::ir::*; + + /// IR of a `@`-statement. + #[derive(Debug)] + pub struct MetaStmt> { + pub at_sign: syn::token::At, + pub kind: T, + } + + #[derive(Debug)] + pub enum MetaStmtKind { + Set(Ir), + Call(Ir), + Resize(Ir), + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for MetaStmt { + fn compile(ast: &ast::MetaStmt, _env: &Env, _dgns: &mut DiagnosticContext) -> Result { + let ast::MetaStmt { at_sign, kind } = ast; + Ok(Self { + at_sign: at_sign.clone(), + kind: kind.clone(), + }) + } + } + impl CompileFrom> for MetaStmt { + fn compile( + input: &MetaStmt, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let MetaStmt { at_sign, kind } = input; + Ok(Self { + at_sign: at_sign.clone(), + kind: kind.compile(env, dgns)?, + }) + } + } + + impl CompileFrom for MetaStmtKind { + fn compile( + ast: &ast::MetaStmtKind, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + Ok(match ast { + ast::MetaStmtKind::Set(stmt) => Self::Set(stmt.compile(env, dgns)?), + ast::MetaStmtKind::Call(stmt) => Self::Call(stmt.compile(env, dgns)?), + ast::MetaStmtKind::Resize(stmt) => Self::Resize(stmt.compile(env, dgns)?), + }) + } + } +} + +mod run { + use crate::ir::*; + use crate::run::*; + + impl Run for MetaStmt { + fn run(self: &Self, _state: &mut State, _ctx: &mut Context) -> Result<(), Stop> { + // TODO: we should run meta statements to check they are correct, + // even though they should have no effect on the I/O validation itself. + Ok(()) + } + } +} + +mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for MetaStmt + where + MetaStmtKind: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let Self { kind, .. } = self; + kind.gen(&mut ctx.with_lang(ctx.lang.0)) + } + } + + impl Gen for MetaStmt { + fn gen(&self, ctx: GenContext) -> Result { + let ctx = &mut ctx.with_lang(&CommonMixin(&Inspect)); + gen!(ctx, { + "@{}" % self; + }) + } + } + + impl Gen> for MetaStmtKind + where + SetMetaStmt: Gen, + CallMetaStmt: Gen, + ResizeMetaStmt: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let ctx = &mut ctx.with_lang(ctx.lang.0); + match self { + Self::Set(stmt) => stmt.gen(ctx), + Self::Call(stmt) => stmt.gen(ctx), + Self::Resize(stmt) => stmt.gen(ctx), + } + } + } + + lang_mixin!(Inspect, MetaStmtKind, CommonMixin); +} diff --git a/task-maker-iospec/src/spec/meta_stmt_kind/call.rs b/task-maker-iospec/src/spec/meta_stmt_kind/call.rs new file mode 100644 index 000000000..df608145e --- /dev/null +++ b/task-maker-iospec/src/spec/meta_stmt_kind/call.rs @@ -0,0 +1,500 @@ +pub mod kw { + syn::custom_keyword!(call); +} + +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct CallMetaStmt { + pub kw: kw::call, + pub name: Name, + pub paren: syn::token::Paren, + pub args: syn::punctuated::Punctuated, + pub ret: Option, + pub semi: syn::Token![;], + } + + #[derive(Debug, Clone)] + pub struct CallArg { + pub name: Name, + pub eq: syn::Token![=], + pub kind: CallArgKind, + } + + #[derive(Debug, Clone)] + pub enum CallArgKind { + Value(CallByValueArg), + Reference(CallByReferenceArg), + } + + #[derive(Debug, Clone)] + pub struct CallByValueArg { + pub expr: Expr, + } + + #[derive(Debug, Clone)] + pub struct CallByReferenceArg { + pub amp: syn::Token![&], + pub expr: Expr, + } + + #[derive(Debug, Clone)] + pub struct CallRet { + pub arrow: syn::Token![->], + pub kind: CallRetKind, + } + + #[derive(Debug, Clone)] + pub enum CallRetKind { + Single(SingleCallRet), + Tuple(TupleCallRet), + } + + #[derive(Debug, Clone)] + pub struct SingleCallRet { + pub expr: Expr, + } + + #[derive(Debug, Clone)] + pub struct TupleCallRet { + pub paren: syn::token::Paren, + pub items: syn::punctuated::Punctuated, + } +} + +mod parse { + use syn::parenthesized; + use syn::parse::*; + use syn::punctuated::Punctuated; + use syn::Token; + + use crate::ast::*; + + impl Parse for CallMetaStmt { + fn parse(input: ParseStream) -> Result { + let paren_input; + Ok(Self { + kw: input.parse()?, + name: input.parse()?, + paren: parenthesized!(paren_input in input), + args: Punctuated::parse_terminated(&paren_input)?, + ret: if input.peek(Token![->]) { + Some(input.parse()?) + } else { + None + }, + semi: input.parse()?, + }) + } + } + + impl Parse for CallArg { + fn parse(input: ParseStream) -> Result { + Ok(Self { + name: input.parse()?, + eq: input.parse()?, + kind: input.parse()?, + }) + } + } + + impl Parse for CallArgKind { + fn parse(input: ParseStream) -> Result { + Ok(if input.peek(Token![&]) { + Self::Reference(input.parse()?) + } else { + Self::Value(input.parse()?) + }) + } + } + + impl Parse for CallByValueArg { + fn parse(input: ParseStream) -> Result { + Ok(Self { + expr: input.parse()?, + }) + } + } + + impl Parse for CallByReferenceArg { + fn parse(input: ParseStream) -> Result { + Ok(Self { + amp: input.parse()?, + expr: input.parse()?, + }) + } + } + + impl Parse for CallRet { + fn parse(input: ParseStream) -> Result { + Ok(Self { + arrow: input.parse()?, + kind: input.parse()?, + }) + } + } + + impl Parse for CallRetKind { + fn parse(input: ParseStream) -> Result { + Ok(if input.peek(syn::token::Paren) { + Self::Tuple(input.parse()?) + } else { + Self::Single(input.parse()?) + }) + } + } + + impl Parse for SingleCallRet { + fn parse(input: ParseStream) -> Result { + Ok(Self { + expr: input.parse()?, + }) + } + } + + impl Parse for TupleCallRet { + fn parse(input: ParseStream) -> Result { + let paren_input; + Ok(Self { + paren: parenthesized!(paren_input in input), + items: Punctuated::parse_separated_nonempty(&paren_input)?, + }) + } + } +} + +pub mod ir { + use crate::ast; + use crate::ir::*; + + #[derive(Debug)] + pub struct CallMetaStmt { + pub kw: ast::kw::call, + pub name: Name, + pub paren: syn::token::Paren, + pub arg_commas: Vec, + pub args: Vec>, + pub ret: CallRet, + pub semi: syn::Token![;], + } + + #[derive(Debug)] + pub struct CallRet(pub Option); + + #[derive(Debug)] + pub struct CallArg { + pub name: Ir, + pub eq: syn::Token![=], + pub kind: CallArgKind, + } + + #[derive(Debug)] + pub enum CallArgKind { + Value(CallByValueArg), + Reference(CallByReferenceArg), + } + + #[derive(Debug)] + pub struct CallByValueArg { + pub expr: Expr, + } + + #[derive(Debug)] + pub struct CallByReferenceArg { + pub amp: syn::Token![&], + pub expr: Expr, + } + + #[derive(Debug)] + pub struct CallRetExpr { + pub arrow: syn::Token![->], + pub kind: CallRetKind, + } + + #[derive(Debug)] + pub enum CallRetKind { + Single(SingleCallRet), + Tuple(TupleCallRet), + } + + #[derive(Debug)] + pub struct SingleCallRet { + pub expr: Expr, + } + + #[derive(Debug)] + pub struct TupleCallRet { + pub paren: syn::token::Paren, + pub items: Vec, + pub item_commas: Vec, + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for CallMetaStmt { + fn compile( + ast: &ast::CallMetaStmt, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let ast::CallMetaStmt { + kw, + name, + paren, + args, + ret, + semi, + } = ast; + + let (args, arg_commas) = unzip_punctuated(args.clone()); + + Ok(Self { + kw: kw.clone(), + name: name.compile(env, dgns)?, + paren: paren.clone(), + args: args + .iter() + .map(|a| a.compile(env, dgns)) + .collect::>()?, + arg_commas, + ret: CallRet(ret.as_ref().map(|ret| ret.compile(env, dgns)).transpose()?), + semi: semi.clone(), + }) + } + } + + impl CompileFrom for CallArg { + fn compile(ast: &ast::CallArg, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::CallArg { name, eq, kind } = ast; + + Ok(Self { + name: name.compile(env, dgns)?, + eq: eq.clone(), + kind: kind.compile(env, dgns)?, + }) + } + } + + impl CompileFrom for CallArgKind { + fn compile( + ast: &ast::CallArgKind, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + Ok(match ast { + ast::CallArgKind::Value(arg) => CallArgKind::Value(arg.compile(env, dgns)?), + ast::CallArgKind::Reference(arg) => CallArgKind::Reference(arg.compile(env, dgns)?), + }) + } + } + + impl CompileFrom for CallByValueArg { + fn compile( + ast: &ast::CallByValueArg, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let ast::CallByValueArg { expr } = ast; + Ok(Self { + expr: expr.compile(env, dgns)?, + }) + } + } + + impl CompileFrom for CallByReferenceArg { + fn compile( + ast: &ast::CallByReferenceArg, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let ast::CallByReferenceArg { amp, expr } = ast; + + Ok(Self { + amp: amp.clone(), + expr: expr.compile(env, dgns)?, + }) + } + } + + impl CompileFrom for CallRetExpr { + fn compile(ast: &ast::CallRet, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::CallRet { arrow, kind } = ast; + + Ok(Self { + arrow: arrow.clone(), + kind: kind.compile(env, dgns)?, + }) + } + } + + impl CompileFrom for CallRetKind { + fn compile( + ast: &ast::CallRetKind, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + Ok(match ast { + ast::CallRetKind::Single(ret) => Self::Single(ret.compile(env, dgns)?), + ast::CallRetKind::Tuple(ret) => Self::Tuple(ret.compile(env, dgns)?), + }) + } + } + + impl CompileFrom for SingleCallRet { + fn compile( + ast: &ast::SingleCallRet, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let ast::SingleCallRet { expr } = ast; + + Ok(Self { + expr: expr.compile(env, dgns)?, + }) + } + } + + impl CompileFrom for TupleCallRet { + fn compile( + ast: &ast::TupleCallRet, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let ast::TupleCallRet { items, paren } = ast; + + let (items, item_commas) = unzip_punctuated(items.clone()); + + Ok(Self { + paren: paren.clone(), + items: items + .iter() + .map(|item| item.compile(env, dgns)) + .collect::>()?, + item_commas, + }) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for CallArg + where + CallArgKind: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + self.kind.gen(&mut ctx.with_lang(ctx.lang.0)) + } + } + + impl Gen> for CallArgKind + where + CallByValueArg: Gen, + CallByReferenceArg: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + match self { + CallArgKind::Value(arg) => arg.gen(&mut ctx.with_lang(ctx.lang.0)), + CallArgKind::Reference(arg) => arg.gen(&mut ctx.with_lang(ctx.lang.0)), + } + } + } + + impl Gen> for CallByValueArg + where + Expr: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + self.expr.gen(&mut ctx.with_lang(ctx.lang.0)) + } + } + + impl Gen> for CallRet + where + CallRetExpr: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let ctx = &mut ctx.with_lang(ctx.lang.0); + match self.0.as_ref() { + Some(ret) => gen!(ctx, "{} = " % ret), + None => gen!(ctx), + } + } + } + + impl Gen> for CallRetExpr + where + CallRetKind: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let Self { kind, .. } = self; + let ctx = &mut ctx.with_lang(ctx.lang.0); + kind.gen(ctx) + } + } + + impl Gen> for CallRetKind + where + SingleCallRet: Gen, + TupleCallRet: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let ctx = &mut ctx.with_lang(ctx.lang.0); + match self { + CallRetKind::Single(ret) => ret.gen(ctx), + CallRetKind::Tuple(ret) => ret.gen(ctx), + } + } + } + + impl Gen> for SingleCallRet + where + Expr: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let Self { expr } = self; + expr.gen(&mut ctx.with_lang(ctx.lang.0)) + } + } + + impl Gen> for TupleCallRet + where + Expr: Gen, + { + fn gen(&self, _ctx: GenContext>) -> Result { + todo!("tuple return value not supported yet") + } + } + + pub struct InFunDecl(pub T); + + impl Gen> for CallMetaStmt + where + CallRet: Gen, + Name: Gen, + CallArg: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let Self { + name, args, ret, .. + } = self; + let ctx = &mut ctx.with_lang(ctx.lang.0); + gen!(ctx, { + "{}{}({});" % (ret, name, &Punctuated(args.iter().cloned().collect(), ", ")); + }) + } + } + + impl Gen for CallMetaStmt { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx, "call (<>)") + } + } +} diff --git a/task-maker-iospec/src/spec/meta_stmt_kind/mod.rs b/task-maker-iospec/src/spec/meta_stmt_kind/mod.rs new file mode 100644 index 000000000..066f7a540 --- /dev/null +++ b/task-maker-iospec/src/spec/meta_stmt_kind/mod.rs @@ -0,0 +1,33 @@ +mod call; +mod resize; +mod set; + +pub mod ast { + use super::*; + + pub use call::ast::*; + pub use resize::ast::*; + pub use set::ast::*; +} + +pub mod kw { + pub use super::call::kw::*; + pub use super::resize::kw::*; + pub use super::set::kw::*; +} + +pub mod ir { + use super::*; + + pub use call::ir::*; + pub use resize::ir::*; + pub use set::ir::*; +} + +pub mod gen { + use super::*; + + pub use call::gen::*; + pub use resize::gen::*; + pub use set::gen::*; +} diff --git a/task-maker-iospec/src/spec/meta_stmt_kind/resize.rs b/task-maker-iospec/src/spec/meta_stmt_kind/resize.rs new file mode 100644 index 000000000..c43a733c1 --- /dev/null +++ b/task-maker-iospec/src/spec/meta_stmt_kind/resize.rs @@ -0,0 +1,106 @@ +pub mod kw { + syn::custom_keyword!(resize); + syn::custom_keyword!(to); +} + +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct ResizeMetaStmt { + pub kw: kw::resize, + pub array: Expr, + pub to_kw: kw::to, + pub size: Expr, + pub semi: syn::Token![;], + } +} + +mod parse { + use crate::ast::*; + + use syn::parse::*; + + impl Parse for ResizeMetaStmt { + fn parse(input: ParseStream) -> Result { + Ok(Self { + kw: input.parse()?, + array: input.parse()?, + to_kw: input.parse()?, + size: input.parse()?, + semi: input.parse()?, + }) + } + } +} + +pub mod ir { + use crate::ast; + use crate::ir::*; + + #[derive(Debug)] + pub struct ResizeMetaStmt { + pub kw: ast::kw::resize, + pub array: Ir, + pub item_ty: Option>, + pub to_kw: ast::kw::to, + pub size: Ir, + pub semi: syn::Token![;], + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for ResizeMetaStmt { + fn compile( + ast: &ast::ResizeMetaStmt, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let ast::ResizeMetaStmt { + kw, + array, + to_kw, + size, + semi, + } = ast; + + let array: Ir = array.compile(env, dgns)?; + + let item_ty = match array.ty.as_ref() { + ExprTy::Array { item, .. } => Some(item.clone()), + _ => { + dgns.error( + &format!("expected array type, got {}", quote_hir(&array.ty)), + vec![dgns.error_ann("expected array type", array.span())], + vec![], + ); + None + } + }; + + Ok(Self { + kw: kw.clone(), + array, + item_ty, + to_kw: to_kw.clone(), + size: size.compile(env, dgns)?, + semi: semi.clone(), + }) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen for ResizeMetaStmt { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx, "call (<>)") + } + } +} diff --git a/task-maker-iospec/src/spec/meta_stmt_kind/set.rs b/task-maker-iospec/src/spec/meta_stmt_kind/set.rs new file mode 100644 index 000000000..d30876118 --- /dev/null +++ b/task-maker-iospec/src/spec/meta_stmt_kind/set.rs @@ -0,0 +1,89 @@ +pub mod kw { + syn::custom_keyword!(set); +} + +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct SetMetaStmt { + pub kw: kw::set, + pub lexpr: Expr, + pub eq: syn::Token![=], + pub rexpr: Expr, + pub semi: syn::Token![;], + } +} + +mod parse { + use syn::parse::*; + + use crate::ast::*; + + impl Parse for SetMetaStmt { + fn parse(input: ParseStream) -> Result { + Ok(Self { + kw: input.parse()?, + lexpr: input.parse()?, + eq: input.parse()?, + rexpr: input.parse()?, + semi: input.parse()?, + }) + } + } +} + +pub mod ir { + use crate::ast; + use crate::ir::*; + + #[derive(Debug)] + pub struct SetMetaStmt { + pub kw: ast::kw::set, + pub lexpr: Expr, + pub eq: syn::Token![=], + pub rexpr: Expr, + pub semi: syn::Token![;], + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for SetMetaStmt { + fn compile( + ast: &ast::SetMetaStmt, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let ast::SetMetaStmt { + kw, + lexpr, + eq, + rexpr, + semi, + } = ast; + Ok(Self { + kw: kw.clone(), + lexpr: lexpr.compile(env, dgns)?, + eq: eq.clone(), + rexpr: rexpr.compile(env, dgns)?, + semi: semi.clone(), + }) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen for SetMetaStmt { + fn gen(&self, ctx: GenContext) -> Result { + let Self { lexpr, rexpr, .. } = self; + gen!(ctx, "set {} = {};" % (lexpr, rexpr)) + } + } +} diff --git a/task-maker-iospec/src/spec/mod.rs b/task-maker-iospec/src/spec/mod.rs new file mode 100644 index 000000000..e2b1a0ef5 --- /dev/null +++ b/task-maker-iospec/src/spec/mod.rs @@ -0,0 +1,112 @@ +//! Implementation of each language constructs. + +extern crate syn; + +mod atom_ty; +mod attr; +mod attr_kind; +mod stmt_block; +mod cfg_expr; +mod data_def_expr; +mod data_var; +mod expr; +mod expr_kind; +mod expr_ty; +mod meta_stmt; +mod meta_stmt_kind; +mod name; +mod range; +mod root; +mod stmt; +mod stmt_kind; +mod var; + +pub mod ast { + //! Abstract Syntax Tree (AST), obtained by parsing spec file syntax. + + use super::*; + + pub use attr::ast::*; + pub use attr_kind::ast::*; + pub use stmt_block::ast::*; + pub use cfg_expr::ast::*; + pub use expr::ast::*; + pub use expr_kind::ast::*; + pub use meta_stmt::ast::*; + pub use meta_stmt_kind::ast::*; + pub use name::ast::*; + pub use root::ast::*; + pub use stmt::ast::*; + pub use stmt_kind::ast::*; + + pub mod kw { + //! Custom keywords + + use super::*; + + pub use attr_kind::kw::*; + pub use cfg_expr::kw::*; + pub use meta_stmt_kind::kw::*; + pub use range::kw::*; + pub use stmt_kind::kw::*; + } +} + +pub mod ir { + use super::*; + + pub use atom_ty::ir::*; + pub use attr::ir::*; + pub use attr_kind::ir::*; + pub use stmt_block::ir::*; + pub use data_def_expr::ir::*; + pub use data_var::ir::*; + pub use expr::ir::*; + pub use expr_kind::ir::*; + pub use expr_ty::ir::*; + pub use meta_stmt::ir::*; + pub use meta_stmt_kind::ir::*; + pub use name::ir::*; + pub use range::ir::*; + pub use root::ir::*; + pub use stmt::ir::*; + pub use stmt_kind::ir::*; + pub use var::ir::*; +} + +pub mod sem { + //! Implementation of semantics of some constructs. + + use super::*; + + pub use atom_ty::sem::*; + pub use cfg_expr::sem::*; + pub use expr::sem::*; + pub use stmt_kind::sem::*; +} + +pub mod mem { + //! In-memory representation of data + + use super::*; + + pub use atom_ty::mem::*; + pub use data_def_expr::mem::*; + pub use expr::mem::*; +} + +pub mod gen { + use super::*; + + pub use attr::gen::*; + pub use attr_kind::gen::*; + pub use stmt_block::gen::*; + pub use data_def_expr::gen::*; + pub use expr::gen::*; + pub use expr_kind::gen::*; + pub use expr_ty::gen::*; + pub use meta_stmt_kind::gen::*; + pub use name::gen::*; + pub use stmt::gen::*; + pub use stmt_kind::gen::*; +} diff --git a/task-maker-iospec/src/spec/name.rs b/task-maker-iospec/src/spec/name.rs new file mode 100644 index 000000000..a5bd60ee8 --- /dev/null +++ b/task-maker-iospec/src/spec/name.rs @@ -0,0 +1,66 @@ +pub mod ast { + /// AST of an identifier + + #[derive(Debug, Clone)] + pub struct Name { + pub ident: proc_macro2::Ident, + } +} + +mod parse { + use crate::ast::*; + + use syn::parse::*; + + impl Parse for Name { + fn parse(input: ParseStream) -> Result { + // Parsing TokenTree instead of Indent to ignore Rust keywords + let token_tree: proc_macro2::TokenTree = input.parse()?; + match token_tree { + proc_macro2::TokenTree::Ident(ident) => Ok(Self { ident }), + _ => Err(Error::new(token_tree.span(), "expected identifier")), + } + } + } +} + +pub mod ir { + /// IR of an identifier + pub type Name = crate::ast::Name; +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for Name { + fn compile(ast: &ast::Name, _env: &Env, _dgns: &mut DiagnosticContext) -> Result { + Ok(ast.clone()) + } + } +} + +mod dgns { + use crate::dgns::*; + use crate::ir::*; + + impl HasSpan for Name { + fn span(self: &Self) -> Span { + self.ident.span() + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for Name { + fn gen(&self, ctx: GenContext>) -> Result { + ctx.append(&self.ident) + } + } + + lang_mixin!(Inspect, Name, CommonMixin); +} diff --git a/task-maker-iospec/src/spec/range.rs b/task-maker-iospec/src/spec/range.rs new file mode 100644 index 000000000..7cfede2d7 --- /dev/null +++ b/task-maker-iospec/src/spec/range.rs @@ -0,0 +1,88 @@ +pub mod kw { + syn::custom_keyword!(upto); +} + +pub mod ir { + use crate::ast::kw; + use crate::ir::*; + + /// IR of the range in a `for` statement. + #[derive(Debug)] + pub struct Range { + pub index: Ir, + pub upto: kw::upto, + pub bound: Ir, + } + + /// IR of the range upper bound in a `for` statement. + #[derive(Debug)] + pub struct RangeBound { + pub val: Ir, + pub ty: Ir, + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + use crate::sem; + + impl CompileFrom for RangeBound { + fn compile(ast: &ast::Expr, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let val: Ir = ast.compile(env, dgns)?; + + Ok(match val.ty.as_ref() { + ExprTy::Atom { atom_ty } => { + match &atom_ty.sem { + None | Some(sem::AtomTy::I32 { .. }) => {} + _ => { + dgns.error( + &format!( + "upper bound of a `for` cycle must be a `i32`, got `{}`", + quote_hir(atom_ty.as_ref()), + ), + vec![ + dgns.error_ann("must be a scalar", val.span()), + dgns.info_ann("type defined here", atom_ty.span()), + ], + vec![], + ); + } + } + + RangeBound { + ty: atom_ty.clone(), + val, + } + } + _ => { + dgns.error( + &format!( + "upper bound of a `for` cycle must be a scalar, got `{}`", + quote_hir(val.ty.as_ref()), + ), + vec![dgns.error_ann("must be a scalar", val.span())], + vec![], + ); + + RangeBound { + ty: Default::default(), + val, + } + } + }) + } + } +} + +mod dgns { + use crate::dgns::*; + use crate::ir::*; + + impl HasSpan for Range { + fn span(self: &Self) -> Span { + self.index.span().join(self.bound.val.span()).unwrap() + } + } +} diff --git a/task-maker-iospec/src/spec/root.rs b/task-maker-iospec/src/spec/root.rs new file mode 100644 index 000000000..e8c738583 --- /dev/null +++ b/task-maker-iospec/src/spec/root.rs @@ -0,0 +1,146 @@ +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct Spec { + pub attrs: Vec, + pub block: BlockContent, + } +} + +mod parse { + use crate::ast::*; + + use syn::parse::*; + use syn::Token; + + impl Parse for Spec { + fn parse(input: ParseStream) -> Result { + let mut attrs = Vec::::new(); + + while input.peek(Token![#]) && input.peek2(Token![!]) { + attrs.push(input.parse()?) + } + + Ok(Self { + attrs, + block: input.parse()?, + }) + } + } +} + +pub mod ir { + use crate::ir::*; + + pub struct Spec { + pub attrs: Vec>, + pub main: Ir, + } + + pub struct Template(pub T); +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for Spec { + fn compile(ast: &ast::Spec, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::Spec { attrs, block } = ast; + let attrs = attrs.into_iter().cloned().map(Ir::new).collect(); + let block: InnerBlock = block.compile(env, dgns)?; + let main = OuterBlock::new(block.data_defs.clone(), block); + Ok(Self { + attrs, + main: main.as_ref().compile(env, dgns)?, + }) + } + } +} + +mod run { + use crate::ir::*; + use crate::run::*; + + impl Run for Spec { + fn run(self: &Self, state: &mut State, ctx: &mut Context) -> Result<(), Stop> { + self.main.run(state, ctx)?; + + let token = ctx + .input_source + .next_token() + .map_err(|_| Stop::Error(anyhow::anyhow!("cannot read from input file")))?; + + if !token.is_empty() { + ctx.dgns.error( + &format!( + "expected EOF in input file, got {}", + String::from_utf8_lossy(&token) + ), + vec![], + vec![ctx.dgns.note_footer("reached end of spec")], + ) + } + + if let Some(output_source) = &mut ctx.output_source { + if !output_source.check_eof() { + ctx.dgns.error( + "expected EOF in output file", + vec![], + vec![ctx.dgns.note_footer("reached end of spec")], + ) + } + } + + Ok(()) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen for Spec { + fn gen(&self, ctx: GenContext) -> Result { + let Self { attrs, main, .. } = self; + + gen!(ctx, { + "<>"; + (); + })?; + + for attr in attrs { + gen!(ctx, attr)?; + } + + gen!(ctx, { + (main); + (); + }) + } + } + + impl Gen> for InFunDecl<&Spec> + where + for<'a> InFunDecl<&'a CallMetaStmt>: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let ctx = &mut ctx.with_lang(ctx.lang.0); + + let calls = &self.0.main.inner.calls; + if !calls.is_empty() { + gen!(ctx, { + (); + })?; + for call in calls.iter() { + gen!(ctx, (&InFunDecl(call.as_ref())))?; + } + } + + gen!(ctx) + } + } +} diff --git a/task-maker-iospec/src/spec/stmt.rs b/task-maker-iospec/src/spec/stmt.rs new file mode 100644 index 000000000..2a2471cd1 --- /dev/null +++ b/task-maker-iospec/src/spec/stmt.rs @@ -0,0 +1,281 @@ +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct Stmt { + pub attrs: Vec, + pub kind: StmtKind, + } + + #[derive(Debug, Clone)] + pub enum StmtKind { + Io(IoStmt), + Check(CheckStmt), + Item(ItemStmt), + For(ForStmt), + If(IfStmt), + Block(BlockStmt), + Meta(MetaStmt), + } +} + +mod parse { + use crate::ast::*; + + use syn::parse::*; + + impl Parse for Stmt { + fn parse(input: ParseStream) -> Result { + let mut attrs = Vec::::new(); + + while input.peek(syn::Token![#]) { + attrs.push(input.parse()?) + } + + Ok(Self { + attrs, + kind: input.parse()?, + }) + } + } + + impl Parse for StmtKind { + fn parse(input: ParseStream) -> Result { + use StmtKind::*; + + let la = input.lookahead1(); + + Ok(if la.peek(kw::inputln) || la.peek(kw::outputln) { + Io(input.parse()?) + } else if la.peek(kw::assume) || la.peek(kw::assert) { + Check(input.parse()?) + } else if la.peek(kw::item) { + Item(input.parse()?) + } else if la.peek(syn::Token![for]) { + For(input.parse()?) + } else if la.peek(syn::Token![if]) { + If(input.parse()?) + } else if la.peek(syn::token::Brace) { + Block(input.parse()?) + } else if la.peek(syn::Token![@]) { + Meta(input.parse()?) + } else { + Err(la.error())? + }) + } + } +} + +pub mod ir { + use crate::ir::*; + + /// IR of a statement. + /// Type parameter `T` depends on the phase of compilation. + #[derive(Debug)] + pub struct Stmt> { + pub attrs: Vec>, + pub kind: Ir>, + /// Data expressions defined inside this statement + pub data_defs: Vec>, + pub calls: Vec>, + pub allocs: Vec, + } + + #[derive(Debug)] + pub enum StmtKind> { + Io(Ir>), + Item(Ir>), + Check(Ir), + For(Ir>), + If(Ir>), + Block(Ir>), + Meta(Ir>), + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for StmtKind { + fn compile(ast: &ast::StmtKind, env: &Env, dgns: &mut DiagnosticContext) -> Result { + Ok(match ast { + ast::StmtKind::Io(stmt) => Self::Io(stmt.compile(env, dgns)?), + ast::StmtKind::Item(stmt) => Self::Item(stmt.compile(env, dgns)?), + ast::StmtKind::Check(stmt) => Self::Check(stmt.compile(env, dgns)?), + ast::StmtKind::For(stmt) => Self::For(stmt.compile(env, dgns)?), + ast::StmtKind::If(stmt) => Self::If(stmt.compile(env, dgns)?), + ast::StmtKind::Block(stmt) => Self::Block(stmt.compile(env, dgns)?), + ast::StmtKind::Meta(stmt) => Self::Meta(stmt.compile(env, dgns)?), + }) + } + } + + impl CompileFrom> for StmtKind { + fn compile( + input: &StmtKind, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + Ok(match input { + StmtKind::Io(stmt) => Self::Io(stmt.as_ref().compile(env, dgns)?), + StmtKind::Item(stmt) => Self::Item(stmt.as_ref().compile(env, dgns)?), + StmtKind::Check(stmt) => Self::Check(stmt.clone()), + StmtKind::For(stmt) => Self::For(stmt.as_ref().compile(env, dgns)?), + StmtKind::If(stmt) => Self::If(stmt.as_ref().compile(env, dgns)?), + StmtKind::Block(stmt) => Self::Block(stmt.as_ref().compile(env, dgns)?), + StmtKind::Meta(stmt) => Self::Meta(stmt.as_ref().compile(env, dgns)?), + }) + } + } + + impl CompileFrom for Stmt { + fn compile(ast: &ast::Stmt, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::Stmt { attrs, kind } = ast; + + let kind: Ir> = kind.compile(env, dgns)?; + + let data_defs = match kind.as_ref() { + StmtKind::For(step) => step.data_defs.clone(), + StmtKind::If(step) => step.body.data_defs.clone(), + StmtKind::Io(step) => step.data_defs.clone(), + StmtKind::Item(step) => vec![step.expr.clone()], + _ => Vec::new(), + }; + + let mut env = env.clone(); + + for expr in data_defs.iter() { + env.declare_expr(expr, dgns) + } + + Ok(Stmt { + attrs: attrs.iter().cloned().map(Ir::new).collect(), + data_defs, + calls: vec![], + allocs: match kind.as_ref() { + StmtKind::For(step) => step.allocs.clone(), + _ => Vec::new(), + }, + kind, + }) + } + } + + impl CompileFrom> for Stmt { + fn compile( + input: &Stmt, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let Stmt { + attrs, + kind, + data_defs, + calls: _, + allocs, + } = input; + + let kind: Ir = kind.as_ref().compile(env, dgns)?; + + let calls = match kind.as_ref() { + StmtKind::For(stmt) => stmt.body.inner.calls.clone(), + StmtKind::If(stmt) => stmt.body.calls.clone(), + StmtKind::Io(stmt) => stmt.body.calls.clone(), + StmtKind::Meta(stmt) => match stmt.kind.as_ref() { + MetaStmtKind::Call(stmt) => vec![stmt.clone()], + _ => vec![], + }, + _ => Vec::new(), + }; + + Ok(Self { + attrs: attrs.clone(), + kind, + data_defs: data_defs.clone(), + calls: calls.clone(), + allocs: allocs.clone(), + }) + } + } +} + +mod run { + use crate::ir::*; + use crate::run::*; + + impl Run for Stmt { + fn run(self: &Self, state: &mut State, ctx: &mut Context) -> Result<(), Stop> { + match self.kind.as_ref() { + StmtKind::Io(stmt) => stmt.run(state, ctx)?, + StmtKind::Item(stmt) => stmt.run(state, ctx)?, + StmtKind::Check(stmt) => stmt.run(state, ctx)?, + StmtKind::For(stmt) => stmt.run(state, ctx)?, + StmtKind::If(stmt) => stmt.run(state, ctx)?, + StmtKind::Block(stmt) => stmt.run(state, ctx)?, + StmtKind::Meta(stmt) => stmt.run(state, ctx)?, + } + Ok(()) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for Stmt + where + StmtAttr: Gen, + DataExprAlloc: Gen, + StmtKind: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let Self { + allocs, + attrs, + kind, + .. + } = self; + let ctx = &mut ctx.with_lang(ctx.lang.0); + + for attr in attrs.iter() { + gen!(ctx, attr)? + } + + for alloc in allocs.iter() { + gen!(ctx, alloc)? + } + + gen!(ctx, kind) + } + } + + lang_mixin!(Inspect, Stmt, CommonMixin); + + impl Gen> for StmtKind + where + IoStmt: Gen, + ItemStmt: Gen, + CheckStmt: Gen, + ForStmt: Gen, + IfStmt: Gen, + BlockStmt: Gen, + MetaStmt: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + match self { + Self::Io(stmt) => stmt.gen(&mut ctx.with_lang(ctx.lang.0)), + Self::Item(stmt) => stmt.gen(&mut ctx.with_lang(ctx.lang.0)), + Self::Check(stmt) => stmt.gen(&mut ctx.with_lang(ctx.lang.0)), + Self::For(stmt) => stmt.gen(&mut ctx.with_lang(ctx.lang.0)), + Self::If(stmt) => stmt.gen(&mut ctx.with_lang(ctx.lang.0)), + Self::Block(stmt) => stmt.gen(&mut ctx.with_lang(ctx.lang.0)), + Self::Meta(stmt) => stmt.gen(&mut ctx.with_lang(ctx.lang.0)), + } + } + } + + lang_mixin!(Inspect, StmtKind, CommonMixin); +} diff --git a/task-maker-iospec/src/spec/stmt_block.rs b/task-maker-iospec/src/spec/stmt_block.rs new file mode 100644 index 000000000..0481dc8ba --- /dev/null +++ b/task-maker-iospec/src/spec/stmt_block.rs @@ -0,0 +1,258 @@ +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct BracedBlock { + pub brace: syn::token::Brace, + pub content: BlockContent, + } + + #[derive(Debug, Clone)] + pub struct BlockContent { + pub stmts: Vec, + } +} + +mod parse { + use crate::ast; + + use syn::parse::*; + + impl Parse for ast::BracedBlock { + fn parse(input: ParseStream) -> Result { + let content; + Ok(Self { + brace: syn::braced!(content in input), + content: content.parse()?, + }) + } + } + + impl Parse for ast::BlockContent { + fn parse(input: ParseStream) -> Result { + let mut stmts = vec![]; + while !input.is_empty() { + stmts.push(input.parse()?); + } + Ok(Self { stmts }) + } + } +} + +pub mod ir { + use crate::ir::*; + + #[derive(Debug)] + pub struct OuterBlock> { + pub inner: Ir>, + pub data_defs: Vec>, + pub decls: Vec>, + } + + #[derive(Debug)] + pub struct InnerBlock> { + pub stmts: Vec>>, + pub data_defs: Vec>, + pub calls: Vec>, + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl OuterBlock { + pub fn new(data_defs: Vec>, block: InnerBlock) -> Ir { + Ir::new(Self { + decls: data_defs + .iter() + .filter_map(|expr| expr.var.as_ref()) + .cloned() + .collect(), + data_defs, + inner: Ir::new(block), + }) + } + } + + impl CompileFrom for InnerBlock { + fn compile( + ast: &ast::BlockContent, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + // Compile non-meta statements first, as they can change the environment + let mut stmts = Vec::new(); + let env = &mut env.clone(); + + for stmt in ast.stmts.iter() { + let has_cfg_false = stmt + .attrs + .iter() + .filter_map(|attr| match &attr.kind { + ast::StmtAttrKind::Cfg(attr) => Some(attr), + _ => None, + }) + .map(|attr| -> Result { (&attr.expr).compile(env, dgns) }) + .find_map(|attr| match attr { + Ok(true) => None, + Ok(false) => Some(Ok(())), + Err(err) => Some(Err(err)), + }) + .transpose()? + .is_some(); + + if has_cfg_false { + continue; + } + + let stmt: Ir> = stmt.compile(env, dgns)?; + for expr in stmt.data_defs.iter() { + env.declare_expr(expr, dgns) + } + stmts.push(stmt) + } + + Ok(Self { + data_defs: stmts + .iter() + .flat_map(|s| s.data_defs.iter()) + .cloned() + .collect(), + calls: vec![], + stmts, + }) + } + } + + impl CompileFrom> for InnerBlock { + fn compile( + input: &InnerBlock, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let InnerBlock { + calls: _, + data_defs, + stmts, + } = input; + + let stmts: Vec> = stmts + .iter() + .map(|stmt| stmt.as_ref().compile(env, dgns)) + .collect::>()?; + + Ok(Self { + calls: stmts.iter().flat_map(|s| s.calls.iter()).cloned().collect(), + data_defs: data_defs.clone(), + stmts, + }) + } + } + + impl CompileFrom> for OuterBlock { + fn compile( + input: &OuterBlock, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let OuterBlock { + inner: block, + decls, + data_defs, + } = input; + + let env = &mut env.clone(); + + for expr in data_defs.iter() { + env.declare_expr(expr, dgns) + } + + Ok(Self { + inner: block.as_ref().compile(env, dgns)?, + decls: decls.clone(), + data_defs: data_defs.clone(), + }) + } + } +} + +mod run { + use crate::ir::*; + use crate::run::*; + + impl Run for OuterBlock { + fn run(self: &Self, state: &mut State, ctx: &mut Context) -> Result<(), Stop> { + self.inner.run(state, ctx) + } + } + + impl Run for InnerBlock { + fn run(self: &Self, state: &mut State, ctx: &mut Context) -> Result<(), Stop> { + for step in self.stmts.iter() { + step.run(state, ctx)?; + } + Ok(()) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for OuterBlock + where + InnerBlock: Gen, + DataVar: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let Self { + inner: block, + decls, + .. + } = self; + let ctx = &mut ctx.with_lang(ctx.lang.0); + + if !decls.is_empty() { + for decl in decls.iter() { + gen!(ctx, decl)? + } + gen!(ctx, { + (); + })? + } + + gen!(ctx, block) + } + } + + impl Gen for OuterBlock { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx, { + "<>"; + (); + })?; + + let mixin = CommonMixin(&Inspect); + let ctx = &mut ctx.with_lang(&mixin); + gen!(ctx, { self }) + } + } + + impl Gen> for InnerBlock + where + Stmt: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let ctx = &mut ctx.with_lang(ctx.lang.0); + for stmt in self.stmts.iter() { + gen!(ctx, stmt)? + } + gen!(ctx) + } + } + + lang_mixin!(Inspect, InnerBlock, CommonMixin); +} diff --git a/task-maker-iospec/src/spec/stmt_kind/block.rs b/task-maker-iospec/src/spec/stmt_kind/block.rs new file mode 100644 index 000000000..921c9bf34 --- /dev/null +++ b/task-maker-iospec/src/spec/stmt_kind/block.rs @@ -0,0 +1,104 @@ +//! Groups zero or more statements + +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct BlockStmt { + pub body: BracedBlock, + } +} + +mod parse { + use crate::ast; + use syn::parse::*; + + impl Parse for ast::BlockStmt { + fn parse(input: ParseStream) -> Result { + Ok(Self { + body: input.parse()?, + }) + } + } +} + +pub mod ir { + use crate::ir::*; + + #[derive(Debug)] + pub struct BlockStmt> { + pub brace: syn::token::Brace, + pub block: InnerBlock, + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for BlockStmt { + fn compile(ast: &ast::BlockStmt, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::BlockStmt { body } = ast; + let ast::BracedBlock { + brace, + content: block, + } = body; + + Ok(Self { + brace: brace.clone(), + block: block.compile(env, dgns)?, + }) + } + } + + impl CompileFrom> for BlockStmt { + fn compile( + input: &BlockStmt, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let BlockStmt { brace, block } = input; + + Ok(Self { + brace: brace.clone(), + block: block.compile(env, dgns)?, + }) + } + } +} + +mod run { + use crate::ir::*; + use crate::run::*; + + impl Run for BlockStmt { + fn run(self: &Self, state: &mut State, ctx: &mut Context) -> Result<(), Stop> { + self.block.run(state, ctx) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen> for BlockStmt + where + InnerBlock: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let Self { block, .. } = self; + gen!(&mut ctx.with_lang(ctx.lang.0), block) + } + } + + impl Gen for BlockStmt { + fn gen(&self, ctx: GenContext) -> Result { + let Self { block, .. } = self; + gen!(ctx, { + ({ block }); + }) + } + } +} diff --git a/task-maker-iospec/src/spec/stmt_kind/check.rs b/task-maker-iospec/src/spec/stmt_kind/check.rs new file mode 100644 index 000000000..4000d48c1 --- /dev/null +++ b/task-maker-iospec/src/spec/stmt_kind/check.rs @@ -0,0 +1,131 @@ +pub mod kw { + syn::custom_keyword!(assume); + syn::custom_keyword!(assert); +} + +pub mod ast { + use super::kw; + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct CheckStmt { + pub kw: CheckKw, + pub cond: Expr, + pub semi: syn::Token![;], + } + + /// AST or either `assume` or `assert`. + #[derive(Debug, Clone)] + pub enum CheckKw { + Assume(kw::assume), + Assert(kw::assert), + } +} + +mod parse { + use crate::ast; + use syn::parse::*; + + impl Parse for ast::CheckStmt { + fn parse(input: ParseStream) -> Result { + Ok(Self { + kw: input.parse()?, + cond: input.parse()?, + semi: input.parse()?, + }) + } + } + + impl Parse for ast::CheckKw { + fn parse(input: ParseStream) -> Result { + let la = input.lookahead1(); + + Ok(if la.peek(ast::kw::assume) { + Self::Assume(input.parse()?) + } else if la.peek(ast::kw::assert) { + Self::Assert(input.parse()?) + } else { + unreachable!() + }) + } + } +} + +pub mod ir { + use crate::ast; + use crate::ir::*; + use crate::sem; + + pub type CheckKw = super::ast::CheckKw; + + #[derive(Debug)] + pub struct CheckStmt { + pub kw: CheckKw, + pub cond: Ir, + pub semi: syn::Token![;], + } + + impl CheckKw { + pub fn to_stream(&self) -> sem::Stream { + match self { + ast::CheckKw::Assume(_) => sem::Stream::Input, + ast::CheckKw::Assert(_) => sem::Stream::Output, + } + } + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for CheckStmt { + fn compile(ast: &ast::CheckStmt, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::CheckStmt { kw, cond, semi } = ast; + + Ok(Self { + kw: kw.clone(), + cond: cond.compile(env, dgns)?, + semi: semi.clone(), + }) + } + } +} + +mod run { + use crate::dgns::HasSpan; + use crate::ir::*; + use crate::run::*; + + impl Run for CheckStmt { + fn run(self: &Self, state: &mut State, ctx: &mut Context) -> Result<(), Stop> { + let res = self.cond.eval(state, ctx)?.unwrap_value_i64(); + if res == 0 { + ctx.dgns.error( + "assumption violated", + vec![ctx.dgns.error_ann("condition is false", self.cond.span())], + vec![], + ); + return Err(Stop::Done); + } + Ok(()) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen for CheckStmt { + fn gen(&self, ctx: GenContext) -> Result { + let Self { cond, .. } = self; + + // TODO: assume/assert + gen!(ctx, { + "check {};" % cond; + }) + } + } +} diff --git a/task-maker-iospec/src/spec/stmt_kind/for_loop.rs b/task-maker-iospec/src/spec/stmt_kind/for_loop.rs new file mode 100644 index 000000000..9e6c54b43 --- /dev/null +++ b/task-maker-iospec/src/spec/stmt_kind/for_loop.rs @@ -0,0 +1,192 @@ +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct ForStmt { + pub kw: syn::Token![for], + pub index: Name, + pub upto: kw::upto, + pub bound: Expr, + pub body: BracedBlock, + } +} + +mod parse { + use crate::ast; + use syn::parse::*; + + impl Parse for ast::ForStmt { + fn parse(input: ParseStream) -> Result { + Ok(Self { + kw: input.parse()?, + index: input.parse()?, + upto: input.parse()?, + bound: input.parse()?, + body: input.parse()?, + }) + } + } +} + +pub mod ir { + use crate::ir::*; + + #[derive(Debug)] + pub struct ForStmt> { + pub kw: syn::token::For, + pub range: Ir, + pub body: Ir>, + pub data_defs: Vec>, + pub allocs: Vec, + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for ForStmt { + fn compile(ast: &ast::ForStmt, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::ForStmt { + kw, + index, + upto, + bound, + body, + } = ast; + + let range = Ir::new(Range { + index: index.compile(env, dgns)?, + upto: upto.clone(), + bound: bound.compile(env, dgns)?, + }); + + let block: InnerBlock = + (&body.content).compile(&env.for_body(range.clone()), dgns)?; + + let inner_decl_exprs: Vec> = block + .data_defs + .iter() + .filter_map(|expr| match &expr.kind { + DataDefExprKind::Var { .. } => Some(expr.clone()), + _ => None, + }) + .collect(); + + let outer_decl_exprs: Vec> = block + .data_defs + .iter() + .filter_map(|node| match &node.kind { + DataDefExprKind::Subscript { array, .. } => Some(array.clone()), + _ => None, + }) + .collect(); + + let allocs: Vec<_> = outer_decl_exprs + .iter() + .flat_map(|expr| { + expr.alloc.as_ref().map(|alloc| DataExprAlloc { + info: alloc.clone(), + expr: expr.clone(), + }) + }) + .collect(); + + Ok(Self { + kw: kw.clone(), + range, + body: OuterBlock::new(inner_decl_exprs, block), + data_defs: outer_decl_exprs, + allocs, + }) + } + } + + impl CompileFrom> for ForStmt { + fn compile( + input: &ForStmt, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let ForStmt { + kw, + range, + body, + data_defs, + allocs, + } = input; + Ok(Self { + kw: kw.clone(), + range: range.clone(), + body: body.as_ref().compile(&env.for_body(range.clone()), dgns)?, + data_defs: data_defs.clone(), + allocs: allocs.clone(), + }) + } + } +} + +mod run { + use crate::ir::*; + use crate::mem::*; + use crate::run::*; + + impl Run for ForStmt { + fn run(self: &Self, state: &mut State, ctx: &mut Context) -> Result<(), Stop> { + let bound = self.range.bound.val.eval(state, ctx)?; + let bound = match bound { + ExprVal::Atom(bound) => bound.value_i64() as usize, + ExprVal::Array(_) => unreachable!(), + }; + + for node in self.data_defs.iter() { + if let Some(var) = &node.var { + state.decl(&var); + } + + if let Some(_) = &node.alloc { + node.eval_mut(state, ctx)?.alloc(node, bound); + } + } + + for i in 0..bound { + state.indexes.insert(self.range.clone().into(), i); + self.body.run(state, ctx)?; + state.indexes.remove(&self.range.clone().into()); + } + + Ok(()) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen for ForStmt { + fn gen(&self, ctx: GenContext) -> Result { + let Self { range, body, .. } = self; + + gen!(ctx, { + "for {}:" % range; + ({ body }); + }) + } + } + + impl Gen for Range { + fn gen(&self, ctx: GenContext) -> Result { + let Self { index, bound, .. } = self; + gen!(ctx, "{} upto {}" % (index, bound)) + } + } + + impl Gen for RangeBound { + fn gen(&self, ctx: GenContext) -> Result { + let Self { val, ty, .. } = self; + gen!(ctx, "{} <{}>" % (val, ty)) + } + } +} diff --git a/task-maker-iospec/src/spec/stmt_kind/if_stmt.rs b/task-maker-iospec/src/spec/stmt_kind/if_stmt.rs new file mode 100644 index 000000000..fd1e1d8d5 --- /dev/null +++ b/task-maker-iospec/src/spec/stmt_kind/if_stmt.rs @@ -0,0 +1,104 @@ +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct IfStmt { + pub kw: syn::Token![if], + pub cond: Expr, + pub body: BracedBlock, + } +} + +mod parse { + use crate::ast::*; + + use syn::parse::*; + + impl Parse for IfStmt { + fn parse(input: ParseStream) -> Result { + Ok(Self { + kw: input.parse()?, + cond: input.parse()?, + body: input.parse()?, + }) + } + } +} + +pub mod ir { + use crate::ir::*; + + #[derive(Debug)] + pub struct IfStmt> { + pub kw: syn::token::If, + pub cond: Ir, + pub body: Ir>, + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for IfStmt { + fn compile(ast: &ast::IfStmt, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::IfStmt { kw, cond, body } = ast; + + Ok(Self { + kw: kw.clone(), + cond: cond.compile(env, dgns)?, + body: (&body.content).compile(env, dgns)?, + }) + } + } + + impl CompileFrom> for IfStmt { + fn compile( + input: &IfStmt, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let IfStmt { kw, cond, body } = input; + + Ok(Self { + kw: kw.clone(), + cond: cond.clone(), + body: body.as_ref().compile(env, dgns)?, + }) + } + } +} + +mod run { + use crate::ir::*; + use crate::run::*; + + impl Run for IfStmt { + fn run(self: &Self, state: &mut State, ctx: &mut Context) -> Result<(), Stop> { + let cond = self.cond.eval(state, ctx)?.unwrap_value_i64(); + + if cond != 0 { + self.body.run(state, ctx)? + } + + Ok(()) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + + impl Gen for IfStmt { + fn gen(&self, ctx: GenContext) -> Result { + let Self { cond, body, .. } = self; + + gen!(ctx, { + "if {}:" % cond; + ({ body }); + }) + } + } +} diff --git a/task-maker-iospec/src/spec/stmt_kind/io.rs b/task-maker-iospec/src/spec/stmt_kind/io.rs new file mode 100644 index 000000000..ab0b750d8 --- /dev/null +++ b/task-maker-iospec/src/spec/stmt_kind/io.rs @@ -0,0 +1,231 @@ +pub mod kw { + syn::custom_keyword!(inputln); + syn::custom_keyword!(outputln); +} + +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct IoStmt { + pub kw: IoKw, + pub body: BracedBlock, + } + + /// AST or either `inputln` or `outputln`. + + #[derive(Debug, Clone)] + pub enum IoKw { + Input(kw::inputln), + Output(kw::outputln), + } +} + +mod parse { + use crate::ast; + + use syn::parse::*; + + impl Parse for ast::IoStmt { + fn parse(input: ParseStream) -> Result { + Ok(Self { + kw: input.parse()?, + body: input.parse()?, + }) + } + } + + impl Parse for ast::IoKw { + fn parse(input: ParseStream) -> Result { + let la = input.lookahead1(); + + Ok(if la.peek(ast::kw::inputln) { + Self::Input(input.parse()?) + } else if la.peek(ast::kw::outputln) { + Self::Output(input.parse()?) + } else { + unreachable!() + }) + } + } +} + +pub mod ir { + use crate::ir::*; + use crate::sem; + + pub type IoKw = super::ast::IoKw; + + #[derive(Debug)] + pub struct IoStmt> { + pub kw: Ir, + pub data_defs: Vec>, + pub stream: sem::Stream, + pub body: Ir>, + } + + impl IoKw { + pub fn to_stream(&self) -> sem::Stream { + match self { + super::ast::IoKw::Input(_) => sem::Stream::Input, + super::ast::IoKw::Output(_) => sem::Stream::Output, + } + } + } +} + +mod compile { + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for IoStmt { + fn compile(ast: &ast::IoStmt, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::IoStmt { kw, body } = ast; + let kw = Ir::new(kw.clone()); + + let stream = kw.to_stream(); + + if let Some(other_io) = &env.cur_io { + dgns.error( + "nested I/O statements", + vec![ + dgns.error_ann("nested I/O statement", kw.span()), + dgns.info_ann("inside this I/O statement", other_io.span()), + ], + vec![dgns.note_footer( + "I/O statements correspond to I/O lines and cannot be nested", + )], + ) + } + + let block: Ir> = + (&body.content).compile(&env.io(&kw), dgns)?; + + Ok(Self { + kw, + data_defs: block.data_defs.clone(), + body: block, + stream: stream, + }) + } + } + + impl CompileFrom> for IoStmt { + fn compile( + input: &IoStmt, + env: &Env, + dgns: &mut DiagnosticContext, + ) -> Result { + let IoStmt { + kw, + data_defs, + stream, + body: block, + } = input; + Ok(Self { + kw: kw.clone(), + data_defs: data_defs.clone(), + stream: stream.clone(), + body: block.as_ref().compile(env, dgns)?, + }) + } + } +} + +mod dgns { + use syn::spanned::Spanned; + + use crate::ast; + use crate::dgns::*; + use crate::ir::*; + + impl HasSpan for IoKw { + fn span(self: &Self) -> Span { + match self { + ast::IoKw::Input(kw) => kw.span(), + ast::IoKw::Output(kw) => kw.span(), + } + } + } +} + +pub mod sem { + #[derive(Debug, Clone, Copy)] + pub enum Stream { + Input, + Output, + } +} + +mod run { + use crate::ir::*; + use crate::run::*; + + impl Run for IoStmt { + fn run(self: &Self, state: &mut State, ctx: &mut Context) -> Result<(), Stop> { + self.body.run(state, ctx) + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + use crate::sem; + + pub struct Endl; + + struct InStream(pub sem::Stream, pub T); + pub struct InInput(pub T); + pub struct InOutput(pub T); + + impl Gen> for IoStmt + where + InnerBlock: Gen, + for<'a> InStream<&'a Endl>: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + let Self { body: block, .. } = self; + let ctx = &mut ctx.with_lang(ctx.lang.0); + gen!(ctx, { (block, &InStream(self.stream, &Endl)) }) + } + } + + impl Gen for InStream<&T> + where + for<'a> InInput<&'a T>: Gen, + for<'a> InOutput<&'a T>: Gen, + { + fn gen(&self, ctx: GenContext) -> Result { + match self.0 { + sem::Stream::Input => InInput(self.1).gen(ctx), + sem::Stream::Output => InOutput(self.1).gen(ctx), + } + } + } + + impl Gen for IoStmt { + fn gen(&self, ctx: GenContext) -> Result { + // TODO: input/output + let mixin = CommonMixin(ctx.lang); + let ctx = &mut ctx.with_lang(&mixin); + gen!(ctx, { + "ioln:"; + ({ self }); + }) + } + } + + impl Gen for InInput<&Endl> { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx) + } + } + + impl Gen for InOutput<&Endl> { + fn gen(&self, ctx: GenContext) -> Result { + gen!(ctx) + } + } +} diff --git a/task-maker-iospec/src/spec/stmt_kind/item.rs b/task-maker-iospec/src/spec/stmt_kind/item.rs new file mode 100644 index 000000000..33c836996 --- /dev/null +++ b/task-maker-iospec/src/spec/stmt_kind/item.rs @@ -0,0 +1,247 @@ +pub mod kw { + syn::custom_keyword!(item); +} + +pub mod ast { + use crate::ast::*; + + #[derive(Debug, Clone)] + pub struct ItemStmt { + pub kw: crate::ast::kw::item, + pub expr: Expr, + pub colon: syn::Token![:], + pub ty: Name, + pub semi: syn::Token![;], + } +} + +mod parse { + use crate::ast::*; + + use syn::parse::*; + + impl Parse for ItemStmt { + fn parse(input: ParseStream) -> Result { + Ok(Self { + kw: input.parse()?, + expr: input.parse()?, + colon: input.parse()?, + ty: input.parse()?, + semi: input.parse()?, + }) + } + } +} + +pub mod ir { + use std::marker::PhantomData; + + use crate::ir::*; + use crate::sem; + + #[derive(Debug)] + pub struct ItemStmt> { + pub kw: super::kw::item, + pub colon: syn::token::Colon, + pub expr: Ir, + pub ty: Ir, + pub io: Option>, + pub stream: Option, + pub semi: syn::token::Semi, + pub phase: PhantomData, + } +} + +mod compile { + use std::marker::PhantomData; + + use syn::spanned::Spanned; + + use crate::ast; + use crate::compile::*; + use crate::ir::*; + + impl CompileFrom for ItemStmt { + fn compile(ast: &ast::ItemStmt, env: &Env, dgns: &mut DiagnosticContext) -> Result { + let ast::ItemStmt { + kw, + expr, + colon, + ty, + semi, + } = ast; + + if env.cur_io.is_none() { + dgns.error( + "`item` statement outside I/O block", + vec![dgns.error_ann("must be inside an I/O block", kw.span())], + vec![dgns.note_footer( + "`item` statements must occur inside a `inputln` or `outputln` block.", + )], + ) + } + + let ty: Ir = ty.compile(env, dgns)?; + + Ok(Self { + kw: kw.clone(), + colon: colon.clone(), + expr: expr.compile(&env.data_env(&ty), dgns)?, + ty, + io: env.cur_io.clone(), + stream: env.cur_io.as_ref().map(|io| io.to_stream()), + semi: semi.clone(), + phase: PhantomData, + }) + } + } + + impl CompileFrom> for ItemStmt { + fn compile( + input: &ItemStmt, + _env: &Env, + _dgns: &mut DiagnosticContext, + ) -> Result { + let ItemStmt { + kw, + colon, + expr, + ty, + io, + stream, + semi, + phase: _, + } = input; + + Ok(Self { + kw: kw.clone(), + colon: colon.clone(), + expr: expr.clone(), + ty: ty.clone(), + io: io.clone(), + stream: stream.clone(), + semi: semi.clone(), + phase: PhantomData, + }) + } + } +} + +mod run { + use crate::dgns::*; + use crate::ir::*; + use crate::mem::*; + use crate::run::*; + use crate::sem; + + impl Run for ItemStmt { + fn run(self: &Self, state: &mut State, ctx: &mut Context) -> Result<(), Stop> { + if let Some(var) = &self.expr.var { + state.decl(var); + } + + let io_source = match self.io.as_ref().unwrap().as_ref() { + IoKw::Input(_) => Ok(&mut ctx.input_source), + IoKw::Output(_) => (&mut ctx.output_source).as_mut().ok_or_else(|| { + if !ctx.input_source.check_eof() { + ctx.dgns.error( + "expected EOF", + vec![ctx + .dgns + .info_ann("reached output data here", self.try_span().unwrap())], + vec![ctx.dgns.note_footer( + "expecting output data at this point, so no more input can be checked", + )], + ); + } + Stop::Done + }), + }?; + + let val = io_source + .next_atom(&self.ty.sem.unwrap()) + .map_err(|e| { + ctx.dgns.error( + &format!("invalid literal: `{}`", &e.to_string()), + vec![ctx + .dgns + .info_ann("when reading this", self.try_span().unwrap())], + vec![], + ); + anyhow::anyhow!("invalid I/O file") + })? + .ok_or_else(|| { + ctx.dgns.error( + "premature EOF", + vec![ctx + .dgns + .info_ann("when reading this", self.try_span().unwrap())], + vec![], + ); + anyhow::anyhow!("invalid I/O file") + })?; + + let val = sem::AtomVal::try_new(self.ty.sem.unwrap(), val).map_err(|_| { + ctx.dgns.error( + "invalid atomic value", + vec![ctx + .dgns + .info_ann("when reading this", self.try_span().unwrap())], + vec![], + ); + anyhow::anyhow!("invalid I/O file") + })?; + + let atom = self.expr.eval_mut(state, ctx)?; + + match atom { + ExprValMut::Atom(atom) => atom.set(val), + _ => unreachable!(), + } + + Ok(()) + } + } +} + +mod dgns { + use crate::dgns::*; + use crate::ir::*; + + impl TryHasSpan for ItemStmt { + fn try_span(self: &Self) -> Option { + self.expr.try_span() + } + } +} + +pub mod gen { + use crate::gen::*; + use crate::ir::*; + use crate::sem; + + impl Gen> for ItemStmt + where + for<'a> InInput<&'a ItemStmt>: Gen, + for<'a> InOutput<&'a ItemStmt>: Gen, + { + fn gen(&self, ctx: GenContext>) -> Result { + match self.stream { + Some(sem::Stream::Input) => InInput(self).gen(&mut ctx.with_lang(ctx.lang.0)), + Some(sem::Stream::Output) => InOutput(self).gen(&mut ctx.with_lang(ctx.lang.0)), + None => gen!(ctx, { + "<>"; + }), + } + } + } + + impl Gen for ItemStmt { + fn gen(&self, ctx: GenContext) -> Result { + let Self { expr, ty, .. } = self; + gen!(ctx, { + "item {}: {};" % (expr, ty); + }) + } + } +} diff --git a/task-maker-iospec/src/spec/stmt_kind/mod.rs b/task-maker-iospec/src/spec/stmt_kind/mod.rs new file mode 100644 index 000000000..0205383bd --- /dev/null +++ b/task-maker-iospec/src/spec/stmt_kind/mod.rs @@ -0,0 +1,55 @@ +pub use super::*; + +mod block; +mod check; +mod for_loop; +mod if_stmt; +mod io; +mod item; + +pub mod kw { + use super::*; + + pub use super::item::kw::item; + pub use check::kw::*; + pub use io::kw::*; +} + +pub mod ast { + use super::*; + + pub use block::ast::*; + pub use check::ast::*; + pub use for_loop::ast::*; + pub use if_stmt::ast::*; + pub use io::ast::*; + pub use item::ast::*; +} + +pub mod ir { + use super::*; + + pub use block::ir::*; + pub use check::ir::*; + pub use for_loop::ir::*; + pub use if_stmt::ir::*; + pub use io::ir::*; + pub use item::ir::*; +} + +pub mod sem { + use super::*; + + pub use io::sem::*; +} + +pub mod gen { + use super::*; + + pub use block::gen::*; + pub use check::gen::*; + pub use for_loop::gen::*; + pub use if_stmt::gen::*; + pub use io::gen::*; + pub use item::gen::*; +} diff --git a/task-maker-iospec/src/spec/var.rs b/task-maker-iospec/src/spec/var.rs new file mode 100644 index 000000000..6cf3ec98c --- /dev/null +++ b/task-maker-iospec/src/spec/var.rs @@ -0,0 +1,50 @@ +pub mod ir { + use crate::ir::*; + + #[derive(Clone, Debug)] + pub struct Var { + pub name: Ir, + pub ty: Ir, + pub kind: VarKind, + } + + #[derive(Clone, Debug)] + pub enum VarKind { + Data { def: Ir }, + Index { range: Ir }, + Err, + } +} + +mod run { + use crate::ir::*; + use crate::mem::*; + use crate::run::*; + use crate::sem; + + impl Eval for Var { + fn eval<'a>( + self: &Self, + state: &'a State, + _ctx: &mut Context, + ) -> Result, Stop> { + Ok(match &self.kind { + VarKind::Data { def } => { + match (def.ty.as_ref(), state.env.get(&def.clone().into()).unwrap()) { + (ExprTy::Atom { atom_ty }, NodeVal::Atom(cell)) => ExprVal::Atom( + cell.get(atom_ty.sem.unwrap()) + .ok_or_else(|| -> Stop { todo!("unresolved var diagnostic") })?, + ), + (_, NodeVal::Array(ref aggr)) => ExprVal::Array(aggr), + _ => unreachable!(), + } + } + VarKind::Index { range } => ExprVal::Atom(sem::AtomVal::new( + range.bound.ty.sem.unwrap().clone(), + *state.indexes.get(&range.clone().into()).unwrap() as i64, + )), + VarKind::Err => unreachable!(), + }) + } + } +} diff --git a/task-maker-iospec/src/tools/iospec_check.rs b/task-maker-iospec/src/tools/iospec_check.rs new file mode 100644 index 000000000..479352708 --- /dev/null +++ b/task-maker-iospec/src/tools/iospec_check.rs @@ -0,0 +1,44 @@ +use std::fs::File; +use std::io; +use std::io::BufReader; +use std::path::PathBuf; + +use anyhow::Error; +use clap::Parser; + +use crate::run::Run; +use crate::*; + +use super::share::SpecOpt; + +#[derive(Parser, Debug, Clone)] +pub struct Opt { + #[clap(flatten)] + pub spec: SpecOpt, + pub input: Option, + pub output: Option, +} + +pub fn do_main(opt: Opt, stderr: &mut dyn io::Write) -> Result<(), Error> { + let (ir, dgns) = opt.spec.load(stderr, vec!["check".into()])?; + + match (opt.input, opt.output) { + (Some(input), output) => ir + .run( + &mut Default::default(), + &mut run::Context { + input_source: run::IoSource(Box::new(BufReader::new( + File::open(input).unwrap(), + ))), + output_source: output.map(|output| { + run::IoSource(Box::new(BufReader::new(File::open(output).unwrap()))) + }), + dgns, + }, + ) + .or_else(|stop| stop.as_result())?, + _ => (), + } + + Ok(()) +} diff --git a/task-maker-iospec/src/tools/iospec_gen.rs b/task-maker-iospec/src/tools/iospec_gen.rs new file mode 100644 index 000000000..23afeca06 --- /dev/null +++ b/task-maker-iospec/src/tools/iospec_gen.rs @@ -0,0 +1,75 @@ +use std::fs::File; +use std::io; +use std::io::stdout; +use std::io::Write; +use std::path::PathBuf; + +use anyhow::bail; +use anyhow::Error; +use clap::ArgEnum; +use clap::Parser; +use gen::gen_string; +use gen::Inspect; +use ir::Template; + +use crate::lang::c::C; +use crate::lang::cpp::Cpp; +// use crate::lang::tex::Tex; +use crate::lang::cpp_lib::CppLib; +use crate::*; + +use super::share::SpecOpt; + +#[derive(Parser, Debug, Clone)] +pub struct Opt { + #[clap(flatten)] + pub spec: SpecOpt, + #[clap(long, arg_enum)] + pub lang: LangOpt, + #[clap(long, arg_enum, default_value = "grader")] + pub target: TargetOpt, + #[clap(long)] + pub dest: Option, +} + +#[derive(ArgEnum, Debug, Clone, Copy)] +pub enum LangOpt { + C, + Cpp, + Inspect, + // Tex, +} + +#[derive(ArgEnum, Debug, Clone, Copy)] +pub enum TargetOpt { + Grader, + Template, + Support, +} + +pub fn do_main(opt: Opt, stderr: &mut dyn io::Write) -> Result<(), Error> { + let (ir, _) = opt + .spec + .load(stderr, vec!["gen".into(), format!("lang={:?}", opt.lang)])?; + + let str = match (&opt.target, &opt.lang) { + (TargetOpt::Grader, LangOpt::C) => gen_string(&ir, &C), + (TargetOpt::Grader, LangOpt::Cpp) => gen_string(&ir, &Cpp), + (TargetOpt::Grader, LangOpt::Inspect) => gen_string(&ir, &Inspect), + (TargetOpt::Template, LangOpt::C) => gen_string(&Template(&ir), &C), + (TargetOpt::Template, LangOpt::Cpp) => gen_string(&Template(&ir), &Cpp), + (TargetOpt::Support, LangOpt::Cpp) => gen_string(&ir, &CppLib), + _ => bail!( + "unsupported combination: `--target {:?} --lang {:?}`", + &opt.target, + &opt.lang + ), + }; + + match &opt.dest { + Some(path) => File::create(path)?.write(str.as_bytes())?, + None => stdout().write(str.as_bytes())?, + }; + + Ok(()) +} diff --git a/task-maker-iospec/src/tools/iospec_gen_all.rs b/task-maker-iospec/src/tools/iospec_gen_all.rs new file mode 100644 index 000000000..8d1d8deca --- /dev/null +++ b/task-maker-iospec/src/tools/iospec_gen_all.rs @@ -0,0 +1,101 @@ +use anyhow::Error; +use clap::Parser; +use std::io; + +use super::iospec_gen::LangOpt; +use super::iospec_gen::TargetOpt; +use super::iospec_gen::{self}; +use super::share::SpecOpt; + +#[derive(Parser, Debug, Clone)] +pub struct Opt { + #[clap(flatten)] + pub spec: SpecOpt, +} + +pub fn do_main(opt: Opt, stderr: &mut dyn io::Write) -> Result<(), Error> { + let gen_targets = vec![ + ( + "gen/iospec.hpp", + LangOpt::Cpp, + TargetOpt::Support, + vec!["support"], + ), + ( + "sol/grader.cpp", + LangOpt::Cpp, + TargetOpt::Grader, + vec!["grader"], + ), + ( + "sol/grader.c", + LangOpt::C, + TargetOpt::Grader, + vec!["grader"], + ), + ( + "sol/template.cpp", + LangOpt::Cpp, + TargetOpt::Template, + vec!["template"], + ), + ( + "sol/template.c", + LangOpt::C, + TargetOpt::Template, + vec!["template"], + ), + ]; + + let copy_targets = vec![ + ("gen/iolib.hpp", include_str!("../assets/iolib.hpp")), + ( + "gen/sample.generator.cpp", + include_str!("../assets/sample.generator.cpp"), + ), + ( + "gen/sample.validator.cpp", + include_str!("../assets/sample.validator.cpp"), + ), + ( + "gen/sample.checker.cpp", + include_str!("../assets/sample.checker.cpp"), + ), + ("gen/IOSPEC.sample", include_str!("../assets/IOSPEC.sample")), + ]; + + for (path, lang, target, cfg) in gen_targets.into_iter() { + let SpecOpt { + spec, + cfg: base_cfg, + color, + } = opt.spec.clone(); + let extra_cfg: Vec<_> = cfg.iter().map(|s| s.to_string()).collect(); + + eprintln!("Generating `{}`...", path); + + iospec_gen::do_main( + iospec_gen::Opt { + spec: SpecOpt { + spec, + cfg: base_cfg.into_iter().chain(extra_cfg.into_iter()).collect(), + color, + }, + target, + lang, + dest: Some(path.into()), + }, + stderr, + ) + .unwrap_or_else(|error| eprintln!("error while generating {}: {}", path, error)); + } + + for (path, content) in copy_targets.into_iter() { + eprintln!("Adding `{}`...", path); + + std::fs::write(path, content) + .unwrap_or_else(|error| eprintln!("error while generating {}: {}", path, error)); + } + + Ok(()) +} diff --git a/task-maker-iospec/src/tools/mod.rs b/task-maker-iospec/src/tools/mod.rs new file mode 100644 index 000000000..1ab0bac0e --- /dev/null +++ b/task-maker-iospec/src/tools/mod.rs @@ -0,0 +1,7 @@ +mod share; + +pub use share::*; + +pub mod iospec_check; +pub mod iospec_gen; +pub mod iospec_gen_all; diff --git a/task-maker-iospec/src/tools/share.rs b/task-maker-iospec/src/tools/share.rs new file mode 100644 index 000000000..057f79212 --- /dev/null +++ b/task-maker-iospec/src/tools/share.rs @@ -0,0 +1,65 @@ +use anyhow::Context; +use anyhow::Error; +use clap::ArgEnum; +use clap::Parser; +use codemap::CodeMap; +use std::fs::read_to_string; +use std::io; +use std::path::PathBuf; + +use crate::ast; +use crate::compile; +use crate::dgns; +use crate::sem; +use crate::spec::ir::Spec; + +#[derive(Parser, Debug, Clone)] +pub struct SpecOpt { + #[clap(long, default_value = "gen/IOSPEC")] + pub spec: PathBuf, + #[clap(long)] + pub cfg: Vec, + #[clap(long, arg_enum, default_value = "always")] + pub color: ColorOpt, +} + +#[derive(ArgEnum, Debug, Clone, Copy)] +pub enum ColorOpt { + Always, + Never, +} + +impl SpecOpt { + pub fn load( + self, + stderr: &mut dyn io::Write, + base_cfg: Vec, + ) -> Result<(Spec, dgns::DiagnosticContext), Error> { + let source = read_to_string(&self.spec).context("cannot read file")?; + let mut code_map = CodeMap::new(); + let file = code_map.add_file(self.spec.to_string_lossy().into(), source.clone()); + let mut dgns = dgns::DiagnosticContext { + spec_file: file, + stderr, + color: matches!(self.color, ColorOpt::Always), + }; + + let ast: ast::Spec = syn::parse_str(&source).map_err(|e| { + dgns.error( + &e.to_string(), + vec![dgns.error_ann("here", e.span())], + vec![], + ); + e + })?; + + let ir = compile::compile( + &ast, + &mut dgns, + sem::Cfg(base_cfg.into_iter().chain(self.cfg).collect()), + ) + .map_err(|_| anyhow::anyhow!("compilation stopped due to previous errors"))?; + + Ok((ir, dgns)) + } +} diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/IOSPEC b/task-maker-iospec/tests/goldenfiles/atomic_types/IOSPEC new file mode 100644 index 000000000..d5a88e173 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/atomic_types/IOSPEC @@ -0,0 +1,16 @@ +inputln { + item xi32: i32; + item xi64: i64; + item xbool: bool; +} + +outputln { + @call gi32(x=xi32) -> yi32; + item yi32: i32; + + @call gi64(x=xi64) -> yi64; + item yi64: i64; + + @call gbool(x=xbool) -> ybool; + item ybool: bool; +} diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/IOSPEC.check.stderr b/task-maker-iospec/tests/goldenfiles/atomic_types/IOSPEC.check.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/grader.c b/task-maker-iospec/tests/goldenfiles/atomic_types/grader.c new file mode 100644 index 000000000..7eae30382 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/atomic_types/grader.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +int gi32(int x); +long long gi64(long long x); +bool gbool(bool x); + +int main() { + int xi32 = 0; + long long xi64 = 0; + bool xbool = 0; + int yi32 = 0; + long long yi64 = 0; + bool ybool = 0; + + assert(scanf("%d", &xi32) == 1); + assert(scanf("%lld", &xi64) == 1); + assert(scanf("%d", &xbool) == 1); + yi32 = gi32(xi32); + printf("%d ", yi32); + yi64 = gi64(xi64); + printf("%lld ", yi64); + ybool = gbool(xbool); + printf("%d ", ybool); + printf("\n"); +} diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/grader.c.gen.stderr b/task-maker-iospec/tests/goldenfiles/atomic_types/grader.c.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/grader.cpp b/task-maker-iospec/tests/goldenfiles/atomic_types/grader.cpp new file mode 100644 index 000000000..04f4f2fcb --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/atomic_types/grader.cpp @@ -0,0 +1,29 @@ +#include +#include +#include + +using namespace std; + +int gi32(int x); +long long gi64(long long x); +bool gbool(bool x); + +int main() { + int xi32; + long long xi64; + bool xbool; + int yi32; + long long yi64; + bool ybool; + + std::cin >> xi32; + std::cin >> xi64; + std::cin >> xbool; + yi32 = gi32(xi32); + std::cout << yi32 << " "; + yi64 = gi64(xi64); + std::cout << yi64 << " "; + ybool = gbool(xbool); + std::cout << ybool << " "; + std::cout << std::endl; +} diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/grader.cpp.gen.stderr b/task-maker-iospec/tests/goldenfiles/atomic_types/grader.cpp.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/grader.inspect b/task-maker-iospec/tests/goldenfiles/atomic_types/grader.inspect new file mode 100644 index 000000000..9c2013414 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/atomic_types/grader.inspect @@ -0,0 +1,23 @@ +<> + +<> + +<> +<> +<> +<> +<> +<> + +ioln: + item xi32: i32; + item xi64: i64; + item xbool: bool; +ioln: + @call (<>) + item yi32: i32; + @call (<>) + item yi64: i64; + @call (<>) + item ybool: bool; + diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/grader.inspect.gen.stderr b/task-maker-iospec/tests/goldenfiles/atomic_types/grader.inspect.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/solution.c b/task-maker-iospec/tests/goldenfiles/atomic_types/solution.c new file mode 100644 index 000000000..fd5992a78 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/atomic_types/solution.c @@ -0,0 +1,13 @@ +#include + +int gi32(int x) { + return x; +} + +long long gi64(long long x) { + return x; +} + +bool gbool(bool x) { + return x; +} diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/solution.cpp b/task-maker-iospec/tests/goldenfiles/atomic_types/solution.cpp new file mode 100644 index 000000000..bca171261 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/atomic_types/solution.cpp @@ -0,0 +1,15 @@ +#include + +using namespace std; + +int gi32(int x) { + return x; +} + +long long gi64(long long x) { + return x; +} + +bool gbool(bool x) { + return x; +} diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/support.cpp b/task-maker-iospec/tests/goldenfiles/atomic_types/support.cpp new file mode 100644 index 000000000..6ec68e8b6 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/atomic_types/support.cpp @@ -0,0 +1,79 @@ +#ifndef IOLIB_HPP +#define IOLIB_HPP + +#include +#include + +using std::vector; + + +int gi32(int x); +long long gi64(long long x); +bool gbool(bool x); +struct IoData { + int xi32 = {}; + long long xi64 = {}; + bool xbool = {}; + int yi32 = {}; + long long yi64 = {}; + bool ybool = {}; + + struct Funs { + std::function gi32 = [](auto...) { return 0; }; + std::function gi64 = [](auto...) { return 0; }; + std::function gbool = [](auto...) { return 0; }; + }; + + static Funs global_funs() { + Funs funs; + funs.gi32 = gi32; + funs.gi64 = gi64; + funs.gbool = gbool; + return funs; + } +}; + +template < + typename Item, + typename Endl, + typename Check, + typename InvokeVoid, + typename Invoke, + typename Resize +> +void process_io( + IoData& data, + IoData::Funs funs, + Item item, + Endl endl, + Check check, + InvokeVoid invoke, + Invoke invoke_void, + Resize resize +) { + auto& xi32 = data.xi32; + auto& xi64 = data.xi64; + auto& xbool = data.xbool; + auto& yi32 = data.yi32; + auto& yi64 = data.yi64; + auto& ybool = data.ybool; + auto& gi32 = funs.gi32; + auto& gi64 = funs.gi64; + auto& gbool = funs.gbool; + const bool INPUT = 0; + const bool OUTPUT = 1; + + item(INPUT, xi32); + item(INPUT, xi64); + item(INPUT, xbool); + endl(INPUT); + invoke(yi32, gi32, xi32); + item(OUTPUT, yi32); + invoke(yi64, gi64, xi64); + item(OUTPUT, yi64); + invoke(ybool, gbool, xbool); + item(OUTPUT, ybool); + endl(OUTPUT); +} + +#endif diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/support.cpp.gen.stderr b/task-maker-iospec/tests/goldenfiles/atomic_types/support.cpp.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/template.c b/task-maker-iospec/tests/goldenfiles/atomic_types/template.c new file mode 100644 index 000000000..ba2933c1b --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/atomic_types/template.c @@ -0,0 +1,13 @@ +#include + +int gi32(int x) { + return 42; +} + +long long gi64(long long x) { + return 42; +} + +bool gbool(bool x) { + return 42; +} diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/template.c.gen.stderr b/task-maker-iospec/tests/goldenfiles/atomic_types/template.c.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/template.cpp b/task-maker-iospec/tests/goldenfiles/atomic_types/template.cpp new file mode 100644 index 000000000..301441273 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/atomic_types/template.cpp @@ -0,0 +1,15 @@ +#include + +using namespace std; + +int gi32(int x) { + return 42; +} + +long long gi64(long long x) { + return 42; +} + +bool gbool(bool x) { + return 42; +} diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/template.cpp.gen.stderr b/task-maker-iospec/tests/goldenfiles/atomic_types/template.cpp.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/valid.c.output b/task-maker-iospec/tests/goldenfiles/atomic_types/valid.c.output new file mode 100644 index 000000000..3ea36dc89 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/atomic_types/valid.c.output @@ -0,0 +1 @@ +0 0 0 diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/valid.c.output.check.stderr b/task-maker-iospec/tests/goldenfiles/atomic_types/valid.c.output.check.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/valid.cpp.output b/task-maker-iospec/tests/goldenfiles/atomic_types/valid.cpp.output new file mode 100644 index 000000000..3ea36dc89 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/atomic_types/valid.cpp.output @@ -0,0 +1 @@ +0 0 0 diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/valid.cpp.output.check.stderr b/task-maker-iospec/tests/goldenfiles/atomic_types/valid.cpp.output.check.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/atomic_types/valid.input.check.stderr b/task-maker-iospec/tests/goldenfiles/atomic_types/valid.input.check.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/compilation_errors/undefined_var.iospec b/task-maker-iospec/tests/goldenfiles/compilation_errors/undefined_var.iospec new file mode 100644 index 000000000..46f7d3b2c --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/compilation_errors/undefined_var.iospec @@ -0,0 +1 @@ +assume x == 0; diff --git a/task-maker-iospec/tests/goldenfiles/compilation_errors/undefined_var.iospec.check.stderr b/task-maker-iospec/tests/goldenfiles/compilation_errors/undefined_var.iospec.check.stderr new file mode 100644 index 000000000..d25f7472b --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/compilation_errors/undefined_var.iospec.check.stderr @@ -0,0 +1,6 @@ +error: no variable named `x` found in the current scope + --> undefined_var.iospec:1:8 + | +1 | assume x == 0; + | ^ not found in this scope + | diff --git a/task-maker-iospec/tests/goldenfiles/segtree/IOSPEC b/task-maker-iospec/tests/goldenfiles/segtree/IOSPEC new file mode 100644 index 000000000..03a6cbef4 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/segtree/IOSPEC @@ -0,0 +1,56 @@ +inputln { + item n: i32; + item q: i32; +} + +inputln { + for i upto n { + item a[i]: i32; + } +} + +// @call init(a=a); + +for i upto q { + inputln { + item op: i32; + + if op == 1 { + item l1: i32; + item r1: i32; + // @call get_sum(l=l1, r=r1) -> s; + } + + + if op == 2 { + item l2: i32; + item r2: i32; + item x2: i32; + // @call add(l=l2, r=r2) -> s; + } + + if op == 3 { + item l3: i32; + item r3: i32; + item x3: i32; + // @call set_range(l=l3, r=r3, x=x3) -> s; + } + + if op == 4 { + item l4: i32; + item r4: i32; + // @call min(l=l4, r=r4) -> s; + } + + if op == 5 { + item l5: i32; + item r5: i32; + item x5: i32; + // @call lower_bound(l=l5, r=r5, x=x5) -> s; + } + } + + outputln { + item s: i32; + } +} diff --git a/task-maker-iospec/tests/goldenfiles/segtree/IOSPEC.check.stderr b/task-maker-iospec/tests/goldenfiles/segtree/IOSPEC.check.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/segtree/grader.c b/task-maker-iospec/tests/goldenfiles/segtree/grader.c new file mode 100644 index 000000000..a7c717406 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/segtree/grader.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include + +int main() { + int n = 0; + int q = 0; + int* a = 0; + + assert(scanf("%d", &n) == 1); + assert(scanf("%d", &q) == 1); + a = realloc(a, sizeof(int) * (n)); + for(int i = 0; i < n; i++) { + assert(scanf("%d", &a[i]) == 1); + } + for(int i = 0; i < q; i++) { + int op = 0; + int l1 = 0; + int r1 = 0; + int l2 = 0; + int r2 = 0; + int x2 = 0; + int l3 = 0; + int r3 = 0; + int x3 = 0; + int l4 = 0; + int r4 = 0; + int l5 = 0; + int r5 = 0; + int x5 = 0; + int s = 0; + + assert(scanf("%d", &op) == 1); + if(op == 1) { + assert(scanf("%d", &l1) == 1); + assert(scanf("%d", &r1) == 1); + } + if(op == 2) { + assert(scanf("%d", &l2) == 1); + assert(scanf("%d", &r2) == 1); + assert(scanf("%d", &x2) == 1); + } + if(op == 3) { + assert(scanf("%d", &l3) == 1); + assert(scanf("%d", &r3) == 1); + assert(scanf("%d", &x3) == 1); + } + if(op == 4) { + assert(scanf("%d", &l4) == 1); + assert(scanf("%d", &r4) == 1); + } + if(op == 5) { + assert(scanf("%d", &l5) == 1); + assert(scanf("%d", &r5) == 1); + assert(scanf("%d", &x5) == 1); + } + printf("%d ", s); + printf("\n"); + } +} diff --git a/task-maker-iospec/tests/goldenfiles/segtree/grader.c.gen.stderr b/task-maker-iospec/tests/goldenfiles/segtree/grader.c.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/segtree/grader.cpp b/task-maker-iospec/tests/goldenfiles/segtree/grader.cpp new file mode 100644 index 000000000..3f15600c0 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/segtree/grader.cpp @@ -0,0 +1,62 @@ +#include +#include +#include + +using namespace std; + +int main() { + int n; + int q; + vector a; + + std::cin >> n; + std::cin >> q; + a.resize(n); + for(int i = 0; i < n; i++) { + std::cin >> a[i]; + } + for(int i = 0; i < q; i++) { + int op; + int l1; + int r1; + int l2; + int r2; + int x2; + int l3; + int r3; + int x3; + int l4; + int r4; + int l5; + int r5; + int x5; + int s; + + std::cin >> op; + if(op == 1) { + std::cin >> l1; + std::cin >> r1; + } + if(op == 2) { + std::cin >> l2; + std::cin >> r2; + std::cin >> x2; + } + if(op == 3) { + std::cin >> l3; + std::cin >> r3; + std::cin >> x3; + } + if(op == 4) { + std::cin >> l4; + std::cin >> r4; + } + if(op == 5) { + std::cin >> l5; + std::cin >> r5; + std::cin >> x5; + } + std::cout << s << " "; + std::cout << std::endl; + } +} diff --git a/task-maker-iospec/tests/goldenfiles/segtree/grader.cpp.gen.stderr b/task-maker-iospec/tests/goldenfiles/segtree/grader.cpp.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/segtree/grader.inspect b/task-maker-iospec/tests/goldenfiles/segtree/grader.inspect new file mode 100644 index 000000000..29edf2c2a --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/segtree/grader.inspect @@ -0,0 +1,59 @@ +<> + +<> + +<> +<> +<> + +ioln: + item n: i32; + item q: i32; +ioln: + <> + for i upto n : + <> + + item a[i]: i32; +for i upto q : + <> + + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + + ioln: + item op: i32; + if op == 1: + item l1: i32; + item r1: i32; + if op == 2: + item l2: i32; + item r2: i32; + item x2: i32; + if op == 3: + item l3: i32; + item r3: i32; + item x3: i32; + if op == 4: + item l4: i32; + item r4: i32; + if op == 5: + item l5: i32; + item r5: i32; + item x5: i32; + ioln: + item s: i32; + diff --git a/task-maker-iospec/tests/goldenfiles/segtree/grader.inspect.gen.stderr b/task-maker-iospec/tests/goldenfiles/segtree/grader.inspect.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/segtree/solution.c b/task-maker-iospec/tests/goldenfiles/segtree/solution.c new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/segtree/solution.cpp b/task-maker-iospec/tests/goldenfiles/segtree/solution.cpp new file mode 100644 index 000000000..b35936f5b --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/segtree/solution.cpp @@ -0,0 +1,4 @@ +#include + +using namespace std; + diff --git a/task-maker-iospec/tests/goldenfiles/segtree/support.cpp b/task-maker-iospec/tests/goldenfiles/segtree/support.cpp new file mode 100644 index 000000000..1900a455a --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/segtree/support.cpp @@ -0,0 +1,102 @@ +#ifndef IOLIB_HPP +#define IOLIB_HPP + +#include +#include + +using std::vector; + +struct IoData { + int n = {}; + int q = {}; + vector a = {}; + + struct Funs { + }; + + static Funs global_funs() { + Funs funs; + return funs; + } +}; + +template < + typename Item, + typename Endl, + typename Check, + typename InvokeVoid, + typename Invoke, + typename Resize +> +void process_io( + IoData& data, + IoData::Funs funs, + Item item, + Endl endl, + Check check, + InvokeVoid invoke, + Invoke invoke_void, + Resize resize +) { + auto& n = data.n; + auto& q = data.q; + auto& a = data.a; + const bool INPUT = 0; + const bool OUTPUT = 1; + + item(INPUT, n); + item(INPUT, q); + endl(INPUT); + resize(INPUT, a, n); + for(int i = 0; i < n; i++) { + item(INPUT, a[i]); + } + endl(INPUT); + for(int i = 0; i < q; i++) { + int op; + int l1; + int r1; + int l2; + int r2; + int x2; + int l3; + int r3; + int x3; + int l4; + int r4; + int l5; + int r5; + int x5; + int s; + + item(INPUT, op); + if(op == 1) { + item(INPUT, l1); + item(INPUT, r1); + } + if(op == 2) { + item(INPUT, l2); + item(INPUT, r2); + item(INPUT, x2); + } + if(op == 3) { + item(INPUT, l3); + item(INPUT, r3); + item(INPUT, x3); + } + if(op == 4) { + item(INPUT, l4); + item(INPUT, r4); + } + if(op == 5) { + item(INPUT, l5); + item(INPUT, r5); + item(INPUT, x5); + } + endl(INPUT); + item(OUTPUT, s); + endl(OUTPUT); + } +} + +#endif diff --git a/task-maker-iospec/tests/goldenfiles/segtree/support.cpp.gen.stderr b/task-maker-iospec/tests/goldenfiles/segtree/support.cpp.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/segtree/template.c b/task-maker-iospec/tests/goldenfiles/segtree/template.c new file mode 100644 index 000000000..e1018195f --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/segtree/template.c @@ -0,0 +1,2 @@ +#include + diff --git a/task-maker-iospec/tests/goldenfiles/segtree/template.c.gen.stderr b/task-maker-iospec/tests/goldenfiles/segtree/template.c.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/segtree/template.cpp b/task-maker-iospec/tests/goldenfiles/segtree/template.cpp new file mode 100644 index 000000000..b35936f5b --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/segtree/template.cpp @@ -0,0 +1,4 @@ +#include + +using namespace std; + diff --git a/task-maker-iospec/tests/goldenfiles/segtree/template.cpp.gen.stderr b/task-maker-iospec/tests/goldenfiles/segtree/template.cpp.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/triangle/IOSPEC b/task-maker-iospec/tests/goldenfiles/triangle/IOSPEC new file mode 100644 index 000000000..0bc460c01 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/triangle/IOSPEC @@ -0,0 +1,11 @@ +inputln { + item N: i32; +} + +for i upto N { + inputln { + for j upto i + 1 { + item A[i][j]: i32; + } + } +} diff --git a/task-maker-iospec/tests/goldenfiles/triangle/IOSPEC.check.stderr b/task-maker-iospec/tests/goldenfiles/triangle/IOSPEC.check.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/triangle/grader.c b/task-maker-iospec/tests/goldenfiles/triangle/grader.c new file mode 100644 index 000000000..52192843d --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/triangle/grader.c @@ -0,0 +1,18 @@ +#include +#include +#include +#include + +int main() { + int N = 0; + int** A = 0; + + assert(scanf("%d", &N) == 1); + A = realloc(A, sizeof(int*) * (N)); + for(int i = 0; i < N; i++) { + A[i] = realloc(A[i], sizeof(int) * (i + 1)); + for(int j = 0; j < i + 1; j++) { + assert(scanf("%d", &A[i][j]) == 1); + } + } +} diff --git a/task-maker-iospec/tests/goldenfiles/triangle/grader.c.gen.stderr b/task-maker-iospec/tests/goldenfiles/triangle/grader.c.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/triangle/grader.cpp b/task-maker-iospec/tests/goldenfiles/triangle/grader.cpp new file mode 100644 index 000000000..656012d5a --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/triangle/grader.cpp @@ -0,0 +1,19 @@ +#include +#include +#include + +using namespace std; + +int main() { + int N; + vector> A; + + std::cin >> N; + A.resize(N); + for(int i = 0; i < N; i++) { + A[i].resize(i + 1); + for(int j = 0; j < i + 1; j++) { + std::cin >> A[i][j]; + } + } +} diff --git a/task-maker-iospec/tests/goldenfiles/triangle/grader.cpp.gen.stderr b/task-maker-iospec/tests/goldenfiles/triangle/grader.cpp.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/triangle/grader.inspect b/task-maker-iospec/tests/goldenfiles/triangle/grader.inspect new file mode 100644 index 000000000..f3e9fcca3 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/triangle/grader.inspect @@ -0,0 +1,20 @@ +<> + +<> + +<> +<> + +ioln: + item N: i32; +<> +for i upto N : + <> + + ioln: + <> + for j upto i + 1 : + <> + + item A[i][j]: i32; + diff --git a/task-maker-iospec/tests/goldenfiles/triangle/grader.inspect.gen.stderr b/task-maker-iospec/tests/goldenfiles/triangle/grader.inspect.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/triangle/solution.c b/task-maker-iospec/tests/goldenfiles/triangle/solution.c new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/triangle/solution.cpp b/task-maker-iospec/tests/goldenfiles/triangle/solution.cpp new file mode 100644 index 000000000..b35936f5b --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/triangle/solution.cpp @@ -0,0 +1,4 @@ +#include + +using namespace std; + diff --git a/task-maker-iospec/tests/goldenfiles/triangle/support.cpp b/task-maker-iospec/tests/goldenfiles/triangle/support.cpp new file mode 100644 index 000000000..a6dac3264 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/triangle/support.cpp @@ -0,0 +1,57 @@ +#ifndef IOLIB_HPP +#define IOLIB_HPP + +#include +#include + +using std::vector; + +struct IoData { + int N = {}; + vector> A = {}; + + struct Funs { + }; + + static Funs global_funs() { + Funs funs; + return funs; + } +}; + +template < + typename Item, + typename Endl, + typename Check, + typename InvokeVoid, + typename Invoke, + typename Resize +> +void process_io( + IoData& data, + IoData::Funs funs, + Item item, + Endl endl, + Check check, + InvokeVoid invoke, + Invoke invoke_void, + Resize resize +) { + auto& N = data.N; + auto& A = data.A; + const bool INPUT = 0; + const bool OUTPUT = 1; + + item(INPUT, N); + endl(INPUT); + resize(INPUT, A, N); + for(int i = 0; i < N; i++) { + resize(INPUT, A[i], i + 1); + for(int j = 0; j < i + 1; j++) { + item(INPUT, A[i][j]); + } + endl(INPUT); + } +} + +#endif diff --git a/task-maker-iospec/tests/goldenfiles/triangle/support.cpp.gen.stderr b/task-maker-iospec/tests/goldenfiles/triangle/support.cpp.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/triangle/template.c b/task-maker-iospec/tests/goldenfiles/triangle/template.c new file mode 100644 index 000000000..e1018195f --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/triangle/template.c @@ -0,0 +1,2 @@ +#include + diff --git a/task-maker-iospec/tests/goldenfiles/triangle/template.c.gen.stderr b/task-maker-iospec/tests/goldenfiles/triangle/template.c.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/triangle/template.cpp b/task-maker-iospec/tests/goldenfiles/triangle/template.cpp new file mode 100644 index 000000000..b35936f5b --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/triangle/template.cpp @@ -0,0 +1,4 @@ +#include + +using namespace std; + diff --git a/task-maker-iospec/tests/goldenfiles/triangle/template.cpp.gen.stderr b/task-maker-iospec/tests/goldenfiles/triangle/template.cpp.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/triangle/valid.c.output b/task-maker-iospec/tests/goldenfiles/triangle/valid.c.output new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/triangle/valid.c.output.check.stderr b/task-maker-iospec/tests/goldenfiles/triangle/valid.c.output.check.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/triangle/valid.cpp.output b/task-maker-iospec/tests/goldenfiles/triangle/valid.cpp.output new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/triangle/valid.cpp.output.check.stderr b/task-maker-iospec/tests/goldenfiles/triangle/valid.cpp.output.check.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/triangle/valid.input.check.stderr b/task-maker-iospec/tests/goldenfiles/triangle/valid.input.check.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/IOSPEC b/task-maker-iospec/tests/goldenfiles/weighted_graph/IOSPEC new file mode 100644 index 000000000..81fc85e15 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/weighted_graph/IOSPEC @@ -0,0 +1,51 @@ +// Comments + +inputln { + /// Number of nodes in the graph + /// Second single-line doc comment + /** + Block doc comment + */ + /** Block doc comment + Block doc comment + */ + item N: i32; + item M: i32; +} + +assume 2 <= N < 100_000; +assume 0 <= M < 500_000; + +#[cfg(subtask_name = "quadratic")] +assume N <= 1_000; + +inputln { + for u upto N { + item W[u]: i32; + assume 0 <= W[u] < 1_000_000_000; + } +} + +for i upto M { + inputln { + item A[i]: i32; + item B[i]: i32; + } + + assume 0 <= A[i] < N; + assume 0 <= B[i] < N; +} + +outputln { + @call f(N = N, M = M) -> S; + /// Answer + item S: i32; +} + +outputln { + @resize X to N; + @call g(N = N, M = &M, A = &A, B = B, X = &X); + for u upto N { + item X[u]: i32; + } +} diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/IOSPEC.check.stderr b/task-maker-iospec/tests/goldenfiles/weighted_graph/IOSPEC.check.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/grader.c b/task-maker-iospec/tests/goldenfiles/weighted_graph/grader.c new file mode 100644 index 000000000..4aa6f959a --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/weighted_graph/grader.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include + +int f(int N, int M); +void g(int N, int* M, int* A, int* B, int* X); + +int main() { + int N = 0; + int M = 0; + int* W = 0; + int* A = 0; + int* B = 0; + int S = 0; + int* X = 0; + + /** Number of nodes in the graph */ + /** Second single-line doc comment */ + /** + Block doc comment + */ + /** Block doc comment + Block doc comment + */ + assert(scanf("%d", &N) == 1); + assert(scanf("%d", &M) == 1); + assert(2 <= N && N < 100000); + assert(0 <= M && M < 500000); + W = realloc(W, sizeof(int) * (N)); + for(int u = 0; u < N; u++) { + assert(scanf("%d", &W[u]) == 1); + assert(0 <= W[u] && W[u] < 1000000000); + } + A = realloc(A, sizeof(int) * (M)); + B = realloc(B, sizeof(int) * (M)); + for(int i = 0; i < M; i++) { + assert(scanf("%d", &A[i]) == 1); + assert(scanf("%d", &B[i]) == 1); + assert(0 <= A[i] && A[i] < N); + assert(0 <= B[i] && B[i] < N); + } + S = f(N, M); + /** Answer */ + printf("%d ", S); + printf("\n"); + X = realloc(X, sizeof(int) * (N)); + g(N, &M, A, B, X); + X = realloc(X, sizeof(int) * (N)); + for(int u = 0; u < N; u++) { + printf("%d ", X[u]); + } + printf("\n"); +} diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/grader.c.gen.stderr b/task-maker-iospec/tests/goldenfiles/weighted_graph/grader.c.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/grader.cpp b/task-maker-iospec/tests/goldenfiles/weighted_graph/grader.cpp new file mode 100644 index 000000000..a51828063 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/weighted_graph/grader.cpp @@ -0,0 +1,55 @@ +#include +#include +#include + +using namespace std; + +int f(int N, int M); +void g(int N, int& M, vector& A, vector B, vector& X); + +int main() { + int N; + int M; + vector W; + vector A; + vector B; + int S; + vector X; + + /** Number of nodes in the graph */ + /** Second single-line doc comment */ + /** + Block doc comment + */ + /** Block doc comment + Block doc comment + */ + std::cin >> N; + std::cin >> M; + assert(2 <= N && N < 100000); + assert(0 <= M && M < 500000); + W.resize(N); + for(int u = 0; u < N; u++) { + std::cin >> W[u]; + assert(0 <= W[u] && W[u] < 1000000000); + } + A.resize(M); + B.resize(M); + for(int i = 0; i < M; i++) { + std::cin >> A[i]; + std::cin >> B[i]; + assert(0 <= A[i] && A[i] < N); + assert(0 <= B[i] && B[i] < N); + } + S = f(N, M); + /** Answer */ + std::cout << S << " "; + std::cout << std::endl; + X.resize(N); + g(N, M, A, B, X); + X.resize(N); + for(int u = 0; u < N; u++) { + std::cout << X[u] << " "; + } + std::cout << std::endl; +} diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/grader.cpp.gen.stderr b/task-maker-iospec/tests/goldenfiles/weighted_graph/grader.cpp.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/grader.inspect b/task-maker-iospec/tests/goldenfiles/weighted_graph/grader.inspect new file mode 100644 index 000000000..96a7a6366 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/weighted_graph/grader.inspect @@ -0,0 +1,51 @@ +<> + +<> + +<> +<> +<> +<> +<> +<> +<> + +ioln: + #[ <> ] + #[ <> ] + #[ <> ] + #[ <> ] + item N: i32; + item M: i32; +check 2 <= N && N < 100000; +check 0 <= M && M < 500000; +ioln: + <> + for u upto N : + <> + + item W[u]: i32; + check 0 <= W[u] && W[u] < 1000000000; +<> +<> +for i upto M : + <> + + ioln: + item A[i]: i32; + item B[i]: i32; + check 0 <= A[i] && A[i] < N; + check 0 <= B[i] && B[i] < N; +ioln: + @call (<>) + #[ <> ] + item S: i32; +ioln: + @call (<>) + @call (<>) + <> + for u upto N : + <> + + item X[u]: i32; + diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/grader.inspect.gen.stderr b/task-maker-iospec/tests/goldenfiles/weighted_graph/grader.inspect.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/solution.c b/task-maker-iospec/tests/goldenfiles/weighted_graph/solution.c new file mode 100644 index 000000000..76cccdb0c --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/weighted_graph/solution.c @@ -0,0 +1,7 @@ +int f(int N, int M) { + return 42; +} + +void g(int N, int* M, int* A, int* B, int* X) { + for(int i = 0; i < N; i++) X[i] = i; +} diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/solution.cpp b/task-maker-iospec/tests/goldenfiles/weighted_graph/solution.cpp new file mode 100644 index 000000000..2dbfd0473 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/weighted_graph/solution.cpp @@ -0,0 +1,11 @@ +#include + +using namespace std; + +int f(int N, int M) { + return 42; +} + +void g(int N, int& M, vector& A, vector B, vector& X) { + for(int i = 0; i < N; i++) X[i] = i; +} diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/support.cpp b/task-maker-iospec/tests/goldenfiles/weighted_graph/support.cpp new file mode 100644 index 000000000..76f2a953b --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/weighted_graph/support.cpp @@ -0,0 +1,104 @@ +#ifndef IOLIB_HPP +#define IOLIB_HPP + +#include +#include + +using std::vector; + + +int f(int N, int M); +void g(int N, int& M, vector& A, vector B, vector& X); +struct IoData { + int N = {}; + int M = {}; + vector W = {}; + vector A = {}; + vector B = {}; + int S = {}; + vector X = {}; + + struct Funs { + std::function f = [](auto...) { return 0; }; + std::function& A, vector B, vector& X)> g = [](auto...) {}; + }; + + static Funs global_funs() { + Funs funs; + funs.f = f; + funs.g = g; + return funs; + } +}; + +template < + typename Item, + typename Endl, + typename Check, + typename InvokeVoid, + typename Invoke, + typename Resize +> +void process_io( + IoData& data, + IoData::Funs funs, + Item item, + Endl endl, + Check check, + InvokeVoid invoke, + Invoke invoke_void, + Resize resize +) { + auto& N = data.N; + auto& M = data.M; + auto& W = data.W; + auto& A = data.A; + auto& B = data.B; + auto& S = data.S; + auto& X = data.X; + auto& f = funs.f; + auto& g = funs.g; + const bool INPUT = 0; + const bool OUTPUT = 1; + + /** Number of nodes in the graph */ + /** Second single-line doc comment */ + /** + Block doc comment + */ + /** Block doc comment + Block doc comment + */ + item(INPUT, N); + item(INPUT, M); + endl(INPUT); + check(INPUT, 2 <= N && N < 100000); + check(INPUT, 0 <= M && M < 500000); + resize(INPUT, W, N); + for(int u = 0; u < N; u++) { + item(INPUT, W[u]); + check(INPUT, 0 <= W[u] && W[u] < 1000000000); + } + endl(INPUT); + resize(INPUT, A, M); + resize(INPUT, B, M); + for(int i = 0; i < M; i++) { + item(INPUT, A[i]); + item(INPUT, B[i]); + endl(INPUT); + check(INPUT, 0 <= A[i] && A[i] < N); + check(INPUT, 0 <= B[i] && B[i] < N); + } + invoke(S, f, N, M); + /** Answer */ + item(OUTPUT, S); + endl(OUTPUT); + invoke_void(g, N, M, A, B, X); + resize(OUTPUT, X, N); + for(int u = 0; u < N; u++) { + item(OUTPUT, X[u]); + } + endl(OUTPUT); +} + +#endif diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/support.cpp.gen.stderr b/task-maker-iospec/tests/goldenfiles/weighted_graph/support.cpp.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/template.c b/task-maker-iospec/tests/goldenfiles/weighted_graph/template.c new file mode 100644 index 000000000..4de20911c --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/weighted_graph/template.c @@ -0,0 +1,8 @@ +#include + +int f(int N, int M) { + return 42; +} + +void g(int N, int* M, int* A, int* B, int* X) { +} diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/template.c.gen.stderr b/task-maker-iospec/tests/goldenfiles/weighted_graph/template.c.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/template.cpp b/task-maker-iospec/tests/goldenfiles/weighted_graph/template.cpp new file mode 100644 index 000000000..1e98cd2e2 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/weighted_graph/template.cpp @@ -0,0 +1,10 @@ +#include + +using namespace std; + +int f(int N, int M) { + return 42; +} + +void g(int N, int& M, vector& A, vector B, vector& X) { +} diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/template.cpp.gen.stderr b/task-maker-iospec/tests/goldenfiles/weighted_graph/template.cpp.gen.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/valid1.c.output b/task-maker-iospec/tests/goldenfiles/weighted_graph/valid1.c.output new file mode 100644 index 000000000..60eda1310 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/weighted_graph/valid1.c.output @@ -0,0 +1,2 @@ +42 +0 1 2 3 diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/valid1.c.output.check.stderr b/task-maker-iospec/tests/goldenfiles/weighted_graph/valid1.c.output.check.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/valid1.cpp.output b/task-maker-iospec/tests/goldenfiles/weighted_graph/valid1.cpp.output new file mode 100644 index 000000000..60eda1310 --- /dev/null +++ b/task-maker-iospec/tests/goldenfiles/weighted_graph/valid1.cpp.output @@ -0,0 +1,2 @@ +42 +0 1 2 3 diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/valid1.cpp.output.check.stderr b/task-maker-iospec/tests/goldenfiles/weighted_graph/valid1.cpp.output.check.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/goldenfiles/weighted_graph/valid1.input.check.stderr b/task-maker-iospec/tests/goldenfiles/weighted_graph/valid1.input.check.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/run_all.rs b/task-maker-iospec/tests/run_all.rs new file mode 100644 index 000000000..51f2fca9d --- /dev/null +++ b/task-maker-iospec/tests/run_all.rs @@ -0,0 +1,289 @@ +use std::env; +use std::fs; +use std::fs::File; +use std::io::Write; +use std::path::Path; +use std::path::PathBuf; + +use assert_cmd::Command; +use goldenfile::Mint; +use task_maker_iospec::tools::iospec_gen::LangOpt; +use task_maker_iospec::tools::iospec_gen::TargetOpt; +use task_maker_iospec::tools::*; +use tempdir::TempDir; +use walkdir::WalkDir; + +const TEST_PREFIX: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/specs"); +const GOLDEN_PREFIX: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/goldenfiles"); + +#[test] +fn check_all_specs() -> Result<(), anyhow::Error> { + for e in WalkDir::new(TEST_PREFIX) + .into_iter() + .map(|e| e.unwrap()) + .filter(|e| !e.file_type().is_dir()) + .filter(|e| { + e.path() + .parent() + .unwrap() + .extension() + .map_or(true, |e| e != "skip") + }) + .filter(|e| { + e.file_name() == "IOSPEC" || e.path().extension().map_or(false, |e| e == "iospec") + }) + { + let spec_path = &e.path(); + let dir_path = spec_path.parent().unwrap(); + let spec_name = spec_path.file_name().unwrap().to_str().unwrap(); + + let mint = &mut create_mint(dir_path); + + let _temp_dir = within_temp_dir(); + + test_spec(dir_path, spec_name, mint); + + if spec_name == "IOSPEC" { + test_valid_spec(dir_path, mint); + } + } + + Ok(()) +} + +fn create_mint(dir_path: &Path) -> Mint { + let mint_path = &PathBuf::from_iter(vec![ + PathBuf::from(GOLDEN_PREFIX).as_path(), + dir_path.strip_prefix(TEST_PREFIX).unwrap(), + ]); + // Only useful when minting for the first time + fs::create_dir_all(mint_path).ok(); + Mint::new(mint_path) +} + +fn test_spec(dir_path: &Path, name: &str, mint: &mut Mint) { + copy_file(dir_path, name, mint); + + let _ = iospec_check::do_main( + iospec_check::Opt { + spec: SpecOpt { + spec: name.into(), + cfg: vec![], + color: ColorOpt::Never, + }, + input: None, + output: None, + }, + &mut File::create(format!("{}.check.stderr", name)).unwrap(), + ); + mint_file(mint, format!("{}.check.stderr", name)); +} + +fn copy_file(dir_path: &Path, name: &str, mint: &mut Mint) { + let path = &PathBuf::from_iter(vec![dir_path, &PathBuf::from(name)]); + let data = fs::read(path).unwrap(); + fs::write(name, &data).unwrap(); + mint_file(mint, name); +} + +fn test_valid_spec(dir_path: &Path, mint: &mut Mint) { + let all_langs = &vec![ + (LangOpt::Cpp, TargetOpt::Grader), + (LangOpt::Cpp, TargetOpt::Template), + (LangOpt::Cpp, TargetOpt::Support), + (LangOpt::C, TargetOpt::Grader), + (LangOpt::C, TargetOpt::Template), + (LangOpt::Inspect, TargetOpt::Grader), + ]; + + for (lang_opt, target_opt) in all_langs { + generate(lang_opt, target_opt, mint); + if matches!(target_opt, TargetOpt::Grader) { + compile_generated(dir_path, lang_opt, mint); + } + } + + for e in fs::read_dir(dir_path) + .unwrap() + .into_iter() + .map(|e| e.unwrap()) + .filter(|e| { + e.path() + .extension() + .map_or(false, |e| e.to_str() == Some("input")) + }) + { + let input_path = &e.path(); + let stem = &PathBuf::from(input_path.file_stem().unwrap()); + + copy_input(input_path, stem); + check_input_and_mint_stderr(stem, mint); + + for ref lang_opt in vec![LangOpt::Cpp, LangOpt::C] { + run_generated_and_mint_output(lang_opt, stem, mint); + check_output_and_mint_stderr(lang_opt, stem, mint); + } + } +} + +fn generate(lang_opt: &LangOpt, target_opt: &TargetOpt, mint: &mut Mint) { + let extension = lang_extension(lang_opt); + + let dest = &match target_opt { + TargetOpt::Grader => format!("grader.{}", extension), + TargetOpt::Template => format!("template.{}", extension), + TargetOpt::Support => format!("support.{}", extension), + }; + let stderr = &format!("{}.gen.stderr", dest); + + File::create(dest).unwrap(); + + let _ = iospec_gen::do_main( + iospec_gen::Opt { + spec: SpecOpt { + spec: "IOSPEC".into(), + cfg: vec![], + color: ColorOpt::Never, + }, + target: *target_opt, + dest: Some(dest.into()), + lang: lang_opt.clone(), + }, + &mut File::create(stderr).unwrap(), + ); + + mint_file(mint, stderr); + mint_file(mint, dest); +} + +fn compile_generated(dir_path: &Path, lang_opt: &LangOpt, mint: &mut Mint) { + match lang_opt { + LangOpt::Cpp => { + copy_file(dir_path, "solution.cpp", mint); + Command::new("g++") + .arg("grader.cpp") + .arg("solution.cpp") + .arg("-o") + .arg("main.cpp.bin") + .arg("-fsanitize=address") + .assert() + .success(); + } + LangOpt::C => { + copy_file(dir_path, "solution.c", mint); + Command::new("gcc") + .arg("grader.c") + .arg("solution.c") + .arg("-o") + .arg("main.c.bin") + // FIXME: missing `free` in generated C + // .arg("-fsanitize=address") + .assert() + .success(); + } + _ => (), + }; +} + +fn copy_input(input_path: &std::path::Path, stem: &std::path::Path) { + let input_data = fs::read(input_path).unwrap(); + fs::write(stem.with_extension("input"), input_data).unwrap(); +} + +fn check_input_and_mint_stderr(stem: &PathBuf, mint: &mut Mint) { + let stderr_path = &stem.with_extension("input.check.stderr"); + let _ = iospec_check::do_main( + iospec_check::Opt { + spec: SpecOpt { + spec: "IOSPEC".into(), + cfg: vec![], + color: ColorOpt::Never, + }, + input: Some(stem.with_extension("input")), + output: None, + }, + &mut File::create(stderr_path).unwrap(), + ); + mint_file(mint, stderr_path); +} + +fn run_generated_and_mint_output(lang_opt: &LangOpt, stem: &std::path::Path, mint: &mut Mint) { + match lang_opt { + LangOpt::Cpp => { + let output_path = &stem.with_extension("cpp.output"); + fs::write( + output_path, + &Command::new("./main.cpp.bin") + .write_stdin(fs::read(stem.with_extension("input")).unwrap()) + .assert() + .success() + .get_output() + .stdout, + ) + .unwrap(); + mint_file(mint, output_path) + } + LangOpt::C => { + let output_path = &stem.with_extension("c.output"); + fs::write( + output_path, + &Command::new("./main.c.bin") + .write_stdin(fs::read(stem.with_extension("input")).unwrap()) + .assert() + .success() + .get_output() + .stdout, + ) + .unwrap(); + mint_file(mint, output_path) + } + _ => (), + } +} + +fn check_output_and_mint_stderr(lang_opt: &LangOpt, stem: &std::path::Path, mint: &mut Mint) { + let extension = lang_extension(lang_opt); + let stderr_path = &stem.with_extension(format!("{}.output.check.stderr", extension)); + let _ = iospec_check::do_main( + iospec_check::Opt { + spec: SpecOpt { + spec: "IOSPEC".into(), + cfg: vec![], + color: ColorOpt::Never, + }, + input: Some(stem.with_extension("input")), + output: Some(stem.with_extension(format!("{}.output", extension))), + }, + &mut File::create(stderr_path).unwrap(), + ); + mint_file(mint, stderr_path); +} + +fn lang_extension(lang_opt: &LangOpt) -> &'static str { + let extension = match lang_opt { + LangOpt::Cpp => "cpp", + LangOpt::C => "c", + LangOpt::Inspect => "inspect", + // LangOpt::Tex => "tex", + }; + extension +} + +fn mint_file>(mint: &mut Mint, path: P) { + mint.new_goldenfile(&path) + .unwrap() + .write(&fs::read(path).unwrap()) + .unwrap(); +} + +fn within_temp_dir() -> TempDir { + // Use tmpfs if available + let dir = option_env!("XDG_RUNTIME_DIR") + .map_or_else( + || TempDir::new("task-maker-iospec-test"), + |path| TempDir::new_in(path, "task-maker-iospec-test"), + ) + .unwrap(); + env::set_current_dir(dir.path()).unwrap(); + dir +} diff --git a/task-maker-iospec/tests/specs/atomic_types/IOSPEC b/task-maker-iospec/tests/specs/atomic_types/IOSPEC new file mode 100644 index 000000000..d5a88e173 --- /dev/null +++ b/task-maker-iospec/tests/specs/atomic_types/IOSPEC @@ -0,0 +1,16 @@ +inputln { + item xi32: i32; + item xi64: i64; + item xbool: bool; +} + +outputln { + @call gi32(x=xi32) -> yi32; + item yi32: i32; + + @call gi64(x=xi64) -> yi64; + item yi64: i64; + + @call gbool(x=xbool) -> ybool; + item ybool: bool; +} diff --git a/task-maker-iospec/tests/specs/atomic_types/solution.c b/task-maker-iospec/tests/specs/atomic_types/solution.c new file mode 100644 index 000000000..fd5992a78 --- /dev/null +++ b/task-maker-iospec/tests/specs/atomic_types/solution.c @@ -0,0 +1,13 @@ +#include + +int gi32(int x) { + return x; +} + +long long gi64(long long x) { + return x; +} + +bool gbool(bool x) { + return x; +} diff --git a/task-maker-iospec/tests/specs/atomic_types/solution.cpp b/task-maker-iospec/tests/specs/atomic_types/solution.cpp new file mode 100644 index 000000000..bca171261 --- /dev/null +++ b/task-maker-iospec/tests/specs/atomic_types/solution.cpp @@ -0,0 +1,15 @@ +#include + +using namespace std; + +int gi32(int x) { + return x; +} + +long long gi64(long long x) { + return x; +} + +bool gbool(bool x) { + return x; +} diff --git a/task-maker-iospec/tests/specs/atomic_types/valid.input b/task-maker-iospec/tests/specs/atomic_types/valid.input new file mode 100644 index 000000000..a5c7b77a2 --- /dev/null +++ b/task-maker-iospec/tests/specs/atomic_types/valid.input @@ -0,0 +1 @@ +0 0 0 diff --git a/task-maker-iospec/tests/specs/compilation_errors/undefined_var.iospec b/task-maker-iospec/tests/specs/compilation_errors/undefined_var.iospec new file mode 100644 index 000000000..46f7d3b2c --- /dev/null +++ b/task-maker-iospec/tests/specs/compilation_errors/undefined_var.iospec @@ -0,0 +1 @@ +assume x == 0; diff --git a/task-maker-iospec/tests/specs/segtree/IOSPEC b/task-maker-iospec/tests/specs/segtree/IOSPEC new file mode 100644 index 000000000..03a6cbef4 --- /dev/null +++ b/task-maker-iospec/tests/specs/segtree/IOSPEC @@ -0,0 +1,56 @@ +inputln { + item n: i32; + item q: i32; +} + +inputln { + for i upto n { + item a[i]: i32; + } +} + +// @call init(a=a); + +for i upto q { + inputln { + item op: i32; + + if op == 1 { + item l1: i32; + item r1: i32; + // @call get_sum(l=l1, r=r1) -> s; + } + + + if op == 2 { + item l2: i32; + item r2: i32; + item x2: i32; + // @call add(l=l2, r=r2) -> s; + } + + if op == 3 { + item l3: i32; + item r3: i32; + item x3: i32; + // @call set_range(l=l3, r=r3, x=x3) -> s; + } + + if op == 4 { + item l4: i32; + item r4: i32; + // @call min(l=l4, r=r4) -> s; + } + + if op == 5 { + item l5: i32; + item r5: i32; + item x5: i32; + // @call lower_bound(l=l5, r=r5, x=x5) -> s; + } + } + + outputln { + item s: i32; + } +} diff --git a/task-maker-iospec/tests/specs/segtree/solution.c b/task-maker-iospec/tests/specs/segtree/solution.c new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/specs/segtree/solution.cpp b/task-maker-iospec/tests/specs/segtree/solution.cpp new file mode 100644 index 000000000..b35936f5b --- /dev/null +++ b/task-maker-iospec/tests/specs/segtree/solution.cpp @@ -0,0 +1,4 @@ +#include + +using namespace std; + diff --git a/task-maker-iospec/tests/specs/triangle/IOSPEC b/task-maker-iospec/tests/specs/triangle/IOSPEC new file mode 100644 index 000000000..0bc460c01 --- /dev/null +++ b/task-maker-iospec/tests/specs/triangle/IOSPEC @@ -0,0 +1,11 @@ +inputln { + item N: i32; +} + +for i upto N { + inputln { + for j upto i + 1 { + item A[i][j]: i32; + } + } +} diff --git a/task-maker-iospec/tests/specs/triangle/solution.c b/task-maker-iospec/tests/specs/triangle/solution.c new file mode 100644 index 000000000..e69de29bb diff --git a/task-maker-iospec/tests/specs/triangle/solution.cpp b/task-maker-iospec/tests/specs/triangle/solution.cpp new file mode 100644 index 000000000..b35936f5b --- /dev/null +++ b/task-maker-iospec/tests/specs/triangle/solution.cpp @@ -0,0 +1,4 @@ +#include + +using namespace std; + diff --git a/task-maker-iospec/tests/specs/triangle/valid.input b/task-maker-iospec/tests/specs/triangle/valid.input new file mode 100644 index 000000000..acb6cfb48 --- /dev/null +++ b/task-maker-iospec/tests/specs/triangle/valid.input @@ -0,0 +1,5 @@ +4 +1 +2 3 +4 5 6 +7 8 9 10 diff --git a/task-maker-iospec/tests/specs/weighted_graph/IOSPEC b/task-maker-iospec/tests/specs/weighted_graph/IOSPEC new file mode 100644 index 000000000..81fc85e15 --- /dev/null +++ b/task-maker-iospec/tests/specs/weighted_graph/IOSPEC @@ -0,0 +1,51 @@ +// Comments + +inputln { + /// Number of nodes in the graph + /// Second single-line doc comment + /** + Block doc comment + */ + /** Block doc comment + Block doc comment + */ + item N: i32; + item M: i32; +} + +assume 2 <= N < 100_000; +assume 0 <= M < 500_000; + +#[cfg(subtask_name = "quadratic")] +assume N <= 1_000; + +inputln { + for u upto N { + item W[u]: i32; + assume 0 <= W[u] < 1_000_000_000; + } +} + +for i upto M { + inputln { + item A[i]: i32; + item B[i]: i32; + } + + assume 0 <= A[i] < N; + assume 0 <= B[i] < N; +} + +outputln { + @call f(N = N, M = M) -> S; + /// Answer + item S: i32; +} + +outputln { + @resize X to N; + @call g(N = N, M = &M, A = &A, B = B, X = &X); + for u upto N { + item X[u]: i32; + } +} diff --git a/task-maker-iospec/tests/specs/weighted_graph/solution.c b/task-maker-iospec/tests/specs/weighted_graph/solution.c new file mode 100644 index 000000000..76cccdb0c --- /dev/null +++ b/task-maker-iospec/tests/specs/weighted_graph/solution.c @@ -0,0 +1,7 @@ +int f(int N, int M) { + return 42; +} + +void g(int N, int* M, int* A, int* B, int* X) { + for(int i = 0; i < N; i++) X[i] = i; +} diff --git a/task-maker-iospec/tests/specs/weighted_graph/solution.cpp b/task-maker-iospec/tests/specs/weighted_graph/solution.cpp new file mode 100644 index 000000000..2dbfd0473 --- /dev/null +++ b/task-maker-iospec/tests/specs/weighted_graph/solution.cpp @@ -0,0 +1,11 @@ +#include + +using namespace std; + +int f(int N, int M) { + return 42; +} + +void g(int N, int& M, vector& A, vector B, vector& X) { + for(int i = 0; i < N; i++) X[i] = i; +} diff --git a/task-maker-iospec/tests/specs/weighted_graph/valid1.input b/task-maker-iospec/tests/specs/weighted_graph/valid1.input new file mode 100644 index 000000000..10b62b0aa --- /dev/null +++ b/task-maker-iospec/tests/specs/weighted_graph/valid1.input @@ -0,0 +1,7 @@ +4 5 +10 11 12 13 +0 1 +1 2 +0 2 +0 3 +2 3