diff --git a/Cargo.toml b/Cargo.toml index 3e53bb3..1b4c2ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.64" +pest = "2.3.0" +pest_derive = "2.3.0" diff --git a/scripts/grade-04.sh b/scripts/grade-04.sh new file mode 100755 index 0000000..7056f53 --- /dev/null +++ b/scripts/grade-04.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +set -e +set -uo pipefail +IFS=$'\n\t' + +# Imports library. +BASEDIR=$(dirname "$0") +source $BASEDIR/grade-utils.sh + +RUNNERS=( + "cargo" + "cargo --release" + "cargo_asan" + "cargo_asan --release" + "cargo_tsan" + "cargo_tsan --release" +) + +# Lints. +cargo fmt --check +cargo clippy + +# Executes test for each runner. +for RUNNER in "${RUNNERS[@]}"; do + echo "Running with $RUNNER..." + + TESTS=("--lib assignment04_grade") + if [ $(run_tests) -ne 0 ]; then + exit 1 + fi +done + +exit 0 diff --git a/scripts/prepare-submissions.sh b/scripts/prepare-submissions.sh new file mode 100755 index 0000000..3a8e550 --- /dev/null +++ b/scripts/prepare-submissions.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +BASEDIR=$(dirname "$0")/.. + +mkdir -p $BASEDIR/target + +zip -rj $BASEDIR/target/assignment04.zip src/assignments/assignment04 diff --git a/src/assignments/assignment04/context.rs b/src/assignments/assignment04/context.rs new file mode 100644 index 0000000..a34991d --- /dev/null +++ b/src/assignments/assignment04/context.rs @@ -0,0 +1,46 @@ +//! Calculator. + +use std::collections::HashMap; + +use anyhow::*; + +use super::syntax::{Command, Expression}; + +/// Calculator's context. +#[derive(Debug, Default, Clone)] +pub struct Context { + anonymous_counter: usize, + variables: HashMap, +} + +impl Context { + /// Creates a new context. + pub fn new() -> Self { + Self::default() + } + + /// Returns the current anonymous variable counter. + pub fn current_counter(&self) -> usize { + self.anonymous_counter + } + + /// Calculates the given expression. + pub fn calc_expression(&self, expression: &Expression) -> Result { + todo!("fill here") + } + + /// Calculates the given command. + /// + /// If there is no variable lhs in the command (i.e. `command.variable = None`), its value should be stored at `$0`, `$1`, `$2`, ... respectively. + /// + /// # Example + /// + /// After calculating commad `3 + 5` => Context's variables = `{($0,8)}` + /// + /// After calculating commad `v = 3 - 2` => Context's variables = `{($0,8),(v,1))}` + /// + /// After calculating commad `3 ^ 2` => Context's variables = `{($0,8),(v,1),($1,9)}` + pub fn calc_command(&mut self, command: &Command) -> Result<(String, f64)> { + todo!("fill here") + } +} diff --git a/src/assignments/assignment04/mod.rs b/src/assignments/assignment04/mod.rs new file mode 100644 index 0000000..7c6a681 --- /dev/null +++ b/src/assignments/assignment04/mod.rs @@ -0,0 +1,20 @@ +//! Assignment 4: Designing a calculator. +//! +//! The primary goal of this assignment is twofold: +//! (1) understanding the `pest` third-party library from documentations; and +//! (2) using programming concepts you've learned so far to implement a simple arithmetic calculator. +//! +//! For `pest`, read the following documentations (that contain 90% of the solution): +//! - +//! - +//! - +//! +//! For calculator, just reading `syntax.rs` would suffice for you to understand what to do. +//! +//! You should fill out the `todo!()` placeholders in such a way that `/scripts/grade-04.sh` works fine. +//! See `assignment04_grade.rs` and `/scripts/grade-04.sh` for the test script. +//! Run `/scripts/prepare-submissions.sh` and submit `/target/assignment04.zip` to . + +pub mod context; +pub mod parser; +pub mod syntax; diff --git a/src/assignments/assignment04/parser.rs b/src/assignments/assignment04/parser.rs new file mode 100644 index 0000000..599094e --- /dev/null +++ b/src/assignments/assignment04/parser.rs @@ -0,0 +1,19 @@ +//! Parser. + +use super::syntax::*; +use anyhow::Result; + +#[allow(missing_docs)] +#[allow(missing_debug_implementations)] +mod inner { + use pest_derive::*; + + #[derive(Parser)] + #[grammar = "assignments/assignment04/syntax.pest"] + pub(crate) struct SyntaxParser; +} + +/// Parses command. +pub fn parse_command(line: &str) -> Result { + todo!("fill here") +} diff --git a/src/assignments/assignment04/syntax.pest b/src/assignments/assignment04/syntax.pest new file mode 100644 index 0000000..ea60093 --- /dev/null +++ b/src/assignments/assignment04/syntax.pest @@ -0,0 +1,17 @@ +num = @{ int ~ ("." ~ ASCII_DIGIT*)? ~ (^"e" ~ int)? } +int = { ("+" | "-")? ~ ASCII_DIGIT+ } +var = { ("$" | ASCII_ALPHA) ~ (ASCII_ALPHA | ASCII_DIGIT)* } + +operation = _{ add | subtract | multiply | divide | power } + add = { "+" } + subtract = { "-" } + multiply = { "*" } + divide = { "/" } + power = { "^" } + +expr = { term ~ (operation ~ term)* } +term = _{ num | var | "(" ~ expr ~ ")" } + +command = _{ SOI ~ (var ~ "=")? ~ expr ~ EOI } + +WHITESPACE = _{ " " | "\t" } diff --git a/src/assignments/assignment04/syntax.rs b/src/assignments/assignment04/syntax.rs new file mode 100644 index 0000000..9f5ddd4 --- /dev/null +++ b/src/assignments/assignment04/syntax.rs @@ -0,0 +1,43 @@ +//! Syntax. + +/// Command of the form "" or " = ". +#[derive(Debug, Clone, PartialEq)] +pub struct Command { + /// Variable (lhs). + pub variable: Option, + /// Expression (rhs). + pub expression: Expression, +} + +/// Binary operators. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BinOp { + /// Add. + Add, + /// Subtract. + Subtract, + /// Multiply. + Multiply, + /// Divide. + Divide, + /// Power. + Power, +} + +/// Expression. +#[derive(Debug, Clone, PartialEq)] +pub enum Expression { + /// Number. + Num(f64), + /// Variable. + Variable(String), + /// Binary operation. + BinOp { + /// Operator. + op: BinOp, + /// Lhs. + lhs: Box, + /// Rhs. + rhs: Box, + }, +} diff --git a/src/assignments/assignment04_grade.rs b/src/assignments/assignment04_grade.rs new file mode 100644 index 0000000..64ddf35 --- /dev/null +++ b/src/assignments/assignment04_grade.rs @@ -0,0 +1,315 @@ +#[cfg(test)] +mod test { + use crate::assignments::assignment04::syntax::*; + + use super::super::assignment04::*; + + #[test] + fn test_parse() { + assert_eq!( + parser::parse_command("$1 = (132 + 77) * 3 ^ 8").unwrap(), + Command { + variable: Some("$1".into()), + expression: Expression::BinOp { + op: BinOp::Multiply, + lhs: Expression::BinOp { + op: BinOp::Add, + lhs: Expression::Num(132.0).into(), + rhs: Expression::Num(77.0).into(), + } + .into(), + rhs: Expression::BinOp { + op: BinOp::Power, + lhs: Expression::Num(3.0).into(), + rhs: Expression::Num(8.0).into(), + } + .into(), + } + } + ); + + assert_eq!( + parser::parse_command("132 + 77").unwrap(), + Command { + variable: None, + expression: Expression::BinOp { + op: BinOp::Add, + lhs: Expression::Num(132.0).into(), + rhs: Expression::Num(77.0).into(), + } + } + ); + + assert_eq!( + parser::parse_command("v2 = 132 + 77").unwrap(), + Command { + variable: Some("v2".into()), + expression: Expression::BinOp { + op: BinOp::Add, + lhs: Expression::Num(132.0).into(), + rhs: Expression::Num(77.0).into(), + } + } + ); + + assert!(parser::parse_command("132 +!s 77").is_err()); + + assert_eq!( + parser::parse_command("12 - 34 + 23 ^ 4").unwrap(), + Command { + variable: None, + expression: Expression::BinOp { + op: BinOp::Add, + lhs: Expression::BinOp { + op: BinOp::Subtract, + lhs: Expression::Num(12.0).into(), + rhs: Expression::Num(34.0).into(), + } + .into(), + rhs: Expression::BinOp { + op: BinOp::Power, + lhs: Expression::Num(23.0).into(), + rhs: Expression::Num(4.0).into(), + } + .into(), + }, + } + ); + } + + #[test] + fn test_context_calc_expression() { + let ctx = context::Context::new(); + + // "(132 + 77) * 3 ^ 8" + assert_eq!( + ctx.calc_expression(&Expression::BinOp { + op: BinOp::Multiply, + lhs: Expression::BinOp { + op: BinOp::Add, + lhs: Expression::Num(132.0).into(), + rhs: Expression::Num(77.0).into(), + } + .into(), + rhs: Expression::BinOp { + op: BinOp::Power, + lhs: Expression::Num(3.0).into(), + rhs: Expression::Num(8.0).into(), + } + .into(), + }) + .expect("calculate expression is failed"), + 1371249.0 + ); + + // "132 + 77" + assert_eq!( + ctx.calc_expression(&Expression::BinOp { + op: BinOp::Add, + lhs: Expression::Num(132.0).into(), + rhs: Expression::Num(77.0).into(), + }) + .expect("calculate expression is failed"), + 209.0 + ); + + // "v + 77" + assert!(ctx + .calc_expression(&Expression::BinOp { + op: BinOp::Add, + lhs: Expression::Variable("v".into()).into(), + rhs: Expression::Num(77.0).into(), + }) + .is_err()); + + // "3 / (3 * 4 - 12)" + assert!(ctx + .calc_expression(&Expression::BinOp { + op: BinOp::Divide, + lhs: Expression::Num(3.0).into(), + rhs: Expression::BinOp { + op: BinOp::Subtract, + lhs: Expression::BinOp { + op: BinOp::Multiply, + lhs: Expression::Num(3.0).into(), + rhs: Expression::Num(4.0).into(), + } + .into(), + rhs: Expression::Num(12.0).into(), + } + .into(), + }) + .is_err()); + + // "12 - 34 + 23 ^ 4" + assert_eq!( + ctx.calc_expression(&Expression::BinOp { + op: BinOp::Add, + lhs: Expression::BinOp { + op: BinOp::Subtract, + lhs: Expression::Num(12.0).into(), + rhs: Expression::Num(34.0).into(), + } + .into(), + rhs: Expression::BinOp { + op: BinOp::Power, + lhs: Expression::Num(23.0).into(), + rhs: Expression::Num(4.0).into(), + } + .into(), + },) + .expect("calculate expression is failed"), + 279819.0 + ); + } + + #[test] + fn test_context_calc_command() { + let mut ctx = context::Context::new(); + + // "v1 = 132 + 77" + assert_eq!( + ctx.calc_command(&Command { + variable: Some("v1".into()), + expression: Expression::BinOp { + op: BinOp::Add, + lhs: Expression::Num(132.0).into(), + rhs: Expression::Num(77.0).into(), + } + }) + .unwrap(), + ("v1".into(), 209.0) + ); + + // "1 - 3 + 2 ^ 4" + assert_eq!( + ctx.calc_command(&Command { + variable: None, + expression: Expression::BinOp { + op: BinOp::Add, + lhs: Expression::BinOp { + op: BinOp::Subtract, + lhs: Expression::Num(1.0).into(), + rhs: Expression::Num(3.0).into(), + } + .into(), + rhs: Expression::BinOp { + op: BinOp::Power, + lhs: Expression::Num(2.0).into(), + rhs: Expression::Num(4.0).into(), + } + .into(), + }, + }) + .unwrap(), + ("$0".into(), 14.0) + ); + + // "v2 = v1 * 3 + $0" + assert_eq!( + ctx.calc_command(&Command { + variable: Some("v2".into()), + expression: Expression::BinOp { + op: BinOp::Add, + lhs: Expression::BinOp { + op: BinOp::Multiply, + lhs: Expression::Variable("v1".into()).into(), + rhs: Expression::Num(3.0).into(), + } + .into(), + rhs: Expression::Variable("$0".into()).into() + } + }) + .unwrap(), + ("v2".into(), 641.0) + ); + + // "5 / 2" + assert_eq!( + ctx.calc_command(&Command { + variable: None, + expression: Expression::BinOp { + op: BinOp::Divide, + lhs: Expression::Num(5.0).into(), + rhs: Expression::Num(2.0).into(), + }, + }) + .unwrap(), + ("$1".into(), 2.5) + ); + + // "v2 = v2 ^ 2" + assert_eq!( + ctx.calc_command(&Command { + variable: Some("v2".into()), + expression: Expression::BinOp { + op: BinOp::Power, + lhs: Expression::Variable("v2".into()).into(), + rhs: Expression::Num(2.0).into(), + } + }) + .unwrap(), + ("v2".into(), 410881.0) + ); + + // "v2 = v2 - $1" + assert_eq!( + ctx.calc_command(&Command { + variable: Some("v2".into()), + expression: Expression::BinOp { + op: BinOp::Subtract, + lhs: Expression::Variable("v2".into()).into(), + rhs: Expression::Variable("$1".into()).into(), + } + }) + .unwrap(), + ("v2".into(), 410878.5) + ); + + // "v2 = v2 / $3" + assert!(ctx + .calc_command(&Command { + variable: Some("v2".into()), + expression: Expression::BinOp { + op: BinOp::Divide, + lhs: Expression::Variable("v2".into()).into(), + rhs: Expression::Variable("$3".into()).into(), + } + }) + .is_err()); + + // "v3 = 3 / (3 * 4 - 12)" + assert!(ctx + .calc_command(&Command { + variable: Some("v3".into()), + expression: Expression::BinOp { + op: BinOp::Divide, + lhs: Expression::Num(3.0).into(), + rhs: Expression::BinOp { + op: BinOp::Subtract, + lhs: Expression::BinOp { + op: BinOp::Multiply, + lhs: Expression::Num(3.0).into(), + rhs: Expression::Num(4.0).into(), + } + .into(), + rhs: Expression::Num(12.0).into(), + } + .into(), + } + }) + .is_err()); + + // v3 = v3 - v2 + assert!(ctx + .calc_command(&Command { + variable: Some("v3".into()), + expression: Expression::BinOp { + op: BinOp::Subtract, + lhs: Expression::Variable("v3".into()).into(), + rhs: Expression::Variable("v2".into()).into(), + } + }) + .is_err()); + } +} diff --git a/src/assignments/mod.rs b/src/assignments/mod.rs index a80e628..79c93f5 100644 --- a/src/assignments/mod.rs +++ b/src/assignments/mod.rs @@ -9,3 +9,5 @@ pub mod assignment02; mod assignment02_grade; pub mod assignment03; mod assignment03_grade; +pub mod assignment04; +mod assignment04_grade; diff --git a/src/lib.rs b/src/lib.rs index d7a4948..9451db6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ // # Tries to deny all lints (`rustc -W help`). #![deny(absolute_paths_not_starting_with_crate)] #![deny(anonymous_parameters)] -#![deny(box_pointers)] +// #![deny(box_pointers)] #![deny(deprecated_in_future)] #![deny(explicit_outlives_requirements)] #![deny(keyword_idents)]