├── .gitignore ├── Chapter01 ├── use_env_logger │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── use_lazy_static │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── use_rand │ ├── Cargo.toml │ └── src │ │ └── main.rs └── use_structopt │ ├── Cargo.toml │ ├── README.md │ └── src │ └── main.rs ├── Chapter02 ├── data │ ├── config.toml │ ├── sales.json │ └── sales.xml ├── json_dynamic │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── json_static │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── postgresql_example │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── redis_example │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── sqlite_example │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── toml_dynamic │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── toml_static │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── transformer │ ├── Cargo.toml │ └── src │ │ └── main.rs └── xml_example │ ├── Cargo.toml │ └── src │ └── main.rs ├── Chapter03 ├── file_transfer │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── file_transfer_stub │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── json_db │ ├── Cargo.toml │ └── src │ │ ├── db_access.rs │ │ └── main.rs └── memory_db │ ├── Cargo.toml │ └── src │ ├── db_access.rs │ └── main.rs ├── Chapter04 ├── auth │ ├── Cargo.toml │ ├── src │ │ ├── db_access.rs │ │ ├── favicon.ico │ │ └── main.rs │ └── templates │ │ ├── login.html │ │ ├── main.html │ │ ├── main.js │ │ ├── one_person.html │ │ └── persons.html ├── crud │ ├── Cargo.toml │ ├── src │ │ ├── db_access.rs │ │ ├── favicon.ico │ │ └── main.rs │ └── templates │ │ ├── main.html │ │ ├── main.js │ │ ├── one_person.html │ │ └── persons.html ├── list │ ├── Cargo.toml │ ├── src │ │ ├── db_access.rs │ │ ├── favicon.ico │ │ └── main.rs │ └── templates │ │ ├── main.html │ │ ├── main.js │ │ └── persons.html └── templ │ ├── Cargo.toml │ ├── src │ └── main.rs │ └── templates │ ├── footer.txt │ ├── templ_id.txt │ └── templ_names.txt ├── Chapter05 ├── README.md ├── adder │ ├── Cargo.toml │ ├── src │ │ └── lib.rs │ └── static │ │ └── index.html ├── incr │ ├── Cargo.toml │ ├── src │ │ └── lib.rs │ └── static │ │ └── index.html ├── login │ ├── Cargo.toml │ ├── src │ │ ├── db_access.rs │ │ ├── lib.rs │ │ └── login.rs │ └── static │ │ └── index.html ├── persons_db │ ├── Cargo.toml │ └── src │ │ ├── db_access.rs │ │ └── main.rs ├── yauth │ ├── Cargo.toml │ ├── src │ │ ├── db_access.rs │ │ ├── lib.rs │ │ ├── login.rs │ │ ├── one_person.rs │ │ └── persons_list.rs │ └── static │ │ └── index.html └── yclient │ ├── Cargo.toml │ ├── src │ ├── common.rs │ ├── lib.rs │ ├── login.rs │ ├── one_person.rs │ └── persons_list.rs │ └── static │ └── index.html ├── Chapter06 ├── assets_slalom │ ├── Cargo.toml │ ├── src │ │ └── main.rs │ └── static │ │ ├── bump.ogg │ │ ├── click.ogg │ │ ├── font.ttf │ │ ├── two_notes.ogg │ │ └── whoosh.ogg ├── silent_slalom │ ├── Cargo.toml │ └── src │ │ └── main.rs └── ski │ ├── Cargo.toml │ └── src │ └── main.rs ├── Chapter07 ├── gg_assets_slalom │ ├── Cargo.toml │ ├── src │ │ └── main.rs │ └── static │ │ ├── bump.ogg │ │ ├── click.ogg │ │ ├── font.ttf │ │ ├── two_notes.ogg │ │ └── whoosh.ogg ├── gg_silent_slalom │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── gg_ski │ ├── Cargo.toml │ └── src │ │ └── main.rs └── gg_whac │ ├── Cargo.toml │ ├── assets │ ├── bump.ogg │ ├── button.png │ ├── click.ogg │ ├── cry.ogg │ ├── font.ttf │ ├── lawn.jpg │ ├── mallet.png │ ├── mole.png │ └── two_notes.ogg │ └── src │ └── main.rs ├── Chapter08 ├── calc_analyzer │ ├── Cargo.toml │ ├── data │ │ ├── bad_sum.calc │ │ └── sum.calc │ └── src │ │ ├── analyzer.rs │ │ ├── main.rs │ │ ├── parser.rs │ │ └── symbol_table.rs ├── calc_compiler │ ├── Cargo.toml │ ├── data │ │ ├── bad_sum.calc │ │ └── sum.calc │ └── src │ │ ├── analyzer.rs │ │ ├── compiler.rs │ │ ├── executor.rs │ │ ├── main.rs │ │ ├── parser.rs │ │ └── symbol_table.rs ├── calc_interpreter │ ├── Cargo.toml │ └── src │ │ ├── analyzer.rs │ │ ├── executor.rs │ │ ├── main.rs │ │ ├── parser.rs │ │ └── symbol_table.rs └── calc_parser │ ├── Cargo.toml │ ├── data │ ├── bad_sum.calc │ └── sum.calc │ └── src │ ├── main.rs │ └── parser.rs ├── Chapter09 ├── nom_byte_machine │ ├── Cargo.toml │ └── src │ │ ├── emulator.rs │ │ ├── instructions.rs │ │ ├── main.rs │ │ ├── parsing_interpreter.rs │ │ └── translator.rs ├── nom_disassembler │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── word_machine_convert │ ├── Cargo.toml │ └── src │ │ └── main.rs └── word_machine_sieve │ ├── Cargo.toml │ └── src │ └── main.rs ├── Chapter10 ├── allocating │ ├── Cargo.toml │ ├── Makefile │ ├── bd │ ├── br │ └── src │ │ └── lib.rs ├── boilerplate │ ├── Cargo.toml │ ├── Makefile │ ├── bd │ ├── br │ └── src │ │ └── lib.rs ├── dots │ ├── Cargo.toml │ ├── Makefile │ ├── bd │ ├── br │ └── src │ │ └── lib.rs ├── linux-fw │ ├── .gitignore │ ├── .travis.yml │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── armv7l-linux-kernel-module.json │ ├── build.rs │ ├── kernel-cflags-finder │ │ └── Makefile │ ├── src │ │ ├── allocator.rs │ │ ├── bindgen_helper.h │ │ ├── bindings.rs │ │ ├── c_types.rs │ │ ├── c_wrapper.c │ │ ├── kernel.rs │ │ ├── kernel_module.rs │ │ ├── kernel_result.rs │ │ ├── lib.rs │ │ ├── panic.rs │ │ ├── printk.rs │ │ └── sync.rs │ └── x86_64-linux-kernel-module.json └── state │ ├── Cargo.toml │ ├── Makefile │ ├── bd │ ├── br │ └── src │ └── lib.rs ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /Chapter01/use_env_logger/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "use_env_logger" 3 | version = "0.1.0" 4 | authors = ["carlomilanesi"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | log = "0.4" 9 | env_logger = "0.6" 10 | -------------------------------------------------------------------------------- /Chapter01/use_env_logger/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | 4 | fn main() { 5 | env_logger::init(); 6 | error!("Error message"); 7 | warn!("Warning message"); 8 | info!("Information message"); 9 | debug!("Debugging message"); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter01/use_lazy_static/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "use_lazy_static" 3 | version = "0.1.0" 4 | authors = ["carlomilanesi"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | lazy_static = "1.2" 9 | -------------------------------------------------------------------------------- /Chapter01/use_lazy_static/src/main.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use std::collections::HashMap; 3 | 4 | lazy_static! { 5 | static ref DICTIONARY: HashMap = { 6 | let mut m = HashMap::new(); 7 | m.insert(11, "foo"); 8 | m.insert(12, "bar"); 9 | println!("Initialized"); 10 | m 11 | }; 12 | } 13 | 14 | fn main() { 15 | println!("Started"); 16 | println!("DICTIONARY contains {:?}", *DICTIONARY); 17 | println!("DICTIONARY contains {:?}", *DICTIONARY); 18 | } 19 | -------------------------------------------------------------------------------- /Chapter01/use_rand/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "use_rand" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rand = "0.6" 9 | -------------------------------------------------------------------------------- /Chapter01/use_rand/src/main.rs: -------------------------------------------------------------------------------- 1 | // Declare basic functions for pseudo-random number generators. 2 | use rand::prelude::*; 3 | 4 | fn main() { 5 | // Create a pseudo-Random Number Generator for the current thread 6 | let mut rng = thread_rng(); 7 | 8 | // Print an integer number 9 | // between 0 (included) and 20 (excluded). 10 | println!("{}", rng.gen_range(0, 20)); 11 | 12 | // Print a floating-point number 13 | // between 0 (included) and 1 (excluded). 14 | println!("{}", rng.gen::()); 15 | 16 | // Generate a Boolean. 17 | println!("{}", if rng.gen() { "Heads" } else { "Tails" }); 18 | } 19 | -------------------------------------------------------------------------------- /Chapter01/use_structopt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "use_structopt" 3 | version = "0.1.0" 4 | authors = ["carlomilanesi"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | structopt = "0.2" 9 | -------------------------------------------------------------------------------- /Chapter01/use_structopt/README.md: -------------------------------------------------------------------------------- 1 | Run the following command: 2 | ``` 3 | cargo run input1.txt input2.txt -v --result res.xyz 4 | ``` 5 | to get: 6 | ``` 7 | Opt { 8 | verbose: true, 9 | result_file: "res.txt", 10 | files: [ 11 | "input1.tx", 12 | "input2.txt" 13 | ] 14 | } 15 | ``` 16 | -------------------------------------------------------------------------------- /Chapter01/use_structopt/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use structopt::StructOpt; 3 | 4 | #[derive(StructOpt, Debug)] 5 | struct Opt { 6 | /// Activate verbose mode 7 | #[structopt(short = "v", long = "verbose")] 8 | verbose: bool, 9 | 10 | /// File to generate 11 | #[structopt(short = "r", long = "result", parse(from_os_str))] 12 | result_file: PathBuf, 13 | 14 | /// Files to process 15 | #[structopt(name = "FILE", parse(from_os_str))] 16 | files: Vec, 17 | } 18 | 19 | fn main() { 20 | println!("{:#?}", Opt::from_args()); 21 | } 22 | -------------------------------------------------------------------------------- /Chapter02/data/config.toml: -------------------------------------------------------------------------------- 1 | [input] 2 | xml_file = "../data/sales.xml" 3 | json_file = "../data/sales.json" 4 | 5 | [redis] 6 | host = "localhost" 7 | 8 | [sqlite] 9 | db_file = "../data/sales.db" 10 | 11 | [postgresql] 12 | username = "postgres" 13 | password = "post" 14 | host = "localhost" 15 | port = "5432" 16 | database = "Rust2018" 17 | 18 | -------------------------------------------------------------------------------- /Chapter02/data/sales.json: -------------------------------------------------------------------------------- 1 | { 2 | "products": [ 3 | { 4 | "id": 591, 5 | "category": "fruit", 6 | "name": "orange" 7 | }, 8 | { 9 | "id": 190, 10 | "category": "furniture", 11 | "name": "chair" 12 | } 13 | ], 14 | "sales": [ 15 | { 16 | "id": "2020-7110", 17 | "product_id": 190, 18 | "date": 1234527890, 19 | "quantity": 2.0, 20 | "unit": "u." 21 | }, 22 | { 23 | "id": "2020-2871", 24 | "product_id": 591, 25 | "date": 1234567590, 26 | "quantity": 2.14, 27 | "unit": "Kg" 28 | }, 29 | { 30 | "id": "2020-2583", 31 | "product_id": 190, 32 | "date": 1234563890, 33 | "quantity": 4.0, 34 | "unit": "u." 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /Chapter02/data/sales.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 862 5 | fruit 6 | cherry 7 | 8 | 9 | 10 | 236 11 | furniture 12 | table 13 | 14 | 15 | 16 | 2020-3987 17 | 862 18 | 1238563890 19 | 0.753 20 | Kg 21 | 22 | 23 | 24 | 2020-3992 25 | 236 26 | 1238567890 27 | 1 28 | u. 29 | 30 | 31 | -------------------------------------------------------------------------------- /Chapter02/json_dynamic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json_dynamic" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | serde = "1.0" 9 | serde_derive = "1.0" 10 | serde_json = "1.0" 11 | -------------------------------------------------------------------------------- /Chapter02/json_dynamic/src/main.rs: -------------------------------------------------------------------------------- 1 | use serde_json::{Number, Value}; 2 | 3 | fn main() { 4 | // Get the filenames from the command line. 5 | let input_path = std::env::args().nth(1).unwrap(); 6 | let output_path = std::env::args().nth(2).unwrap(); 7 | 8 | let mut sales_and_products = { 9 | // Load the first file into a string. 10 | let sales_and_products_text = std::fs::read_to_string(&input_path).unwrap(); 11 | 12 | // Parse the string into a dynamically-typed JSON structure. 13 | serde_json::from_str::(&sales_and_products_text).unwrap() 14 | }; 15 | 16 | // Get the field of the structure 17 | // containing the weight of the sold oranges. 18 | if let Value::Number(n) = &sales_and_products["sales"][1]["quantity"] { 19 | // Increment it and store it back into the structure. 20 | sales_and_products["sales"][1]["quantity"] = 21 | Value::Number(Number::from_f64(n.as_f64().unwrap() + 1.5).unwrap()); 22 | } 23 | 24 | // Save the JSON structure into the other file. 25 | std::fs::write( 26 | output_path, 27 | serde_json::to_string_pretty(&sales_and_products).unwrap(), 28 | ) 29 | .unwrap(); 30 | } 31 | -------------------------------------------------------------------------------- /Chapter02/json_static/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json_static" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | serde = "1.0" 9 | serde_derive = "1.0" 10 | serde_json = "1.0" 11 | -------------------------------------------------------------------------------- /Chapter02/json_static/src/main.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::{Deserialize, Serialize}; 2 | 3 | #[derive(Deserialize, Serialize, Debug)] 4 | struct Product { 5 | id: u32, 6 | category: String, 7 | name: String, 8 | } 9 | 10 | #[derive(Deserialize, Serialize, Debug)] 11 | struct Sale { 12 | id: String, 13 | product_id: u32, 14 | date: i64, 15 | quantity: f64, 16 | unit: String, 17 | } 18 | 19 | #[derive(Deserialize, Serialize, Debug)] 20 | struct SalesAndProducts { 21 | products: Vec, 22 | sales: Vec, 23 | } 24 | 25 | fn main() -> Result<(), std::io::Error> { 26 | let input_path = std::env::args().nth(1).unwrap(); 27 | let output_path = std::env::args().nth(2).unwrap(); 28 | let mut sales_and_products = { 29 | let sales_and_products_text = std::fs::read_to_string(&input_path)?; 30 | 31 | // 1. Load the sale structure from the string. 32 | serde_json::from_str::(&sales_and_products_text).unwrap() 33 | }; 34 | 35 | // Increment the weight of the sold oranges. 36 | sales_and_products.sales[1].quantity += 1.5; 37 | 38 | std::fs::write( 39 | output_path, 40 | serde_json::to_string_pretty(&sales_and_products).unwrap(), 41 | )?; 42 | 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /Chapter02/postgresql_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "postgresql_write" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | postgres = "0.17" 9 | -------------------------------------------------------------------------------- /Chapter02/postgresql_example/src/main.rs: -------------------------------------------------------------------------------- 1 | use postgres::{error::Error, Client, NoTls}; 2 | 3 | #[derive(Debug)] 4 | struct SaleWithProduct { 5 | category: String, 6 | name: String, 7 | quantity: f64, 8 | unit: String, 9 | date: i64, 10 | } 11 | 12 | fn create_db() -> Result { 13 | let username = "postgres"; 14 | let password = "post"; 15 | let host = "localhost"; 16 | let port = "5432"; 17 | let database = "Rust2018"; 18 | let mut conn = Client::connect( 19 | &format!( 20 | "postgres://{}{}{}@{}{}{}{}{}", 21 | username, 22 | if password.is_empty() { "" } else { ":" }, 23 | password, 24 | host, 25 | if port.is_empty() { "" } else { ":" }, 26 | port, 27 | if database.is_empty() { "" } else { "/" }, 28 | database 29 | ), 30 | NoTls, 31 | )?; 32 | let _ = conn.execute("DROP TABLE Sales", &[]); 33 | let _ = conn.execute("DROP TABLE Products", &[]); 34 | conn.execute( 35 | "CREATE TABLE Products ( 36 | id INTEGER PRIMARY KEY, 37 | category TEXT NOT NULL, 38 | name TEXT NOT NULL UNIQUE)", 39 | &[], 40 | )?; 41 | conn.execute( 42 | "CREATE TABLE Sales ( 43 | id TEXT PRIMARY KEY, 44 | product_id INTEGER NOT NULL REFERENCES Products, 45 | sale_date BIGINT NOT NULL, 46 | quantity DOUBLE PRECISION NOT NULL, 47 | unit TEXT NOT NULL)", 48 | &[], 49 | )?; 50 | Ok(conn) 51 | } 52 | 53 | fn populate_db(conn: &mut Client) -> Result<(), Error> { 54 | conn.execute( 55 | "INSERT INTO Products ( 56 | id, category, name 57 | ) VALUES ($1, $2, $3)", 58 | &[&1, &"fruit", &"pears"], 59 | )?; 60 | conn.execute( 61 | "INSERT INTO Sales ( 62 | id, product_id, sale_date, quantity, unit 63 | ) VALUES ($1, $2, $3, $4, $5)", 64 | &[&"2020-183", &1, &1_234_567_890_i64, &7.439, &"Kg"], 65 | )?; 66 | Ok(()) 67 | } 68 | 69 | fn print_db(conn: &mut Client) -> Result<(), Error> { 70 | for row in &conn.query( 71 | "SELECT p.name, s.unit, s.quantity, s.sale_date 72 | FROM Sales s 73 | LEFT JOIN Products p 74 | ON p.id = s.product_id 75 | ORDER BY s.sale_date", 76 | &[], 77 | )? { 78 | let sale_with_product = SaleWithProduct { 79 | category: "".to_string(), 80 | name: row.get(0), 81 | quantity: row.get(2), 82 | unit: row.get(1), 83 | date: row.get(3), 84 | }; 85 | println!( 86 | "At instant {}, {} {} of {} were sold.", 87 | sale_with_product.date, 88 | sale_with_product.quantity, 89 | sale_with_product.unit, 90 | sale_with_product.name 91 | ); 92 | } 93 | Ok(()) 94 | } 95 | 96 | fn main() -> Result<(), Error> { 97 | let mut conn = create_db()?; 98 | populate_db(&mut conn)?; 99 | print_db(&mut conn)?; 100 | Ok(()) 101 | } 102 | -------------------------------------------------------------------------------- /Chapter02/redis_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redis_example" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | redis = "0.16" 9 | -------------------------------------------------------------------------------- /Chapter02/redis_example/src/main.rs: -------------------------------------------------------------------------------- 1 | use redis::Commands; 2 | 3 | fn main() -> redis::RedisResult<()> { 4 | let client = redis::Client::open("redis://localhost/")?; 5 | let mut conn = client.get_connection()?; 6 | 7 | conn.set("aKey", "a string")?; 8 | conn.set("anotherKey", 4567)?; 9 | conn.set(45, 12345)?; 10 | 11 | println!( 12 | "{}, {}, {}, {:?}, {}.", 13 | conn.get::<_, String>("aKey")?, 14 | conn.get::<_, u64>("anotherKey")?, 15 | conn.get::<_, u16>(45)?, 16 | conn.get::<_, String>(40), 17 | conn.exists::<_, bool>(40)? 18 | ); 19 | 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /Chapter02/sqlite_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlite_example" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rusqlite = "0.23" 9 | -------------------------------------------------------------------------------- /Chapter02/sqlite_example/src/main.rs: -------------------------------------------------------------------------------- 1 | use rusqlite::{params, Connection, Result}; 2 | 3 | #[derive(Debug)] 4 | struct SaleWithProduct { 5 | category: String, 6 | name: String, 7 | quantity: f64, 8 | unit: String, 9 | date: i64, 10 | } 11 | 12 | fn create_db() -> Result { 13 | let database_file = "sales.db"; 14 | let conn = Connection::open(database_file)?; 15 | let _ = conn.execute("DROP TABLE Sales", params![]); 16 | let _ = conn.execute("DROP TABLE Products", params![]); 17 | conn.execute( 18 | "CREATE TABLE Products ( 19 | id INTEGER PRIMARY KEY, 20 | category TEXT NOT NULL, 21 | name TEXT NOT NULL UNIQUE)", 22 | params![], 23 | )?; 24 | conn.execute( 25 | "CREATE TABLE Sales ( 26 | id TEXT PRIMARY KEY, 27 | product_id INTEGER NOT NULL REFERENCES Products, 28 | sale_date BIGINT NOT NULL, 29 | quantity DOUBLE PRECISION NOT NULL, 30 | unit TEXT NOT NULL)", 31 | params![], 32 | )?; 33 | Ok(conn) 34 | } 35 | 36 | fn populate_db(conn: &Connection) -> Result<()> { 37 | conn.execute( 38 | "INSERT INTO Products ( 39 | id, category, name 40 | ) VALUES ($1, $2, $3)", 41 | params![1, "fruit", "pears"], 42 | )?; 43 | conn.execute( 44 | "INSERT INTO Sales ( 45 | id, product_id, sale_date, quantity, unit 46 | ) VALUES ($1, $2, $3, $4, $5)", 47 | params!["2020-183", 1, 1_234_567_890_i64, 7.439, "Kg",], 48 | )?; 49 | Ok(()) 50 | } 51 | 52 | fn print_db(conn: &Connection) -> Result<()> { 53 | let mut command = conn.prepare( 54 | "SELECT p.name, s.unit, s.quantity, s.sale_date 55 | FROM Sales s 56 | LEFT JOIN Products p 57 | ON p.id = s.product_id 58 | ORDER BY s.sale_date", 59 | )?; 60 | for sale_with_product in command.query_map(params![], |row| { 61 | Ok(SaleWithProduct { 62 | category: "".to_string(), 63 | name: row.get(0)?, 64 | quantity: row.get(2)?, 65 | unit: row.get(1)?, 66 | date: row.get(3)?, 67 | }) 68 | })? { 69 | if let Ok(item) = sale_with_product { 70 | println!( 71 | "At instant {}, {} {} of {} were sold.", 72 | item.date, item.quantity, item.unit, item.name 73 | ); 74 | } 75 | } 76 | Ok(()) 77 | } 78 | 79 | fn main() -> Result<()> { 80 | let conn = create_db()?; 81 | populate_db(&conn)?; 82 | print_db(&conn)?; 83 | Ok(()) 84 | } 85 | -------------------------------------------------------------------------------- /Chapter02/toml_dynamic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "toml_dynamic" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | toml = "0.4" 9 | -------------------------------------------------------------------------------- /Chapter02/toml_dynamic/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // 1. Define the config structure. 3 | let config_const_values = { 4 | // 2. Get the path of the config file from the command line. 5 | let config_path = std::env::args().nth(1).unwrap(); 6 | 7 | // 3. Load the whole file contents into a string. 8 | let config_text = std::fs::read_to_string(&config_path).unwrap(); 9 | 10 | // 4. Load an unmutable config structure from the string. 11 | config_text.parse::().unwrap() 12 | }; 13 | 14 | // 5. Show the whole config structure. 15 | println!("Original: {:#?}", config_const_values); 16 | 17 | // 6. Get and show one config value. 18 | println!( 19 | "[Postgresql].Database: {}", 20 | config_const_values 21 | .get("postgresql") 22 | .unwrap() 23 | .get("database") 24 | .unwrap() 25 | .as_str() 26 | .unwrap() 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /Chapter02/toml_static/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "toml_static" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | toml = "0.4" 9 | serde = "1.0" 10 | serde_derive = "1.0" 11 | -------------------------------------------------------------------------------- /Chapter02/toml_static/src/main.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::Deserialize; 2 | 3 | #[allow(unused)] 4 | #[derive(Deserialize)] 5 | struct Input { 6 | xml_file: String, 7 | json_file: String, 8 | } 9 | #[allow(unused)] 10 | #[derive(Deserialize)] 11 | struct Redis { 12 | host: String, 13 | } 14 | #[allow(unused)] 15 | #[derive(Deserialize)] 16 | struct Sqlite { 17 | db_file: String, 18 | } 19 | #[allow(unused)] 20 | #[derive(Deserialize)] 21 | struct Postgresql { 22 | username: String, 23 | password: String, 24 | host: String, 25 | port: String, 26 | database: String, 27 | } 28 | #[allow(unused)] 29 | #[derive(Deserialize)] 30 | struct Config { 31 | input: Input, 32 | redis: Redis, 33 | sqlite: Sqlite, 34 | postgresql: Postgresql, 35 | } 36 | 37 | fn main() { 38 | // 1. Define the config structure. 39 | let config_const_values: Config = { 40 | // 2. Get the path of the config file from the command line. 41 | let config_path = std::env::args().nth(1).unwrap(); 42 | 43 | // 3. Load the whole file contents into a string. 44 | let config_text = std::fs::read_to_string(&config_path).unwrap(); 45 | 46 | // 4. Load an unmutable statically-typed structure from the string. 47 | toml::from_str(&config_text).unwrap() 48 | }; 49 | 50 | // 5. Get and show one config value. 51 | println!( 52 | "[postgresql].database: {}", 53 | config_const_values.postgresql.database 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /Chapter02/transformer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "transformer" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | serde = "1.0" 9 | serde_derive = "1.0" 10 | serde_json = "1.0" 11 | toml = "0.4" 12 | xml-rs = "0.8" 13 | rusqlite = "0.23" 14 | postgres = "0.17" 15 | redis = "0.16" 16 | time = "0.1" 17 | -------------------------------------------------------------------------------- /Chapter02/xml_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xml_example" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | xml-rs = "0.8" 9 | 10 | -------------------------------------------------------------------------------- /Chapter03/file_transfer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "file_transfer" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | actix-web = "1" 9 | futures = "0.1" 10 | rand = "0.6" 11 | -------------------------------------------------------------------------------- /Chapter03/file_transfer_stub/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "file_transfer_stub" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | actix-web = "1" 9 | -------------------------------------------------------------------------------- /Chapter03/file_transfer_stub/src/main.rs: -------------------------------------------------------------------------------- 1 | // Test it with the following commands: 2 | // curl -X DELETE http://localhost:8080/datafile.txt 3 | // curl -X GET http://localhost:8080/datafile.txt 4 | // curl -X PUT http://localhost:8080/datafile.txt -d "File contents." 5 | // curl -X POST http://localhost:8080/data -d "File contents." 6 | // curl -X GET http://localhost:8080/a/b 7 | // 8 | // after running the second command, the client should have printed: 9 | // Contents of the file. 10 | // 11 | // After running all five commands, the server should have printed: 12 | // Listening at address 127.0.0.1:8080 ... 13 | // Deleting file "datafile.txt" ... Deleted file "datafile.txt" 14 | // Downloading file "datafile.txt" ... Downloaded file "datafile.txt" 15 | // Uploading file "datafile.txt" ... Uploaded file "datafile.txt" 16 | // Uploading file "data_*.txt" ... Uploaded file "data_17.txt" 17 | // Invalid URI: "/a/b" 18 | 19 | use actix_web::{web, web::Path, App, HttpRequest, HttpResponse, HttpServer, Responder}; 20 | use std::io::Write; 21 | 22 | fn flush_stdout() { 23 | std::io::stdout().flush().unwrap(); 24 | } 25 | 26 | fn delete_file(info: Path<(String,)>) -> impl Responder { 27 | let filename = &info.0; 28 | print!("Deleting file \"{}\" ... ", filename); 29 | flush_stdout(); 30 | 31 | // TODO: Delete the file. 32 | 33 | println!("Deleted file \"{}\"", filename); 34 | HttpResponse::Ok() 35 | } 36 | 37 | fn download_file(info: Path<(String,)>) -> impl Responder { 38 | let filename = &info.0; 39 | print!("Downloading file \"{}\" ... ", filename); 40 | flush_stdout(); 41 | 42 | // TODO: Read the contents of the file. 43 | let contents = "Contents of the file.\n".to_string(); 44 | 45 | println!("Downloaded file \"{}\"", filename); 46 | HttpResponse::Ok().content_type("text/plain").body(contents) 47 | } 48 | 49 | fn upload_specified_file(info: Path<(String,)>) -> impl Responder { 50 | let filename = &info.0; 51 | print!("Uploading file \"{}\" ... ", filename); 52 | flush_stdout(); 53 | 54 | // TODO: Get from the client the contents to write into the file. 55 | let _contents = "Contents of the file.\n".to_string(); 56 | 57 | // TODO: Create the file and write the contents into it. 58 | 59 | println!("Uploaded file \"{}\"", filename); 60 | HttpResponse::Ok() 61 | } 62 | 63 | fn upload_new_file(info: Path<(String,)>) -> impl Responder { 64 | let filename = &info.0; 65 | print!("Uploading file \"{}*.txt\" ... ", filename); 66 | flush_stdout(); 67 | 68 | // TODO: Get from the client the contents to write into the file. 69 | let _contents = "Contents of the file.\n".to_string(); 70 | 71 | // TODO: Generate new filename and create that file. 72 | let file_id = 17; 73 | 74 | let filename = format!("{}{}.txt", filename, file_id); 75 | 76 | // TODO: Write the contents into the file. 77 | 78 | println!("Uploaded file \"{}\"", filename); 79 | HttpResponse::Ok().content_type("text/plain").body(filename) 80 | } 81 | 82 | fn invalid_resource(req: HttpRequest) -> impl Responder { 83 | println!("Invalid URI: \"{}\"", req.uri()); 84 | HttpResponse::NotFound() 85 | } 86 | 87 | fn main() -> std::io::Result<()> { 88 | let server_address = "127.0.0.1:8080"; 89 | println!("Listening at address {} ...", server_address); 90 | HttpServer::new(|| { 91 | App::new() 92 | .service( 93 | web::resource("/{filename}") 94 | .route(web::delete().to(delete_file)) 95 | .route(web::get().to(download_file)) 96 | .route(web::put().to(upload_specified_file)) 97 | .route(web::post().to(upload_new_file)), 98 | ) 99 | .default_service(web::route().to(invalid_resource)) 100 | }) 101 | .bind(server_address)? 102 | .run() 103 | } 104 | -------------------------------------------------------------------------------- /Chapter03/json_db/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json_db" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | actix-web = "1" 9 | serde = "1" 10 | serde_derive = "1" 11 | serde_json = "1" 12 | -------------------------------------------------------------------------------- /Chapter03/json_db/src/db_access.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug)] 2 | pub struct Person { 3 | pub id: u32, 4 | pub name: String, 5 | } 6 | 7 | pub struct DbConnection { 8 | persons: Vec, 9 | } 10 | 11 | impl DbConnection { 12 | pub fn new() -> DbConnection { 13 | DbConnection { persons: vec![] } 14 | } 15 | 16 | pub fn get_all_persons_ids(&self) -> impl Iterator + '_ { 17 | self.persons.iter().map(|p| p.id) 18 | } 19 | 20 | pub fn get_person_name_by_id(&self, id: u32) -> Option { 21 | Some(self.persons.iter().find(|p| p.id == id)?.name.clone()) 22 | } 23 | 24 | pub fn get_persons_id_and_name_by_partial_name<'a>( 25 | &'a self, 26 | subname: &'a str, 27 | ) -> impl Iterator + 'a { 28 | self.persons 29 | .iter() 30 | .filter(move |p| p.name.contains(subname)) 31 | .map(|p| (p.id, p.name.clone())) 32 | } 33 | 34 | pub fn insert_person(&mut self, name: &str) -> u32 { 35 | let new_id = if self.persons.is_empty() { 36 | 1 37 | } else { 38 | self.persons[self.persons.len() - 1].id + 1 39 | }; 40 | self.persons.push(Person { 41 | id: new_id, 42 | name: name.to_string(), 43 | }); 44 | new_id 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Chapter03/json_db/src/main.rs: -------------------------------------------------------------------------------- 1 | mod db_access; 2 | 3 | use actix_web::{web, web::Path, App, HttpRequest, HttpResponse, HttpServer, Responder}; 4 | use serde_derive::Deserialize; 5 | use serde_json::json; 6 | use std::sync::Mutex; 7 | 8 | struct AppState { 9 | db: db_access::DbConnection, 10 | } 11 | 12 | fn get_all_persons_ids(state: web::Data>) -> impl Responder { 13 | println!("In get_all_persons_ids"); 14 | let db_conn = &state.lock().unwrap().db; 15 | HttpResponse::Ok() 16 | .content_type("application/json") 17 | .body(json!(db_conn.get_all_persons_ids().collect::>()).to_string()) 18 | } 19 | 20 | fn get_person_name_by_id( 21 | state: web::Data>, 22 | info: Path<(String,)>, 23 | ) -> impl Responder { 24 | println!("In get_person_name_by_id"); 25 | let id = &info.0; 26 | let id = id.parse::(); 27 | if id.is_err() { 28 | return HttpResponse::NotFound().finish(); 29 | } 30 | let id = id.unwrap(); 31 | let db_conn = &state.lock().unwrap().db; 32 | if let Some(name) = db_conn.get_person_name_by_id(id) { 33 | HttpResponse::Ok() 34 | .content_type("application/json") 35 | .body(json!(name).to_string()) 36 | } else { 37 | HttpResponse::NotFound().finish() 38 | } 39 | } 40 | 41 | #[derive(Deserialize)] 42 | pub struct Filter { 43 | partial_name: Option, 44 | } 45 | 46 | fn get_persons(state: web::Data>, query: web::Query) -> impl Responder { 47 | println!("In get_persons"); 48 | let db_conn = &state.lock().unwrap().db; 49 | HttpResponse::Ok().content_type("application/json").body( 50 | json!(db_conn 51 | .get_persons_id_and_name_by_partial_name( 52 | &query.partial_name.clone().unwrap_or_else(|| "".to_string()), 53 | ) 54 | .collect::>()) 55 | .to_string(), 56 | ) 57 | } 58 | 59 | fn insert_person(state: web::Data>, info: Path<(String,)>) -> impl Responder { 60 | println!("In insert_person"); 61 | let name = &info.0; 62 | let db_conn = &mut state.lock().unwrap().db; 63 | HttpResponse::Ok() 64 | .content_type("application/json") 65 | .body(json!(db_conn.insert_person(name)).to_string()) 66 | } 67 | 68 | fn invalid_resource(req: HttpRequest) -> impl Responder { 69 | println!("Invalid URI: \"{}\"", req.uri()); 70 | HttpResponse::NotFound() 71 | } 72 | 73 | fn main() -> std::io::Result<()> { 74 | let server_address = "127.0.0.1:8080"; 75 | println!("Listening at address {}", server_address); 76 | let db_conn = web::Data::new(Mutex::new(AppState { 77 | db: db_access::DbConnection::new(), 78 | })); 79 | HttpServer::new(move || { 80 | App::new() 81 | .register_data(db_conn.clone()) 82 | .service(web::resource("/persons/ids").route(web::get().to(get_all_persons_ids))) 83 | .service( 84 | web::resource("/person/name_by_id/{id}") 85 | .route(web::get().to(get_person_name_by_id)), 86 | ) 87 | .service(web::resource("/persons").route(web::get().to(get_persons))) 88 | .service(web::resource("/person/{name}").route(web::post().to(insert_person))) 89 | .default_service(web::route().to(invalid_resource)) 90 | }) 91 | .bind(server_address)? 92 | .run() 93 | } 94 | -------------------------------------------------------------------------------- /Chapter03/memory_db/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "memory_db" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | actix-web = "1" 9 | serde = "1" 10 | serde_derive = "1" 11 | -------------------------------------------------------------------------------- /Chapter03/memory_db/src/db_access.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug)] 2 | pub struct Person { 3 | pub id: u32, 4 | pub name: String, 5 | } 6 | 7 | pub struct DbConnection { 8 | persons: Vec, 9 | } 10 | 11 | impl DbConnection { 12 | pub fn new() -> DbConnection { 13 | DbConnection { persons: vec![] } 14 | } 15 | 16 | pub fn get_all_persons_ids(&self) -> impl Iterator + '_ { 17 | self.persons.iter().map(|p| p.id) 18 | } 19 | 20 | pub fn get_person_name_by_id(&self, id: u32) -> Option { 21 | Some(self.persons.iter().find(|p| p.id == id)?.name.clone()) 22 | } 23 | 24 | pub fn get_persons_id_and_name_by_partial_name<'a>( 25 | &'a self, 26 | subname: &'a str, 27 | ) -> impl Iterator + 'a { 28 | self.persons 29 | .iter() 30 | .filter(move |p| p.name.contains(subname)) 31 | .map(|p| (p.id, p.name.clone())) 32 | } 33 | 34 | pub fn insert_person(&mut self, name: &str) -> u32 { 35 | let new_id = if self.persons.is_empty() { 36 | 1 37 | } else { 38 | self.persons[self.persons.len() - 1].id + 1 39 | }; 40 | self.persons.push(Person { 41 | id: new_id, 42 | name: name.to_string(), 43 | }); 44 | new_id 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Chapter03/memory_db/src/main.rs: -------------------------------------------------------------------------------- 1 | mod db_access; 2 | 3 | use actix_web::{web, web::Path, App, HttpRequest, HttpResponse, HttpServer, Responder}; 4 | use serde_derive::Deserialize; 5 | use std::sync::Mutex; 6 | 7 | struct AppState { 8 | db: db_access::DbConnection, 9 | } 10 | 11 | fn get_all_persons_ids(state: web::Data>) -> impl Responder { 12 | println!("In get_all_persons_ids"); 13 | let db_conn = &state.lock().unwrap().db; 14 | db_conn 15 | .get_all_persons_ids() 16 | .map(|id| id.to_string()) 17 | .collect::>() 18 | .join(", ") 19 | } 20 | 21 | fn get_person_name_by_id( 22 | state: web::Data>, 23 | info: Path<(String,)>, 24 | ) -> impl Responder { 25 | println!("In get_person_name_by_id"); 26 | let id = &info.0; 27 | let id = id.parse::(); 28 | if id.is_err() { 29 | return HttpResponse::NotFound().finish(); 30 | } 31 | let id = id.unwrap(); 32 | let db_conn = &state.lock().unwrap().db; 33 | if let Some(name) = db_conn.get_person_name_by_id(id) { 34 | HttpResponse::Ok().content_type("text/plain").body(name) 35 | } else { 36 | HttpResponse::NotFound().finish() 37 | } 38 | } 39 | 40 | #[derive(Deserialize)] 41 | pub struct Filter { 42 | partial_name: Option, 43 | } 44 | 45 | fn get_persons(state: web::Data>, query: web::Query) -> impl Responder { 46 | println!("In get_persons"); 47 | let db_conn = &state.lock().unwrap().db; 48 | db_conn 49 | .get_persons_id_and_name_by_partial_name( 50 | &query.partial_name.clone().unwrap_or_else(|| "".to_string()), 51 | ) 52 | .map(|p| p.0.to_string() + ": " + &p.1) 53 | .collect::>() 54 | .join("; ") 55 | } 56 | 57 | fn insert_person(state: web::Data>, info: Path<(String,)>) -> impl Responder { 58 | println!("In insert_person"); 59 | let name = &info.0; 60 | let db_conn = &mut state.lock().unwrap().db; 61 | format!("{}", db_conn.insert_person(name)) 62 | } 63 | 64 | fn invalid_resource(req: HttpRequest) -> impl Responder { 65 | println!("Invalid URI: \"{}\"", req.uri()); 66 | HttpResponse::NotFound() 67 | } 68 | 69 | fn main() -> std::io::Result<()> { 70 | let server_address = "127.0.0.1:8080"; 71 | println!("Listening at address {}", server_address); 72 | let db_conn = web::Data::new(Mutex::new(AppState { 73 | db: db_access::DbConnection::new(), 74 | })); 75 | HttpServer::new(move || { 76 | App::new() 77 | .register_data(db_conn.clone()) 78 | .service(web::resource("/persons/ids").route(web::get().to(get_all_persons_ids))) 79 | .service( 80 | web::resource("/person/name_by_id/{id}") 81 | .route(web::get().to(get_person_name_by_id)), 82 | ) 83 | .service(web::resource("/persons").route(web::get().to(get_persons))) 84 | .service(web::resource("/person/{name}").route(web::post().to(insert_person))) 85 | .default_service(web::route().to(invalid_resource)) 86 | }) 87 | .bind(server_address)? 88 | .run() 89 | } 90 | -------------------------------------------------------------------------------- /Chapter04/auth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | actix-web = "1" 9 | lazy_static = "1.2" 10 | tera = "1" 11 | serde = "1" 12 | serde_derive = "1" 13 | actix-web-httpauth = "0.3" 14 | -------------------------------------------------------------------------------- /Chapter04/auth/src/db_access.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::Serialize; 2 | 3 | #[derive(Serialize, Clone, Debug)] 4 | pub struct Person { 5 | pub id: u32, 6 | pub name: String, 7 | } 8 | 9 | #[derive(Serialize, Clone, Copy, PartialEq, Debug)] 10 | pub enum DbPrivilege { 11 | CanRead, 12 | CanWrite, 13 | } 14 | 15 | #[derive(Serialize, Clone, Debug)] 16 | pub struct User { 17 | pub username: String, 18 | pub password: String, 19 | pub privileges: Vec, 20 | } 21 | 22 | pub struct DbConnection { 23 | persons: Vec, 24 | users: Vec, 25 | } 26 | 27 | impl DbConnection { 28 | pub fn new() -> DbConnection { 29 | DbConnection { 30 | persons: vec![], 31 | users: vec![ 32 | User { 33 | username: "joe".to_string(), 34 | password: "xjoe".to_string(), 35 | privileges: vec![DbPrivilege::CanRead], 36 | }, 37 | User { 38 | username: "susan".to_string(), 39 | password: "xsusan".to_string(), 40 | privileges: vec![DbPrivilege::CanRead, DbPrivilege::CanWrite], 41 | }, 42 | ], 43 | } 44 | } 45 | 46 | pub fn get_user_by_username(&self, username: &str) -> Option<&User> { 47 | if let Some(u) = self.users.iter().find(|u| u.username == username) { 48 | Some(u) 49 | } else { 50 | None 51 | } 52 | } 53 | 54 | pub fn get_person_by_id(&self, id: u32) -> Option<&Person> { 55 | if let Some(p) = self.persons.iter().find(|p| p.id == id) { 56 | Some(p) 57 | } else { 58 | None 59 | } 60 | } 61 | 62 | pub fn get_persons_by_partial_name<'a>( 63 | &'a self, 64 | subname: &'a str, 65 | ) -> impl Iterator + 'a { 66 | self.persons 67 | .iter() 68 | .filter(move |p| p.name.contains(subname)) 69 | } 70 | 71 | pub fn delete_by_id(&mut self, id: u32) -> bool { 72 | if let Some((n, _)) = self.persons.iter().enumerate().find(|(_, p)| p.id == id) { 73 | self.persons.remove(n); 74 | true 75 | } else { 76 | false 77 | } 78 | } 79 | 80 | pub fn insert_person(&mut self, mut person: Person) -> u32 { 81 | let new_id = if self.persons.is_empty() { 82 | 1 83 | } else { 84 | self.persons[self.persons.len() - 1].id + 1 85 | }; 86 | person.id = new_id; 87 | self.persons.push(person); 88 | new_id 89 | } 90 | 91 | pub fn update_person(&mut self, person: Person) -> bool { 92 | if let Some((n, _)) = self 93 | .persons 94 | .iter() 95 | .enumerate() 96 | .find(|(_, p)| p.id == person.id) 97 | { 98 | self.persons[n] = person; 99 | true 100 | } else { 101 | false 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Chapter04/auth/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter04/auth/src/favicon.ico -------------------------------------------------------------------------------- /Chapter04/auth/templates/login.html: -------------------------------------------------------------------------------- 1 |

Login to Persons

2 |
3 | Current user: 4 | 5 |
6 |
7 | 8 |
9 | 10 | 11 |
12 |
13 | 14 | 15 |
16 | 17 | -------------------------------------------------------------------------------- /Chapter04/auth/templates/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Persons management 6 | 9 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Chapter04/auth/templates/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | The client can send two possible kinds of messages: 3 | - sendCommand: sends a specified REST command 4 | - getPage: requests HTML code that will be assigned to the body 5 | */ 6 | var username = ''; 7 | 8 | var password = ''; 9 | 10 | function sendCommand(method, uri, body, success, failure) { 11 | var xhttp = new XMLHttpRequest(); 12 | xhttp.onreadystatechange = function() { 13 | if (this.readyState == 4) 14 | if (this.status == 200) success(); 15 | else failure(); 16 | }; 17 | xhttp.open(method, uri, true); 18 | xhttp.setRequestHeader("Authorization", 19 | "Basic " + btoa(username + ":" + password)); 20 | xhttp.send(body); 21 | } 22 | 23 | function getPage(uri) { 24 | var xhttp = new XMLHttpRequest(); 25 | xhttp.onreadystatechange = function() { 26 | if (this.readyState == 4 && this.status == 200) { 27 | document.getElementById('body') 28 | .innerHTML = xhttp.responseText; 29 | var cur_user = document.getElementById('current_user'); 30 | if (cur_user) 31 | cur_user.innerHTML = username ? username : '---'; 32 | } 33 | }; 34 | xhttp.open('GET', uri, true); 35 | xhttp.setRequestHeader("Authorization", 36 | "Basic " + btoa(username + ":" + password)); 37 | xhttp.send(); 38 | } 39 | 40 | function delete_selected_persons() { 41 | var items; 42 | for (var item of document.getElementsByName('selector')) 43 | if (item.checked) 44 | if (items) items += ',' + item.id; 45 | else items = '' + item.id; 46 | if (items) 47 | sendCommand('DELETE', '/persons?id_list=' + items, '', 48 | function() { getPage('/page/persons'); }, 49 | function() { alert('Failed deletion.'); }); 50 | } 51 | 52 | function savePerson(method) { 53 | sendCommand(method, 54 | '/one_person?' 55 | + (method === 'POST' ? '' : 56 | 'id=' 57 | + document.getElementById('person_id').value 58 | + '&') 59 | + 'name=' 60 | + encodeURIComponent( 61 | document.getElementById('person_name') 62 | .value), 63 | '', 64 | function() { 65 | getPage('/page/persons'); 66 | }, 67 | function() { 68 | alert('Failed command.'); 69 | }); 70 | } 71 | 72 | function login() { 73 | username = document.getElementById('username').value; 74 | password = document.getElementById('password').value; 75 | getPage('/page/persons'); 76 | } 77 | -------------------------------------------------------------------------------- /Chapter04/auth/templates/one_person.html: -------------------------------------------------------------------------------- 1 |

Person data

2 |
3 | Current user: 4 | 5 | 6 |
7 |
8 |
9 | 10 | 11 |
12 |
13 | 14 | 17 |
18 | {% if inserting %} 19 | 22 | {% else %} 23 | 26 | {% endif %} 27 | 28 | -------------------------------------------------------------------------------- /Chapter04/auth/templates/persons.html: -------------------------------------------------------------------------------- 1 |

Persons

2 |
3 | Current user: 4 | 5 | 6 |
7 |
8 | 9 |
10 | 11 | 12 | 15 |
16 |
17 | 18 | 19 | 21 |
22 |
23 | 27 | 31 |
32 | {% if persons %} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | {% for p in persons %} 42 | 43 | 44 | 45 | 46 | 47 | 48 | {% endfor %} 49 | 50 |
IdName
{{p.id}}{{p.name}}
51 | {% else %} 52 |

No persons.

53 | {% endif %} 54 | -------------------------------------------------------------------------------- /Chapter04/crud/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crud" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | actix-web = "1" 9 | lazy_static = "1.2" 10 | tera = "1" 11 | serde = "1" 12 | serde_derive = "1" 13 | -------------------------------------------------------------------------------- /Chapter04/crud/src/db_access.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::Serialize; 2 | 3 | #[derive(Serialize, Clone, Debug)] 4 | pub struct Person { 5 | pub id: u32, 6 | pub name: String, 7 | } 8 | 9 | pub struct DbConnection { 10 | persons: Vec, 11 | } 12 | 13 | impl DbConnection { 14 | pub fn new() -> DbConnection { 15 | DbConnection { persons: vec![] } 16 | } 17 | 18 | pub fn get_person_by_id(&self, id: u32) -> Option<&Person> { 19 | if let Some(p) = self.persons.iter().find(|p| p.id == id) { 20 | Some(p) 21 | } else { 22 | None 23 | } 24 | } 25 | 26 | pub fn get_persons_by_partial_name<'a>( 27 | &'a self, 28 | subname: &'a str, 29 | ) -> impl Iterator + 'a { 30 | self.persons 31 | .iter() 32 | .filter(move |p| p.name.contains(subname)) 33 | } 34 | 35 | pub fn delete_by_id(&mut self, id: u32) -> bool { 36 | if let Some((n, _)) = self.persons.iter().enumerate().find(|(_, p)| p.id == id) { 37 | self.persons.remove(n); 38 | true 39 | } else { 40 | false 41 | } 42 | } 43 | 44 | pub fn insert_person(&mut self, mut person: Person) -> u32 { 45 | let new_id = if self.persons.is_empty() { 46 | 1 47 | } else { 48 | self.persons[self.persons.len() - 1].id + 1 49 | }; 50 | person.id = new_id; 51 | self.persons.push(person); 52 | new_id 53 | } 54 | 55 | pub fn update_person(&mut self, person: Person) -> bool { 56 | if let Some((n, _)) = self 57 | .persons 58 | .iter() 59 | .enumerate() 60 | .find(|(_, p)| p.id == person.id) 61 | { 62 | self.persons[n] = person; 63 | true 64 | } else { 65 | false 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Chapter04/crud/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter04/crud/src/favicon.ico -------------------------------------------------------------------------------- /Chapter04/crud/templates/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Persons management 6 | 9 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Chapter04/crud/templates/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | The client can send two possible kinds of messages: 3 | - sendCommand: sends a specified REST command 4 | - getPage: requests HTML code that will be assigned to the body 5 | */ 6 | function sendCommand(method, uri, body, success, failure) { 7 | var xhttp = new XMLHttpRequest(); 8 | xhttp.onreadystatechange = function() { 9 | if (this.readyState == 4) 10 | if (this.status == 200) success(); 11 | else failure(); 12 | }; 13 | xhttp.open(method, uri, true); 14 | xhttp.send(body); 15 | } 16 | 17 | function getPage(uri) { 18 | var xhttp = new XMLHttpRequest(); 19 | xhttp.onreadystatechange = function() { 20 | if (this.readyState == 4 && this.status == 200) { 21 | document.getElementById('body') 22 | .innerHTML = xhttp.responseText; 23 | } 24 | }; 25 | xhttp.open('GET', uri, true); 26 | xhttp.send(); 27 | } 28 | 29 | function delete_selected_persons() { 30 | var items; 31 | for (var item of document.getElementsByName('selector')) 32 | if (item.checked) 33 | if (items) items += ',' + item.id; 34 | else items = '' + item.id; 35 | if (items) 36 | sendCommand('DELETE', '/persons?id_list=' + items, '', 37 | function() { getPage('/page/persons'); }, 38 | function() { alert('Failed deletion.'); }); 39 | } 40 | 41 | function savePerson(method) { 42 | sendCommand(method, 43 | '/one_person?' 44 | + (method === 'POST' ? '' : 45 | 'id=' 46 | + document.getElementById('person_id').value 47 | + '&') 48 | + 'name=' 49 | + encodeURIComponent( 50 | document.getElementById('person_name') 51 | .value), 52 | '', 53 | function() { 54 | getPage('/page/persons'); 55 | }, 56 | function() { 57 | alert('Failed command.'); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /Chapter04/crud/templates/one_person.html: -------------------------------------------------------------------------------- 1 |

Person data

2 |
3 | 4 | 5 |
6 |
7 | 8 | 9 |
10 | {% if inserting %} 11 | 12 | {% else %} 13 | 14 | {% endif %} 15 | 16 | -------------------------------------------------------------------------------- /Chapter04/crud/templates/persons.html: -------------------------------------------------------------------------------- 1 |

Persons

2 | 3 |
4 | 5 | 6 | 9 |
10 |
11 | 12 | 13 | 15 |
16 |
17 | 18 | 19 |
20 | {% if persons %} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {% for p in persons %} 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% endfor %} 37 | 38 |
IdName
{{p.id}}{{p.name}}
39 | {% else %} 40 |

No persons.

41 | {% endif %} 42 | -------------------------------------------------------------------------------- /Chapter04/list/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "list" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | actix-web = "1" 9 | lazy_static = "1.2" 10 | tera = "1" 11 | serde = "1" 12 | serde_derive = "1" 13 | -------------------------------------------------------------------------------- /Chapter04/list/src/db_access.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::Serialize; 2 | 3 | #[derive(Serialize, Clone, Debug)] 4 | pub struct Person { 5 | pub id: u32, 6 | pub name: String, 7 | } 8 | 9 | pub struct DbConnection { 10 | persons: Vec, 11 | } 12 | 13 | impl DbConnection { 14 | pub fn new() -> DbConnection { 15 | DbConnection { 16 | persons: vec![ 17 | Person { 18 | id: 2, 19 | name: "Hamlet".to_string(), 20 | }, 21 | Person { 22 | id: 4, 23 | name: "Macbeth".to_string(), 24 | }, 25 | Person { 26 | id: 7, 27 | name: "Othello".to_string(), 28 | }, 29 | ], 30 | } 31 | } 32 | 33 | pub fn get_persons_by_partial_name<'a>( 34 | &'a self, 35 | subname: &'a str, 36 | ) -> impl Iterator + 'a { 37 | self.persons 38 | .iter() 39 | .filter(move |p| p.name.contains(subname)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Chapter04/list/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter04/list/src/favicon.ico -------------------------------------------------------------------------------- /Chapter04/list/src/main.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, App, HttpResponse, HttpServer, Responder}; 2 | use lazy_static::lazy_static; 3 | use serde_derive::Deserialize; 4 | use std::sync::Mutex; 5 | 6 | mod db_access; 7 | 8 | struct AppState { 9 | db: db_access::DbConnection, 10 | } 11 | 12 | fn get_main() -> impl Responder { 13 | let context = tera::Context::new(); 14 | HttpResponse::Ok() 15 | .content_type("text/html") 16 | .body(TERA.render("main.html", &context).unwrap()) 17 | } 18 | 19 | #[derive(Deserialize)] 20 | pub struct Filter { 21 | partial_name: Option, 22 | } 23 | 24 | fn get_page_persons( 25 | query: web::Query, 26 | state: web::Data>, 27 | ) -> impl Responder { 28 | let partial_name = &query.partial_name.clone().unwrap_or_else(|| "".to_string()); 29 | let db_conn = &state.lock().unwrap().db; 30 | let person_list = db_conn.get_persons_by_partial_name(&partial_name); 31 | let mut context = tera::Context::new(); 32 | context.insert("partial_name", &partial_name); 33 | context.insert("persons", &person_list.collect::>()); 34 | HttpResponse::Ok() 35 | .content_type("text/html") 36 | .body(TERA.render("persons.html", &context).unwrap()) 37 | } 38 | 39 | fn get_favicon() -> impl Responder { 40 | HttpResponse::Ok() 41 | .content_type("image/x-icon") 42 | .body(include_bytes!("favicon.ico") as &[u8]) 43 | } 44 | 45 | fn invalid_resource() -> impl Responder { 46 | HttpResponse::NotFound() 47 | .content_type("text/html") 48 | .body("

Invalid request.

") 49 | } 50 | 51 | lazy_static! { 52 | pub static ref TERA: tera::Tera = tera::Tera::new("templates/**/*").unwrap(); 53 | } 54 | 55 | fn main() -> std::io::Result<()> { 56 | let server_address = "127.0.0.1:8080"; 57 | println!("Listening at address {}", server_address); 58 | let db_conn = web::Data::new(Mutex::new(AppState { 59 | db: db_access::DbConnection::new(), 60 | })); 61 | HttpServer::new(move || { 62 | App::new() 63 | .register_data(db_conn.clone()) 64 | .service( 65 | web::resource("/") 66 | // Get the frame of the page to manage the persons. 67 | // Such frame should request its body. 68 | .route(web::get().to(get_main)), 69 | ) 70 | .service( 71 | web::resource("/page/persons") 72 | // Get the page to manage the persons, 73 | // showing all the persons. 74 | .route(web::get().to(get_page_persons)), 75 | ) 76 | .service( 77 | web::resource("/favicon.ico") 78 | // Get the App icon. 79 | .route(web::get().to(get_favicon)), 80 | ) 81 | .default_service(web::route().to(invalid_resource)) 82 | }) 83 | .bind(server_address)? 84 | .run() 85 | } 86 | -------------------------------------------------------------------------------- /Chapter04/list/templates/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Persons management 6 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Chapter04/list/templates/main.js: -------------------------------------------------------------------------------- 1 | function getPage(uri) { 2 | var xhttp = new XMLHttpRequest(); 3 | xhttp.onreadystatechange = function() { 4 | if (this.readyState == 4 && this.status == 200) { 5 | document.getElementById('body') 6 | .innerHTML = xhttp.responseText; 7 | } 8 | }; 9 | xhttp.open('GET', uri, true); 10 | xhttp.send(); 11 | } 12 | -------------------------------------------------------------------------------- /Chapter04/list/templates/persons.html: -------------------------------------------------------------------------------- 1 |

Persons

2 |
3 | 4 | 5 | 7 |
8 | {% if persons %} 9 | 10 | 11 | 12 | 13 | 14 | 15 | {% for p in persons %} 16 | 17 | 18 | 19 | 20 | {% endfor %} 21 | 22 |
IdName
{{p.id}}{{p.name}}
23 | {% else %} 24 |

No persons.

25 | {% endif %} 26 | -------------------------------------------------------------------------------- /Chapter04/templ/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "templ" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | tera = "1" 9 | serde = "1" 10 | serde_derive = "1" 11 | lazy_static = "1.2" 12 | -------------------------------------------------------------------------------- /Chapter04/templ/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut tera_engine = tera::Tera::default(); 3 | 4 | tera_engine 5 | .add_raw_template("id_template", "Identifier: {{id}}.") 6 | .unwrap(); 7 | 8 | let mut numeric_id = tera::Context::new(); 9 | numeric_id.insert("id", &7362); 10 | println!( 11 | "id_template with numeric_id: [{}]", 12 | tera_engine 13 | .render("id_template", &numeric_id.clone()) 14 | .unwrap() 15 | ); 16 | 17 | let mut textual_id = tera::Context::new(); 18 | textual_id.insert("id", &"ABCD"); 19 | println!( 20 | "id_template with textual_id: [{}]", 21 | tera_engine.render("id_template", &textual_id).unwrap() 22 | ); 23 | 24 | tera_engine 25 | .add_raw_template("person_id_template", "Person id: {{person.id}}") 26 | .unwrap(); 27 | 28 | #[derive(serde_derive::Serialize)] 29 | struct Person { 30 | id: u32, 31 | name: String, 32 | } 33 | 34 | let mut one_person = tera::Context::new(); 35 | one_person.insert( 36 | "person", 37 | &Person { 38 | id: 534, 39 | name: "Mary".to_string(), 40 | }, 41 | ); 42 | println!( 43 | "person_id_template with one_person: [{}]", 44 | tera_engine 45 | .render("person_id_template", &one_person.clone()) 46 | .unwrap() 47 | ); 48 | 49 | tera_engine 50 | .add_raw_template( 51 | "possible_person_id_template", 52 | "{%if person%}Id: {{person.id}}\ 53 | {%else%}No person\ 54 | {%endif%}", 55 | ) 56 | .unwrap(); 57 | 58 | println!( 59 | "possible_person_id_template with one_person: [{}]", 60 | tera_engine 61 | .render("possible_person_id_template", &one_person) 62 | .unwrap() 63 | ); 64 | 65 | println!( 66 | "possible_person_id_template with empty context: [{}]", 67 | tera_engine 68 | .render("possible_person_id_template", &tera::Context::new()) 69 | .unwrap() 70 | ); 71 | 72 | tera_engine 73 | .add_raw_template( 74 | "multiple_person_id_template", 75 | "{%for p in persons%}\ 76 | Id: {{p.id}};\n\ 77 | {%endfor%}", 78 | ) 79 | .unwrap(); 80 | 81 | let mut three_persons = tera::Context::new(); 82 | three_persons.insert( 83 | "persons", 84 | &vec![ 85 | Person { 86 | id: 534, 87 | name: "Mary".to_string(), 88 | }, 89 | Person { 90 | id: 298, 91 | name: "Joe".to_string(), 92 | }, 93 | Person { 94 | id: 820, 95 | name: "Ann".to_string(), 96 | }, 97 | ], 98 | ); 99 | println!( 100 | "multiple_person_id_template with three_persons: [{}]", 101 | tera_engine 102 | .render("multiple_person_id_template", &three_persons.clone()) 103 | .unwrap() 104 | ); 105 | 106 | tera_engine 107 | .add_template_file("templates/templ_id.txt", Some("id_file_template")) 108 | .unwrap(); 109 | 110 | println!( 111 | "id_file_template with numeric_id: [{}]", 112 | tera_engine 113 | .render("id_file_template", &numeric_id.clone()) 114 | .unwrap() 115 | ); 116 | 117 | tera_engine 118 | .add_template_file("templates/templ_id.txt", None) 119 | .unwrap(); 120 | 121 | println!( 122 | "templates/templ_id.txt with numeric_id: [{}]", 123 | tera_engine 124 | .render("templates/templ_id.txt", &numeric_id) 125 | .unwrap() 126 | ); 127 | 128 | println!( 129 | "templates/templ_names.txt with numeric_id: [{}]", 130 | TERA.render("templ_names.txt", &three_persons).unwrap() 131 | ); 132 | } 133 | 134 | lazy_static::lazy_static! { 135 | pub static ref TERA: tera::Tera = 136 | tera::Tera::new("templates/**/*").unwrap(); 137 | } 138 | -------------------------------------------------------------------------------- /Chapter04/templ/templates/footer.txt: -------------------------------------------------------------------------------- 1 | This is the footer. -------------------------------------------------------------------------------- /Chapter04/templ/templates/templ_id.txt: -------------------------------------------------------------------------------- 1 | This file contains one id: {{id}}. -------------------------------------------------------------------------------- /Chapter04/templ/templates/templ_names.txt: -------------------------------------------------------------------------------- 1 | This file contains some names: 2 | {% for pers in persons %} 3 | * {{pers.name}} 4 | {% endfor %} 5 | {% include "footer.txt" %} -------------------------------------------------------------------------------- /Chapter05/README.md: -------------------------------------------------------------------------------- 1 | All these examples have been fixed and updated to work with a more recent version of [yew](https://github.com/yewstack/yew/). 2 | In order to run them, you need to run the steps below instead of the steps described in the book: 3 | 4 | 1. Install `wasm-pack` to compile the WASM apps: `cargo install wasm-pack` 5 | 2. Go into the app directory. 6 | 3. Run `wasm-pack build --target web --out-name wasm --out-dir ./static` to compile the application. The first run may take a long time, `wasm-pack` needs to download files from the internet to build your app. 7 | 4. Serve the app. For instance, you can install [miniserve](https://crates.io/crates/miniserve) a small webserver written in rust with `cargo install miniserve` and then serve your app with `miniserve ./static --index index.html` 8 | 9 | ## For `yclient` 10 | 11 | This app is a bit different since it also needs as server to run correctly. 12 | 13 | 1. Go into the `person_db` folder and run `cargo run` to start the backend. This backend is similar to what you saw in the previous chapter. 14 | 2. Go into the `yclient` folder and run the steps above to compile and serve the client. 15 | 3. For the front-end, use an IP port different from 8080, that is already used to communicate with the backend. For example,use: `miniserve ./static --index index.html -p 8081`. 16 | -------------------------------------------------------------------------------- /Chapter05/adder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "adder" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | yew = "0.17.4" 9 | wasm-bindgen = "0.2.67" 10 | 11 | [lib] 12 | crate-type = ["cdylib", "rlib"] 13 | -------------------------------------------------------------------------------- /Chapter05/adder/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "512"] 2 | 3 | use wasm_bindgen::prelude::*; 4 | use yew::prelude::*; 5 | use yew::events::InputData; 6 | 7 | struct Model { 8 | link: ComponentLink, 9 | addend1: String, 10 | addend2: String, 11 | sum: Option, 12 | } 13 | 14 | enum Msg { 15 | ChangedAddend1(String), 16 | ChangedAddend2(String), 17 | ComputeSum, 18 | } 19 | 20 | impl Component for Model { 21 | type Message = Msg; 22 | type Properties = (); 23 | fn create(_: Self::Properties, link: ComponentLink) -> Self { 24 | Self { 25 | addend1: "".to_string(), 26 | addend2: "".to_string(), 27 | sum: None, 28 | link, 29 | } 30 | } 31 | 32 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 33 | match msg { 34 | Msg::ComputeSum => { 35 | self.sum = match (self.addend1.parse::(), self.addend2.parse::()) { 36 | (Ok(a1), Ok(a2)) => Some(a1 + a2), 37 | _ => None, 38 | }; 39 | } 40 | Msg::ChangedAddend1(value) => { 41 | self.addend1 = value; 42 | self.sum = None; 43 | } 44 | Msg::ChangedAddend2(value) => { 45 | self.addend2 = value; 46 | self.sum = None; 47 | } 48 | } 49 | true 50 | } 51 | 52 | fn change(&mut self, _props: Self::Properties) -> ShouldRender { 53 | // Should only return "true" if new properties are different to 54 | // previously received properties. 55 | // This component has no properties so we will always return "false". 56 | false 57 | } 58 | 59 | fn view(&self) -> Html { 60 | let numeric = "text-align: right;"; 61 | html! { 62 | 63 | 64 | 65 | 70 | 71 | 72 | 73 | 78 | 79 | 80 | 81 | 86 | 87 | 88 | 89 | 99 | 100 |
{"Addend 1:"} 66 | 69 |
{"Addend 2:"} 74 | 77 |
82 |
{"Sum:"} 90 | n.to_string(), None => "".to_string() } 96 | }, 97 | /> 98 |
101 | } 102 | } 103 | } 104 | 105 | #[wasm_bindgen(start)] 106 | pub fn run_app() { 107 | App::::new().mount_to_body(); 108 | } 109 | -------------------------------------------------------------------------------- /Chapter05/adder/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Yew Sample App 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter05/incr/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "incr" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | yew = "0.17.4" 9 | wasm-bindgen = "0.2.67" 10 | 11 | [lib] 12 | crate-type = ["cdylib", "rlib"] 13 | -------------------------------------------------------------------------------- /Chapter05/incr/src/lib.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | use yew::prelude::*; 3 | use yew::events::KeyboardEvent; 4 | 5 | struct Model { 6 | link: ComponentLink, 7 | value: u64, 8 | } 9 | 10 | enum Msg { 11 | Increment, 12 | Reset, 13 | KeyDown(String), 14 | } 15 | 16 | impl Component for Model { 17 | type Message = Msg; 18 | type Properties = (); 19 | fn create(_: Self::Properties, link: ComponentLink) -> Self { 20 | Self { value: 0, link } 21 | } 22 | 23 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 24 | match msg { 25 | Msg::Increment => { 26 | self.value += 1; 27 | true 28 | } 29 | Msg::Reset => { 30 | self.value = 0; 31 | true 32 | } 33 | Msg::KeyDown(s) => match s.as_ref() { 34 | "+" => { 35 | self.value += 1; 36 | true 37 | } 38 | "0" => { 39 | self.value = 0; 40 | true 41 | } 42 | _ => false, 43 | }, 44 | } 45 | } 46 | 47 | fn change(&mut self, _props: Self::Properties) -> ShouldRender { 48 | // Should only return "true" if new properties are different to 49 | // previously received properties. 50 | // This component has no properties so we will always return "false". 51 | false 52 | } 53 | 54 | fn view(&self) -> Html { 55 | html! { 56 |
57 | 58 | 59 | 64 |
65 | } 66 | } 67 | } 68 | 69 | #[wasm_bindgen(start)] 70 | pub fn run_app() { 71 | App::::new().mount_to_body(); 72 | } 73 | -------------------------------------------------------------------------------- /Chapter05/incr/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Yew Sample App 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter05/login/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "login" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | yew = "0.17.4" 9 | wasm-bindgen = "0.2.67" 10 | 11 | [lib] 12 | crate-type = ["cdylib", "rlib"] 13 | -------------------------------------------------------------------------------- /Chapter05/login/src/db_access.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, PartialEq, Debug)] 2 | pub enum DbPrivilege { 3 | CanRead, 4 | CanWrite, 5 | } 6 | 7 | #[derive(Clone, Debug, PartialEq)] 8 | pub struct User { 9 | pub username: String, 10 | pub password: String, 11 | pub privileges: Vec, 12 | } 13 | 14 | #[derive(PartialEq, Clone)] 15 | pub struct DbConnection { 16 | users: Vec, 17 | } 18 | 19 | impl DbConnection { 20 | pub fn new() -> DbConnection { 21 | DbConnection { 22 | users: vec![ 23 | User { 24 | username: "joe".to_string(), 25 | password: "xjoe".to_string(), 26 | privileges: vec![DbPrivilege::CanRead], 27 | }, 28 | User { 29 | username: "susan".to_string(), 30 | password: "xsusan".to_string(), 31 | privileges: vec![DbPrivilege::CanRead, DbPrivilege::CanWrite], 32 | }, 33 | ], 34 | } 35 | } 36 | 37 | pub fn get_user_by_username(&self, username: &str) -> Option<&User> { 38 | if let Some(u) = self.users.iter().find(|u| u.username == username) { 39 | Some(u) 40 | } else { 41 | None 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Chapter05/login/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "512"] 2 | 3 | use wasm_bindgen::prelude::*; 4 | use yew::prelude::*; 5 | 6 | mod db_access; 7 | use crate::login::LoginModel; 8 | use db_access::{DbConnection, DbPrivilege, User}; 9 | 10 | mod login; 11 | 12 | enum Page { 13 | Login, 14 | PersonsList, 15 | } 16 | 17 | struct MainModel { 18 | page: Page, 19 | current_user: Option, 20 | can_write: bool, 21 | db_connection: std::rc::Rc>, 22 | link: ComponentLink, 23 | } 24 | 25 | enum MainMsg { 26 | LoggedIn(User), 27 | ChangeUserPressed, 28 | } 29 | 30 | impl Component for MainModel { 31 | type Message = MainMsg; 32 | type Properties = (); 33 | 34 | fn create(_: Self::Properties, link: ComponentLink) -> Self { 35 | MainModel { 36 | page: Page::Login, 37 | current_user: None, 38 | can_write: false, 39 | db_connection: std::rc::Rc::new(std::cell::RefCell::new(DbConnection::new())), 40 | link, 41 | } 42 | } 43 | 44 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 45 | match msg { 46 | MainMsg::LoggedIn(user) => { 47 | self.page = Page::PersonsList; 48 | self.current_user = Some(user.username); 49 | self.can_write = user.privileges.contains(&DbPrivilege::CanWrite); 50 | } 51 | MainMsg::ChangeUserPressed => self.page = Page::Login, 52 | } 53 | true 54 | } 55 | 56 | fn change(&mut self, _props: Self::Properties) -> ShouldRender { 57 | // Should only return "true" if new properties are different to 58 | // previously received properties. 59 | // This component has no properties so we will always return "false". 60 | false 61 | } 62 | 63 | fn view(&self) -> Html { 64 | html! { 65 |
66 | 71 |
72 |

{ "Persons management" }

73 |

74 | { "Current user: " } 75 | 76 | { 77 | if let Some(user) = &self.current_user { 78 | user 79 | } 80 | else { 81 | "---" 82 | } 83 | } 84 | 85 | { 86 | match self.page { 87 | Page::Login => html! {

}, 88 | _ => html! { 89 | 90 | { " " } 91 | 95 | 96 | }, 97 | } 98 | } 99 |

100 |
101 |
102 | { 103 | match &self.page { 104 | Page::Login => html! { 105 | 110 | }, 111 | Page::PersonsList => html! { 112 |

{ "Page to be implemented" }

113 | }, 114 | } 115 | } 116 |
117 |
118 | { "\u{A9} Carlo Milanesi - Developed using Yew" } 119 |
120 |
121 | } 122 | } 123 | } 124 | 125 | #[wasm_bindgen(start)] 126 | pub fn run_app() { 127 | App::::new().mount_to_body(); 128 | } 129 | -------------------------------------------------------------------------------- /Chapter05/login/src/login.rs: -------------------------------------------------------------------------------- 1 | use yew::services::DialogService; 2 | use yew::{html, Callback, Component, ComponentLink, Html, ShouldRender, Properties}; 3 | use yew::events::InputData; 4 | 5 | use crate::db_access::{DbConnection, User}; 6 | 7 | pub struct LoginModel { 8 | username: String, 9 | password: String, 10 | when_logged_in: Option>, 11 | db_connection: std::rc::Rc>, 12 | link: ComponentLink, 13 | } 14 | 15 | #[derive(Debug)] 16 | pub enum LoginMsg { 17 | UsernameChanged(String), 18 | PasswordChanged(String), 19 | LoginPressed, 20 | } 21 | 22 | #[derive(PartialEq, Clone, Properties)] 23 | pub struct LoginProps { 24 | pub current_username: Option, 25 | pub when_logged_in: Option>, 26 | pub db_connection: Option>>, 27 | } 28 | 29 | impl Default for LoginProps { 30 | fn default() -> Self { 31 | LoginProps { 32 | current_username: None, 33 | when_logged_in: None, 34 | db_connection: None, 35 | } 36 | } 37 | } 38 | 39 | impl Component for LoginModel { 40 | type Message = LoginMsg; 41 | type Properties = LoginProps; 42 | 43 | fn create(props: Self::Properties, link: ComponentLink) -> Self { 44 | LoginModel { 45 | username: props.current_username.unwrap_or_default(), 46 | password: String::new(), 47 | when_logged_in: props.when_logged_in, 48 | db_connection: props.db_connection.unwrap(), 49 | link, 50 | } 51 | } 52 | 53 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 54 | match msg { 55 | LoginMsg::UsernameChanged(username) => self.username = username, 56 | LoginMsg::PasswordChanged(password) => self.password = password, 57 | LoginMsg::LoginPressed => { 58 | if let Some(user) = self 59 | .db_connection 60 | .borrow() 61 | .get_user_by_username(&self.username) 62 | { 63 | if user.password == self.password { 64 | if let Some(ref go_to_page) = self.when_logged_in { 65 | go_to_page.emit(user.clone()); 66 | } 67 | } else { 68 | DialogService::alert("Invalid password for the specified user."); 69 | } 70 | } else { 71 | DialogService::alert("User not found."); 72 | } 73 | } 74 | } 75 | true 76 | } 77 | 78 | fn change(&mut self, props: Self::Properties) -> ShouldRender { 79 | self.username = props.current_username.unwrap_or_default(); 80 | self.when_logged_in = props.when_logged_in; 81 | self.db_connection = props.db_connection.unwrap(); 82 | true 83 | } 84 | 85 | fn view(&self) -> Html { 86 | html! { 87 |
88 |
89 | 90 | 95 |
96 |
97 | 98 | 102 |
103 | 107 |
108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Chapter05/login/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Yew Sample App 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter05/persons_db/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "persons_db" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | actix-web = "1" 9 | serde = "1" 10 | serde_derive = "1" 11 | serde_json = "1" 12 | actix-cors = "0.1" 13 | actix-web-httpauth = "0.3" 14 | -------------------------------------------------------------------------------- /Chapter05/persons_db/src/db_access.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::Serialize; 2 | 3 | #[derive(Clone, Debug, Serialize)] 4 | pub struct Person { 5 | pub id: u32, 6 | pub name: String, 7 | } 8 | 9 | #[derive(Clone, Copy, PartialEq, Debug, Serialize)] 10 | pub enum DbPrivilege { 11 | CanRead, 12 | CanWrite, 13 | } 14 | 15 | #[derive(Clone, Debug, PartialEq, Serialize)] 16 | pub struct User { 17 | pub username: String, 18 | pub password: String, 19 | pub privileges: Vec, 20 | } 21 | 22 | pub struct DbConnection { 23 | persons: Vec, 24 | users: Vec, 25 | } 26 | 27 | impl DbConnection { 28 | pub fn new() -> DbConnection { 29 | DbConnection { 30 | persons: vec![], 31 | users: vec![ 32 | User { 33 | username: "joe".to_string(), 34 | password: "xjoe".to_string(), 35 | privileges: vec![DbPrivilege::CanRead], 36 | }, 37 | User { 38 | username: "susan".to_string(), 39 | password: "xsusan".to_string(), 40 | privileges: vec![DbPrivilege::CanRead, DbPrivilege::CanWrite], 41 | }, 42 | ], 43 | } 44 | } 45 | 46 | pub fn get_user_by_username(&self, username: &str) -> Option { 47 | if let Some(u) = self.users.iter().find(|u| u.username == username) { 48 | Some(u.clone()) 49 | } else { 50 | None 51 | } 52 | } 53 | 54 | pub fn get_person_by_id(&self, id: u32) -> Option { 55 | Some(self.persons.iter().find(|p| p.id == id)?.clone()) 56 | } 57 | 58 | pub fn get_persons_by_partial_name<'a>( 59 | &'a self, 60 | subname: &'a str, 61 | ) -> impl Iterator + 'a { 62 | self.persons 63 | .iter() 64 | .filter(move |p| p.name.contains(subname)) 65 | } 66 | 67 | pub fn delete_by_id(&mut self, id: u32) -> bool { 68 | if let Some((n, _)) = self.persons.iter().enumerate().find(|(_, p)| p.id == id) { 69 | self.persons.remove(n); 70 | true 71 | } else { 72 | false 73 | } 74 | } 75 | 76 | pub fn insert_person(&mut self, mut person: Person) -> u32 { 77 | let new_id = if self.persons.is_empty() { 78 | 1 79 | } else { 80 | self.persons[self.persons.len() - 1].id + 1 81 | }; 82 | person.id = new_id; 83 | self.persons.push(person); 84 | new_id 85 | } 86 | 87 | pub fn update_person(&mut self, person: Person) -> bool { 88 | if let Some((n, _)) = self 89 | .persons 90 | .iter() 91 | .enumerate() 92 | .find(|(_, p)| p.id == person.id) 93 | { 94 | self.persons[n] = person; 95 | true 96 | } else { 97 | false 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Chapter05/yauth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yauth" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | yew = "0.17.4" 9 | wasm-bindgen = "0.2.67" 10 | 11 | [lib] 12 | crate-type = ["cdylib", "rlib"] 13 | -------------------------------------------------------------------------------- /Chapter05/yauth/src/db_access.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq)] 2 | pub struct Person { 3 | pub id: u32, 4 | pub name: String, 5 | } 6 | 7 | #[derive(Clone, Copy, PartialEq, Debug)] 8 | pub enum DbPrivilege { 9 | CanRead, 10 | CanWrite, 11 | } 12 | 13 | #[derive(Clone, Debug, PartialEq)] 14 | pub struct User { 15 | pub username: String, 16 | pub password: String, 17 | pub privileges: Vec, 18 | } 19 | 20 | #[derive(PartialEq, Clone)] 21 | pub struct DbConnection { 22 | persons: Vec, 23 | users: Vec, 24 | } 25 | 26 | impl DbConnection { 27 | pub fn new() -> DbConnection { 28 | DbConnection { 29 | persons: vec![], 30 | users: vec![ 31 | User { 32 | username: "joe".to_string(), 33 | password: "xjoe".to_string(), 34 | privileges: vec![DbPrivilege::CanRead], 35 | }, 36 | User { 37 | username: "susan".to_string(), 38 | password: "xsusan".to_string(), 39 | privileges: vec![DbPrivilege::CanRead, DbPrivilege::CanWrite], 40 | }, 41 | ], 42 | } 43 | } 44 | 45 | pub fn get_user_by_username(&self, username: &str) -> Option<&User> { 46 | if let Some(u) = self.users.iter().find(|u| u.username == username) { 47 | Some(u) 48 | } else { 49 | None 50 | } 51 | } 52 | 53 | pub fn get_person_by_id(&self, id: u32) -> Option<&Person> { 54 | if let Some(p) = self.persons.iter().find(|p| p.id == id) { 55 | Some(p) 56 | } else { 57 | None 58 | } 59 | } 60 | 61 | pub fn get_persons_by_partial_name(&self, subname: &str) -> Vec { 62 | self.persons 63 | .iter() 64 | .filter(|p| p.name.contains(subname)) 65 | .cloned() 66 | .collect() 67 | } 68 | 69 | pub fn delete_by_id(&mut self, id: u32) -> bool { 70 | if let Some((n, _)) = self.persons.iter().enumerate().find(|(_, p)| p.id == id) { 71 | self.persons.remove(n); 72 | true 73 | } else { 74 | false 75 | } 76 | } 77 | 78 | pub fn insert_person(&mut self, mut person: Person) -> u32 { 79 | let new_id = if self.persons.is_empty() { 80 | 1 81 | } else { 82 | self.persons[self.persons.len() - 1].id + 1 83 | }; 84 | person.id = new_id; 85 | self.persons.push(person); 86 | new_id 87 | } 88 | 89 | pub fn update_person(&mut self, person: Person) -> bool { 90 | if let Some((n, _)) = self 91 | .persons 92 | .iter() 93 | .enumerate() 94 | .find(|(_, p)| p.id == person.id) 95 | { 96 | self.persons[n] = person; 97 | true 98 | } else { 99 | false 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Chapter05/yauth/src/login.rs: -------------------------------------------------------------------------------- 1 | use yew::services::DialogService; 2 | use yew::{html, Callback, Component, ComponentLink, Html, ShouldRender, Properties}; 3 | use yew::events::InputData; 4 | 5 | use crate::db_access::{DbConnection, User}; 6 | 7 | pub struct LoginModel { 8 | username: String, 9 | password: String, 10 | when_logged_in: Option>, 11 | db_connection: std::rc::Rc>, 12 | link: ComponentLink, 13 | } 14 | 15 | #[derive(Debug)] 16 | pub enum LoginMsg { 17 | UsernameChanged(String), 18 | PasswordChanged(String), 19 | LoginPressed, 20 | } 21 | 22 | #[derive(PartialEq, Clone, Properties)] 23 | pub struct LoginProps { 24 | pub current_username: Option, 25 | pub when_logged_in: Option>, 26 | pub db_connection: Option>>, 27 | } 28 | 29 | impl Default for LoginProps { 30 | fn default() -> Self { 31 | LoginProps { 32 | current_username: None, 33 | when_logged_in: None, 34 | db_connection: None, 35 | } 36 | } 37 | } 38 | 39 | impl Component for LoginModel { 40 | type Message = LoginMsg; 41 | type Properties = LoginProps; 42 | 43 | fn create(props: Self::Properties, link: ComponentLink) -> Self { 44 | LoginModel { 45 | username: props.current_username.unwrap_or_default(), 46 | password: String::new(), 47 | when_logged_in: props.when_logged_in, 48 | db_connection: props.db_connection.unwrap(), 49 | link, 50 | } 51 | } 52 | 53 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 54 | match msg { 55 | LoginMsg::UsernameChanged(username) => self.username = username, 56 | LoginMsg::PasswordChanged(password) => self.password = password, 57 | LoginMsg::LoginPressed => { 58 | if let Some(user) = self 59 | .db_connection 60 | .borrow() 61 | .get_user_by_username(&self.username) 62 | { 63 | if user.password == self.password { 64 | if let Some(ref go_to_page) = self.when_logged_in { 65 | go_to_page.emit(user.clone()); 66 | } 67 | } else { 68 | DialogService::alert("Invalid password for the specified user."); 69 | } 70 | } else { 71 | DialogService::alert("User not found."); 72 | } 73 | } 74 | } 75 | true 76 | } 77 | 78 | fn change(&mut self, props: Self::Properties) -> ShouldRender { 79 | self.username = props.current_username.unwrap_or_default(); 80 | self.when_logged_in = props.when_logged_in; 81 | self.db_connection = props.db_connection.unwrap(); 82 | true 83 | } 84 | 85 | fn view(&self) -> Html { 86 | html! { 87 |
88 |
89 | 90 | 95 |
96 |
97 | 98 | 102 |
103 | 107 |
108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Chapter05/yauth/src/one_person.rs: -------------------------------------------------------------------------------- 1 | use yew::{html, Callback, Component, ComponentLink, Html, Properties, ShouldRender}; 2 | use yew::events::InputData; 3 | 4 | use crate::db_access::{DbConnection, Person}; 5 | 6 | pub struct OnePersonModel { 7 | id: Option, 8 | name: String, 9 | can_write: bool, 10 | is_inserting: bool, 11 | go_to_persons_list_page: Option>, 12 | db_connection: std::rc::Rc>, 13 | link: ComponentLink, 14 | } 15 | 16 | #[derive(Debug)] 17 | pub enum OnePersonMsg { 18 | NameChanged(String), 19 | SavePressed, 20 | CancelPressed, 21 | } 22 | 23 | #[derive(PartialEq, Clone, Properties)] 24 | pub struct OnePersonProps { 25 | pub id: Option, 26 | pub name: String, 27 | pub can_write: bool, 28 | pub go_to_persons_list_page: Option>, 29 | pub db_connection: Option>>, 30 | } 31 | 32 | impl Default for OnePersonProps { 33 | fn default() -> Self { 34 | OnePersonProps { 35 | id: None, 36 | name: "".to_string(), 37 | can_write: false, 38 | go_to_persons_list_page: None, 39 | db_connection: None, 40 | } 41 | } 42 | } 43 | 44 | impl Component for OnePersonModel { 45 | type Message = OnePersonMsg; 46 | type Properties = OnePersonProps; 47 | 48 | fn create(props: Self::Properties, link: ComponentLink) -> Self { 49 | OnePersonModel { 50 | id: props.id, 51 | name: props.name, 52 | can_write: props.can_write, 53 | is_inserting: props.id.is_none(), 54 | go_to_persons_list_page: props.go_to_persons_list_page, 55 | db_connection: props.db_connection.unwrap(), 56 | link, 57 | } 58 | } 59 | 60 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 61 | match msg { 62 | OnePersonMsg::NameChanged(name) => self.name = name, 63 | OnePersonMsg::SavePressed => { 64 | if self.is_inserting { 65 | self.db_connection.borrow_mut().insert_person(Person { 66 | id: 0, 67 | name: self.name.clone(), 68 | }); 69 | } else { 70 | self.db_connection.borrow_mut().update_person(Person { 71 | id: self.id.unwrap(), 72 | name: self.name.clone(), 73 | }); 74 | } 75 | if let Some(ref go_to_page) = self.go_to_persons_list_page { 76 | go_to_page.emit(()); 77 | } 78 | } 79 | OnePersonMsg::CancelPressed => { 80 | if let Some(ref go_to_page) = self.go_to_persons_list_page { 81 | go_to_page.emit(()); 82 | } 83 | } 84 | } 85 | true 86 | } 87 | 88 | fn change(&mut self, props: Self::Properties) -> ShouldRender { 89 | self.id = props.id; 90 | self.name = props.name; 91 | self.can_write = props.can_write; 92 | self.is_inserting = props.id.is_none(); 93 | self.go_to_persons_list_page = props.go_to_persons_list_page; 94 | self.db_connection = props.db_connection.unwrap(); 95 | true 96 | } 97 | 98 | fn view(&self) -> Html { 99 | html! { 100 |
101 |
102 | 103 | format!("{}", id), _ => "".to_string() }, 106 | disabled=true, 107 | /> 108 |
109 |
110 | 111 | 117 |
118 |
119 | 125 | { " " } 126 | 132 |
133 |
134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Chapter05/yauth/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Yew Sample App 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter05/yclient/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yclient" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | anyhow = "1" 9 | yew = "0.17.4" 10 | wasm-bindgen = "0.2.67" 11 | failure = "0.1" 12 | serde = "1" 13 | serde_derive = "1" 14 | url = "2.1" 15 | base64 = "0.13" 16 | 17 | [lib] 18 | crate-type = ["cdylib", "rlib"] 19 | -------------------------------------------------------------------------------- /Chapter05/yclient/src/common.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::Deserialize; 2 | use yew::services::fetch::Request; 3 | 4 | pub const BACKEND_SITE: &str = "http://localhost:8080/"; 5 | 6 | #[derive(Clone, Debug, PartialEq, Deserialize)] 7 | pub struct Person { 8 | pub id: u32, 9 | pub name: String, 10 | } 11 | 12 | #[derive(Clone, Copy, PartialEq, Debug, Deserialize)] 13 | pub enum DbPrivilege { 14 | CanRead, 15 | CanWrite, 16 | } 17 | 18 | #[derive(Clone, Debug, PartialEq, Deserialize)] 19 | pub struct User { 20 | pub username: String, 21 | pub password: String, 22 | pub privileges: Vec, 23 | } 24 | 25 | pub fn add_auth(username: &str, password: &str, request: &mut Request) { 26 | let mut auth_string = "Basic ".to_string(); 27 | base64::encode_config_buf( 28 | format!("{}:{}", username, password).as_bytes(), 29 | base64::STANDARD, 30 | &mut auth_string, 31 | ); 32 | request 33 | .headers_mut() 34 | .append("authorization", auth_string.parse().unwrap()); 35 | } 36 | -------------------------------------------------------------------------------- /Chapter05/yclient/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Yew Sample App 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter06/assets_slalom/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assets_slalom" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | quicksilver = "0.3" 9 | rand = "0.6" 10 | -------------------------------------------------------------------------------- /Chapter06/assets_slalom/static/bump.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter06/assets_slalom/static/bump.ogg -------------------------------------------------------------------------------- /Chapter06/assets_slalom/static/click.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter06/assets_slalom/static/click.ogg -------------------------------------------------------------------------------- /Chapter06/assets_slalom/static/font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter06/assets_slalom/static/font.ttf -------------------------------------------------------------------------------- /Chapter06/assets_slalom/static/two_notes.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter06/assets_slalom/static/two_notes.ogg -------------------------------------------------------------------------------- /Chapter06/assets_slalom/static/whoosh.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter06/assets_slalom/static/whoosh.ogg -------------------------------------------------------------------------------- /Chapter06/silent_slalom/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "silent_slalom" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | quicksilver = "0.3" 9 | rand = "0.6" 10 | -------------------------------------------------------------------------------- /Chapter06/ski/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ski" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | quicksilver = "0.3" 9 | -------------------------------------------------------------------------------- /Chapter06/ski/src/main.rs: -------------------------------------------------------------------------------- 1 | use quicksilver::{ 2 | geom::{Rectangle, Transform, Triangle, Vector}, 3 | graphics::{Background, Color}, 4 | input::Key, 5 | lifecycle::{run, Settings, State, Window}, 6 | Result, 7 | }; 8 | 9 | const SCREEN_WIDTH: f32 = 800.; 10 | const SCREEN_HEIGHT: f32 = 600.; 11 | const SKI_WIDTH: f32 = 10.; 12 | const SKI_LENGTH: f32 = 50.; 13 | const SKI_TIP_LEN: f32 = 20.; 14 | const STEERING_SPEED: f32 = 3.5; 15 | const MAX_ANGLE: f32 = 75.; 16 | 17 | struct Screen { 18 | ski_across_offset: f32, 19 | direction: f32, 20 | } 21 | 22 | impl Screen { 23 | fn steer(&mut self, side: f32) { 24 | self.direction += STEERING_SPEED * side; 25 | if self.direction > MAX_ANGLE { 26 | self.direction = MAX_ANGLE; 27 | } else if self.direction < -MAX_ANGLE { 28 | self.direction = -MAX_ANGLE; 29 | } 30 | } 31 | } 32 | 33 | impl State for Screen { 34 | fn new() -> Result { 35 | Ok(Screen { 36 | ski_across_offset: 0., 37 | direction: 0., 38 | }) 39 | } 40 | 41 | fn update(&mut self, window: &mut Window) -> Result<()> { 42 | if window.keyboard()[Key::Right].is_down() { 43 | self.steer(1.); 44 | } 45 | if window.keyboard()[Key::Left].is_down() { 46 | self.steer(-1.); 47 | } 48 | Ok(()) 49 | } 50 | 51 | fn draw(&mut self, window: &mut Window) -> Result<()> { 52 | window.clear(Color::WHITE)?; 53 | window.draw_ex( 54 | &Rectangle::new( 55 | ( 56 | SCREEN_WIDTH / 2. + self.ski_across_offset - SKI_WIDTH / 2., 57 | SCREEN_HEIGHT * 15. / 16. - SKI_LENGTH / 2., 58 | ), 59 | (SKI_WIDTH, SKI_LENGTH), 60 | ), 61 | Background::Col(Color::PURPLE), 62 | Transform::translate(Vector::new(0, -SKI_LENGTH / 2. - SKI_TIP_LEN)) 63 | * Transform::rotate(self.direction) 64 | * Transform::translate(Vector::new(0, SKI_LENGTH / 2. + SKI_TIP_LEN)), 65 | 0, 66 | ); 67 | 68 | window.draw_ex( 69 | &Triangle::new( 70 | Vector::new( 71 | SCREEN_WIDTH / 2. + self.ski_across_offset - SKI_WIDTH / 2., 72 | SCREEN_HEIGHT * 15. / 16. - SKI_LENGTH / 2., 73 | ), 74 | Vector::new( 75 | SCREEN_WIDTH / 2. + self.ski_across_offset + SKI_WIDTH / 2., 76 | SCREEN_HEIGHT * 15. / 16. - SKI_LENGTH / 2., 77 | ), 78 | Vector::new( 79 | SCREEN_WIDTH / 2. + self.ski_across_offset, 80 | SCREEN_HEIGHT * 15. / 16. - SKI_LENGTH / 2. - SKI_TIP_LEN, 81 | ), 82 | ), 83 | Background::Col(Color::INDIGO), 84 | Transform::translate(Vector::new(0, -SKI_TIP_LEN * 2. / 3.)) 85 | * Transform::rotate(self.direction) 86 | * Transform::translate(Vector::new(0, SKI_TIP_LEN * 2. / 3.)), 87 | 0, 88 | ); 89 | 90 | Ok(()) 91 | } 92 | } 93 | 94 | fn main() { 95 | run::( 96 | "Ski", 97 | Vector::new(SCREEN_WIDTH, SCREEN_HEIGHT), 98 | Settings { 99 | draw_rate: 40., 100 | update_rate: 40., 101 | ..Settings::default() 102 | }, 103 | ); 104 | } 105 | -------------------------------------------------------------------------------- /Chapter07/gg_assets_slalom/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gg_assets_slalom" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ggez = "0.5" 9 | rand = "0.6" 10 | nalgebra = "0.18" 11 | -------------------------------------------------------------------------------- /Chapter07/gg_assets_slalom/static/bump.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter07/gg_assets_slalom/static/bump.ogg -------------------------------------------------------------------------------- /Chapter07/gg_assets_slalom/static/click.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter07/gg_assets_slalom/static/click.ogg -------------------------------------------------------------------------------- /Chapter07/gg_assets_slalom/static/font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter07/gg_assets_slalom/static/font.ttf -------------------------------------------------------------------------------- /Chapter07/gg_assets_slalom/static/two_notes.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter07/gg_assets_slalom/static/two_notes.ogg -------------------------------------------------------------------------------- /Chapter07/gg_assets_slalom/static/whoosh.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter07/gg_assets_slalom/static/whoosh.ogg -------------------------------------------------------------------------------- /Chapter07/gg_silent_slalom/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gg_silent_slalom" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ggez = "0.5" 9 | rand = "0.6" 10 | nalgebra = "0.18" 11 | -------------------------------------------------------------------------------- /Chapter07/gg_ski/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gg_ski" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ggez = "0.5" 9 | nalgebra = "0.18" 10 | -------------------------------------------------------------------------------- /Chapter07/gg_whac/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gg_whac" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ggez = "0.5" 9 | rand = "0.6" 10 | nalgebra = "0.18" 11 | -------------------------------------------------------------------------------- /Chapter07/gg_whac/assets/bump.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter07/gg_whac/assets/bump.ogg -------------------------------------------------------------------------------- /Chapter07/gg_whac/assets/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter07/gg_whac/assets/button.png -------------------------------------------------------------------------------- /Chapter07/gg_whac/assets/click.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter07/gg_whac/assets/click.ogg -------------------------------------------------------------------------------- /Chapter07/gg_whac/assets/cry.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter07/gg_whac/assets/cry.ogg -------------------------------------------------------------------------------- /Chapter07/gg_whac/assets/font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter07/gg_whac/assets/font.ttf -------------------------------------------------------------------------------- /Chapter07/gg_whac/assets/lawn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter07/gg_whac/assets/lawn.jpg -------------------------------------------------------------------------------- /Chapter07/gg_whac/assets/mallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter07/gg_whac/assets/mallet.png -------------------------------------------------------------------------------- /Chapter07/gg_whac/assets/mole.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter07/gg_whac/assets/mole.png -------------------------------------------------------------------------------- /Chapter07/gg_whac/assets/two_notes.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Creative-Projects-for-Rust-Programmers/ab1f5f09510b6e19e9c9df112fd5c7822372cd38/Chapter07/gg_whac/assets/two_notes.ogg -------------------------------------------------------------------------------- /Chapter08/calc_analyzer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calc_analyzer" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | nom = "5" 9 | -------------------------------------------------------------------------------- /Chapter08/calc_analyzer/data/bad_sum.calc: -------------------------------------------------------------------------------- 1 | @a 2 | @d 3 | >a 4 | >b 5 | a 4 | >b 5 | ), 14 | } 15 | 16 | pub type AnalyzedTerm = (AnalyzedFactor, Vec<(TermOperator, AnalyzedFactor)>); 17 | 18 | pub type AnalyzedExpr = (AnalyzedTerm, Vec<(ExprOperator, AnalyzedTerm)>); 19 | 20 | #[derive(Debug)] 21 | pub enum AnalyzedStatement { 22 | Declaration(usize), 23 | InputOperation(usize), 24 | OutputOperation(AnalyzedExpr), 25 | Assignment(usize, AnalyzedExpr), 26 | } 27 | 28 | pub type AnalyzedProgram = Vec; 29 | 30 | pub fn analyze_program( 31 | variables: &mut SymbolTable, 32 | parsed_program: &ParsedProgram, 33 | ) -> Result { 34 | let mut analyzed_program = AnalyzedProgram::new(); 35 | for statement in parsed_program { 36 | analyzed_program.push(analyze_statement(variables, statement)?); 37 | } 38 | Ok(analyzed_program) 39 | } 40 | 41 | fn analyze_factor( 42 | variables: &mut SymbolTable, 43 | parsed_factor: &ParsedFactor, 44 | ) -> Result { 45 | match parsed_factor { 46 | ParsedFactor::Literal(value) => Ok(AnalyzedFactor::Literal(*value)), 47 | ParsedFactor::Identifier(name) => { 48 | Ok(AnalyzedFactor::Identifier(variables.find_symbol(name)?)) 49 | } 50 | ParsedFactor::SubExpression(expr) => Ok(AnalyzedFactor::SubExpression( 51 | Box::::new(analyze_expr(variables, expr)?), 52 | )), 53 | } 54 | } 55 | 56 | fn analyze_term( 57 | variables: &mut SymbolTable, 58 | parsed_term: &ParsedTerm, 59 | ) -> Result { 60 | let first_factor = analyze_factor(variables, &parsed_term.0)?; 61 | let mut other_factors = Vec::<(TermOperator, AnalyzedFactor)>::new(); 62 | for factor in &parsed_term.1 { 63 | other_factors.push((factor.0, analyze_factor(variables, &factor.1)?)); 64 | } 65 | Ok((first_factor, other_factors)) 66 | } 67 | 68 | fn analyze_expr( 69 | variables: &mut SymbolTable, 70 | parsed_expr: &ParsedExpr, 71 | ) -> Result { 72 | let first_term = analyze_term(variables, &parsed_expr.0)?; 73 | let mut other_terms = Vec::<(ExprOperator, AnalyzedTerm)>::new(); 74 | for term in &parsed_expr.1 { 75 | other_terms.push((term.0, analyze_term(variables, &term.1)?)); 76 | } 77 | Ok((first_term, other_terms)) 78 | } 79 | 80 | fn analyze_statement( 81 | variables: &mut SymbolTable, 82 | parsed_statement: &ParsedStatement, 83 | ) -> Result { 84 | match parsed_statement { 85 | ParsedStatement::Assignment(identifier, expr) => { 86 | let handle = variables.find_symbol(identifier)?; 87 | let analyzed_expr = analyze_expr(variables, expr)?; 88 | Ok(AnalyzedStatement::Assignment(handle, analyzed_expr)) 89 | } 90 | ParsedStatement::Declaration(identifier) => { 91 | let handle = variables.insert_symbol(identifier)?; 92 | Ok(AnalyzedStatement::Declaration(handle)) 93 | } 94 | ParsedStatement::InputOperation(identifier) => { 95 | let handle = variables.find_symbol(identifier)?; 96 | Ok(AnalyzedStatement::InputOperation(handle)) 97 | } 98 | ParsedStatement::OutputOperation(expr) => { 99 | let analyzed_expr = analyze_expr(variables, expr)?; 100 | Ok(AnalyzedStatement::OutputOperation(analyzed_expr)) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Chapter08/calc_analyzer/src/main.rs: -------------------------------------------------------------------------------- 1 | mod analyzer; 2 | mod parser; 3 | mod symbol_table; 4 | 5 | fn main() { 6 | let mut args = std::env::args(); 7 | let current_program_path = args.next().unwrap(); 8 | let source_path = args.next(); 9 | if source_path.is_none() { 10 | eprintln!("{}: Missing argument .calc", current_program_path); 11 | } else { 12 | process_file(¤t_program_path, &source_path.unwrap()); 13 | } 14 | } 15 | 16 | fn process_file(current_program_path: &str, source_path: &str) { 17 | const CALC_SUFFIX: &str = ".calc"; 18 | if !source_path.ends_with(CALC_SUFFIX) { 19 | eprintln!( 20 | "{}: Invalid argument '{}': It must end with {}", 21 | current_program_path, source_path, CALC_SUFFIX 22 | ); 23 | return; 24 | } 25 | let source_code = std::fs::read_to_string(&source_path); 26 | if source_code.is_err() { 27 | eprintln!( 28 | "Failed to read from file {}: ({})", 29 | source_path, 30 | source_code.unwrap_err() 31 | ); 32 | return; 33 | } 34 | let source_code = source_code.unwrap(); 35 | 36 | let parsed_program; 37 | match parser::parse_program(&source_code) { 38 | Ok((rest, syntax_tree)) => { 39 | let trimmed_rest = rest.trim(); 40 | if trimmed_rest.len() > 0 { 41 | eprintln!( 42 | "Invalid remaining code in '{}': {}", 43 | source_path, trimmed_rest 44 | ); 45 | return; 46 | } 47 | parsed_program = syntax_tree; 48 | } 49 | Err(err) => { 50 | eprintln!("Invalid code in '{}': {:?}", source_path, err); 51 | return; 52 | } 53 | } 54 | 55 | let analyzed_program; 56 | let mut variables = symbol_table::SymbolTable::new(); 57 | match analyzer::analyze_program(&mut variables, &parsed_program) { 58 | Ok(analyzed_tree) => { 59 | analyzed_program = analyzed_tree; 60 | } 61 | Err(err) => { 62 | eprintln!("Invalid code in '{}': {}", source_path, err); 63 | return; 64 | } 65 | } 66 | 67 | println!("Symbol table: {:#?}", variables); 68 | println!("Analyzed program: {:#?}", analyzed_program); 69 | } 70 | -------------------------------------------------------------------------------- /Chapter08/calc_analyzer/src/parser.rs: -------------------------------------------------------------------------------- 1 | extern crate nom; 2 | use nom::{ 3 | branch::alt, 4 | bytes::complete::tag, 5 | bytes::complete::take_while, 6 | character::complete::{alpha1, char}, 7 | combinator::map, 8 | multi::many0, 9 | number::complete::double, 10 | sequence::{delimited, preceded, tuple}, 11 | IResult, 12 | }; 13 | 14 | #[derive(Debug, PartialEq)] 15 | pub enum ParsedFactor<'a> { 16 | Literal(f64), 17 | Identifier(&'a str), 18 | SubExpression(Box>), 19 | } 20 | 21 | #[derive(Debug, PartialEq, Clone, Copy)] 22 | pub enum TermOperator { 23 | Multiply, 24 | Divide, 25 | } 26 | 27 | #[derive(Debug, PartialEq, Clone, Copy)] 28 | pub enum ExprOperator { 29 | Add, 30 | Subtract, 31 | } 32 | 33 | pub type ParsedTerm<'a> = (ParsedFactor<'a>, Vec<(TermOperator, ParsedFactor<'a>)>); 34 | 35 | pub type ParsedExpr<'a> = (ParsedTerm<'a>, Vec<(ExprOperator, ParsedTerm<'a>)>); 36 | 37 | #[derive(Debug)] 38 | pub enum ParsedStatement<'a> { 39 | Declaration(&'a str), 40 | InputOperation(&'a str), 41 | OutputOperation(ParsedExpr<'a>), 42 | Assignment(&'a str, ParsedExpr<'a>), 43 | } 44 | 45 | pub type ParsedProgram<'a> = Vec>; 46 | 47 | pub fn parse_program(input: &str) -> IResult<&str, ParsedProgram> { 48 | many0(preceded( 49 | skip_spaces, 50 | alt(( 51 | parse_declaration, 52 | parse_input_statement, 53 | parse_output_statement, 54 | parse_assignment, 55 | )), 56 | ))(input) 57 | } 58 | 59 | fn parse_declaration(input: &str) -> IResult<&str, ParsedStatement> { 60 | tuple((char('@'), skip_spaces, parse_identifier))(input) 61 | .map(|(input, output)| (input, ParsedStatement::Declaration(output.2))) 62 | } 63 | 64 | fn parse_input_statement(input: &str) -> IResult<&str, ParsedStatement> { 65 | tuple((char('>'), skip_spaces, parse_identifier))(input) 66 | .map(|(input, output)| (input, ParsedStatement::InputOperation(output.2))) 67 | } 68 | 69 | fn parse_output_statement(input: &str) -> IResult<&str, ParsedStatement> { 70 | tuple((char('<'), skip_spaces, parse_expr))(input) 71 | .map(|(input, output)| (input, ParsedStatement::OutputOperation(output.2))) 72 | } 73 | 74 | fn parse_assignment(input: &str) -> IResult<&str, ParsedStatement> { 75 | tuple(( 76 | parse_identifier, 77 | skip_spaces, 78 | tag(":="), 79 | skip_spaces, 80 | parse_expr, 81 | ))(input) 82 | .map(|(input, output)| (input, ParsedStatement::Assignment(output.0, output.4))) 83 | } 84 | 85 | fn parse_identifier(input: &str) -> IResult<&str, &str> { 86 | alpha1(input) 87 | } 88 | 89 | fn parse_subexpr(input: &str) -> IResult<&str, ParsedExpr> { 90 | delimited( 91 | preceded(skip_spaces, char('(')), 92 | parse_expr, 93 | preceded(skip_spaces, char(')')), 94 | )(input) 95 | } 96 | 97 | fn parse_factor(input: &str) -> IResult<&str, ParsedFactor> { 98 | preceded( 99 | skip_spaces, 100 | alt(( 101 | map(parse_identifier, ParsedFactor::Identifier), 102 | map(double, ParsedFactor::Literal), 103 | map(parse_subexpr, |expr| { 104 | ParsedFactor::SubExpression(Box::new(expr)) 105 | }), 106 | )), 107 | )(input) 108 | } 109 | 110 | fn parse_term(input: &str) -> IResult<&str, ParsedTerm> { 111 | tuple(( 112 | parse_factor, 113 | many0(tuple(( 114 | preceded( 115 | skip_spaces, 116 | alt(( 117 | map(char('*'), |_| TermOperator::Multiply), 118 | map(char('/'), |_| TermOperator::Divide), 119 | )), 120 | ), 121 | parse_factor, 122 | ))), 123 | ))(input) 124 | } 125 | 126 | fn parse_expr(input: &str) -> IResult<&str, ParsedExpr> { 127 | tuple(( 128 | parse_term, 129 | many0(tuple(( 130 | preceded( 131 | skip_spaces, 132 | alt(( 133 | map(char('+'), |_| ExprOperator::Add), 134 | map(char('-'), |_| ExprOperator::Subtract), 135 | )), 136 | ), 137 | parse_term, 138 | ))), 139 | ))(input) 140 | } 141 | 142 | fn skip_spaces(input: &str) -> IResult<&str, &str> { 143 | let chars = " \t\r\n"; 144 | take_while(move |ch| chars.contains(ch))(input) 145 | } 146 | -------------------------------------------------------------------------------- /Chapter08/calc_analyzer/src/symbol_table.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct SymbolTable { 3 | entries: Vec<(String, f64)>, 4 | } 5 | 6 | impl SymbolTable { 7 | pub fn new() -> SymbolTable { 8 | SymbolTable { 9 | entries: Vec::<(String, f64)>::new(), 10 | } 11 | } 12 | pub fn insert_symbol(&mut self, identifier: &str) -> Result { 13 | if self 14 | .entries 15 | .iter() 16 | .find(|item| item.0 == identifier) 17 | .is_some() 18 | { 19 | Err(format!( 20 | "Error: Identifier '{}' declared several times.", 21 | identifier 22 | )) 23 | } else { 24 | self.entries.push((identifier.to_string(), 0.)); 25 | Ok(self.entries.len() - 1) 26 | } 27 | } 28 | pub fn find_symbol(&self, identifier: &str) -> Result { 29 | if let Some(pos) = self.entries.iter().position(|item| item.0 == identifier) { 30 | Ok(pos) 31 | } else { 32 | Err(format!( 33 | "Error: Identifier '{}' used before having been declared.", 34 | identifier 35 | )) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Chapter08/calc_compiler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calc_compiler" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | nom = "5" 9 | -------------------------------------------------------------------------------- /Chapter08/calc_compiler/data/bad_sum.calc: -------------------------------------------------------------------------------- 1 | @a 2 | @d 3 | >a 4 | >b 5 | a 4 | >b 5 | ), 14 | } 15 | 16 | pub type AnalyzedTerm = (AnalyzedFactor, Vec<(TermOperator, AnalyzedFactor)>); 17 | 18 | pub type AnalyzedExpr = (AnalyzedTerm, Vec<(ExprOperator, AnalyzedTerm)>); 19 | 20 | #[derive(Debug)] 21 | pub enum AnalyzedStatement { 22 | Declaration(usize), 23 | InputOperation(usize), 24 | OutputOperation(AnalyzedExpr), 25 | Assignment(usize, AnalyzedExpr), 26 | } 27 | 28 | pub type AnalyzedProgram = Vec; 29 | 30 | pub fn analyze_program( 31 | variables: &mut SymbolTable, 32 | parsed_program: &ParsedProgram, 33 | ) -> Result { 34 | let mut analyzed_program = AnalyzedProgram::new(); 35 | for statement in parsed_program { 36 | analyzed_program.push(analyze_statement(variables, statement)?); 37 | } 38 | Ok(analyzed_program) 39 | } 40 | 41 | fn analyze_factor( 42 | variables: &mut SymbolTable, 43 | parsed_factor: &ParsedFactor, 44 | ) -> Result { 45 | match parsed_factor { 46 | ParsedFactor::Literal(value) => Ok(AnalyzedFactor::Literal(*value)), 47 | ParsedFactor::Identifier(name) => { 48 | Ok(AnalyzedFactor::Identifier(variables.find_symbol(name)?)) 49 | } 50 | ParsedFactor::SubExpression(expr) => Ok(AnalyzedFactor::SubExpression( 51 | Box::::new(analyze_expr(variables, expr)?), 52 | )), 53 | } 54 | } 55 | 56 | fn analyze_term( 57 | variables: &mut SymbolTable, 58 | parsed_term: &ParsedTerm, 59 | ) -> Result { 60 | let first_factor = analyze_factor(variables, &parsed_term.0)?; 61 | let mut other_factors = Vec::<(TermOperator, AnalyzedFactor)>::new(); 62 | for factor in &parsed_term.1 { 63 | other_factors.push((factor.0, analyze_factor(variables, &factor.1)?)); 64 | } 65 | Ok((first_factor, other_factors)) 66 | } 67 | 68 | fn analyze_expr( 69 | variables: &mut SymbolTable, 70 | parsed_expr: &ParsedExpr, 71 | ) -> Result { 72 | let first_term = analyze_term(variables, &parsed_expr.0)?; 73 | let mut other_terms = Vec::<(ExprOperator, AnalyzedTerm)>::new(); 74 | for term in &parsed_expr.1 { 75 | other_terms.push((term.0, analyze_term(variables, &term.1)?)); 76 | } 77 | Ok((first_term, other_terms)) 78 | } 79 | 80 | fn analyze_statement( 81 | variables: &mut SymbolTable, 82 | parsed_statement: &ParsedStatement, 83 | ) -> Result { 84 | match parsed_statement { 85 | ParsedStatement::Assignment(identifier, expr) => { 86 | let handle = variables.find_symbol(identifier)?; 87 | let analyzed_expr = analyze_expr(variables, expr)?; 88 | Ok(AnalyzedStatement::Assignment(handle, analyzed_expr)) 89 | } 90 | ParsedStatement::Declaration(identifier) => { 91 | let handle = variables.insert_symbol(identifier)?; 92 | Ok(AnalyzedStatement::Declaration(handle)) 93 | } 94 | ParsedStatement::InputOperation(identifier) => { 95 | let handle = variables.find_symbol(identifier)?; 96 | Ok(AnalyzedStatement::InputOperation(handle)) 97 | } 98 | ParsedStatement::OutputOperation(expr) => { 99 | let analyzed_expr = analyze_expr(variables, expr)?; 100 | Ok(AnalyzedStatement::OutputOperation(analyzed_expr)) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Chapter08/calc_compiler/src/compiler.rs: -------------------------------------------------------------------------------- 1 | use crate::analyzer::{ 2 | AnalyzedExpr, AnalyzedFactor, AnalyzedProgram, AnalyzedStatement, AnalyzedTerm, 3 | }; 4 | use crate::parser::{ExprOperator, TermOperator}; 5 | use crate::symbol_table::SymbolTable; 6 | 7 | fn translate_to_rust_factor(variables: &SymbolTable, analyzed_factor: &AnalyzedFactor) -> String { 8 | match analyzed_factor { 9 | AnalyzedFactor::Literal(value) => value.to_string() + "f64", 10 | AnalyzedFactor::Identifier(handle) => "_".to_string() + &variables.get_name(*handle), 11 | AnalyzedFactor::SubExpression(expr) => { 12 | "(".to_string() + &translate_to_rust_expr(variables, expr) + ")" 13 | } 14 | } 15 | } 16 | 17 | fn translate_to_rust_term(variables: &SymbolTable, analyzed_term: &AnalyzedTerm) -> String { 18 | let mut result = translate_to_rust_factor(variables, &analyzed_term.0); 19 | for factor in &analyzed_term.1 { 20 | match factor.0 { 21 | TermOperator::Multiply => { 22 | result += " * "; 23 | result += &translate_to_rust_factor(variables, &factor.1); 24 | } 25 | TermOperator::Divide => { 26 | result += " / "; 27 | result += &translate_to_rust_factor(variables, &factor.1); 28 | } 29 | } 30 | } 31 | result 32 | } 33 | 34 | fn translate_to_rust_expr(variables: &SymbolTable, analyzed_expr: &AnalyzedExpr) -> String { 35 | let mut result = translate_to_rust_term(variables, &analyzed_expr.0); 36 | for term in &analyzed_expr.1 { 37 | match term.0 { 38 | ExprOperator::Add => { 39 | result += " + "; 40 | result += &translate_to_rust_term(variables, &term.1); 41 | } 42 | ExprOperator::Subtract => { 43 | result += " - "; 44 | result += &translate_to_rust_term(variables, &term.1); 45 | } 46 | } 47 | } 48 | result 49 | } 50 | 51 | fn translate_to_rust_statement( 52 | variables: &SymbolTable, 53 | analyzed_statement: &AnalyzedStatement, 54 | ) -> String { 55 | match analyzed_statement { 56 | AnalyzedStatement::Assignment(handle, expr) => format!( 57 | "_{} = {}", 58 | variables.get_name(*handle), 59 | translate_to_rust_expr(&variables, expr) 60 | ), 61 | AnalyzedStatement::Declaration(handle) => { 62 | format!("let mut _{} = 0.0", variables.get_name(*handle)) 63 | } 64 | AnalyzedStatement::InputOperation(handle) => { 65 | format!("_{} = input()", variables.get_name(*handle)) 66 | } 67 | AnalyzedStatement::OutputOperation(expr) => format!( 68 | "println!(\"{}\", {})", 69 | "{}", 70 | translate_to_rust_expr(&variables, expr) 71 | ), 72 | } 73 | } 74 | 75 | pub fn translate_to_rust_program( 76 | variables: &SymbolTable, 77 | analyzed_program: &AnalyzedProgram, 78 | ) -> String { 79 | let mut rust_program = String::new(); 80 | rust_program += "use std::io::Write;\n"; 81 | rust_program += "\n"; 82 | rust_program += "#[allow(dead_code)]\n"; 83 | rust_program += "fn input() -> f64 {\n"; 84 | rust_program += " let mut text = String::new();\n"; 85 | rust_program += " eprint!(\"? \");\n"; 86 | rust_program += " std::io::stderr().flush().unwrap();\n"; 87 | rust_program += " std::io::stdin()\n"; 88 | rust_program += " .read_line(&mut text)\n"; 89 | rust_program += " .expect(\"Cannot read line.\");\n"; 90 | rust_program += " text.trim().parse::().unwrap_or(0.)\n"; 91 | rust_program += "}\n"; 92 | rust_program += "\n"; 93 | rust_program += "fn main() {\n"; 94 | for statement in analyzed_program { 95 | rust_program += " "; 96 | rust_program += &translate_to_rust_statement(&variables, statement); 97 | rust_program += ";\n"; 98 | } 99 | rust_program += "}\n"; 100 | rust_program 101 | } 102 | -------------------------------------------------------------------------------- /Chapter08/calc_compiler/src/executor.rs: -------------------------------------------------------------------------------- 1 | use crate::analyzer::{ 2 | AnalyzedExpr, AnalyzedFactor, AnalyzedProgram, AnalyzedStatement, AnalyzedTerm, 3 | }; 4 | use crate::parser::{ExprOperator, TermOperator}; 5 | use crate::symbol_table::SymbolTable; 6 | 7 | fn evaluate_factor(variables: &SymbolTable, factor: &AnalyzedFactor) -> f64 { 8 | match factor { 9 | AnalyzedFactor::Literal(value) => *value, 10 | AnalyzedFactor::Identifier(handle) => variables.get_value(*handle), 11 | AnalyzedFactor::SubExpression(expr) => evaluate_expr(variables, expr), 12 | } 13 | } 14 | 15 | fn evaluate_term(variables: &SymbolTable, term: &AnalyzedTerm) -> f64 { 16 | let mut result = evaluate_factor(variables, &term.0); 17 | for factor in &term.1 { 18 | match factor.0 { 19 | TermOperator::Multiply => result *= evaluate_factor(variables, &factor.1), 20 | TermOperator::Divide => result /= evaluate_factor(variables, &factor.1), 21 | } 22 | } 23 | result 24 | } 25 | 26 | fn evaluate_expr(variables: &SymbolTable, expr: &AnalyzedExpr) -> f64 { 27 | let mut result = evaluate_term(variables, &expr.0); 28 | for term in &expr.1 { 29 | match term.0 { 30 | ExprOperator::Add => result += evaluate_term(variables, &term.1), 31 | ExprOperator::Subtract => result -= evaluate_term(variables, &term.1), 32 | } 33 | } 34 | result 35 | } 36 | 37 | fn execute_statement(variables: &mut SymbolTable, statement: &AnalyzedStatement) { 38 | match statement { 39 | AnalyzedStatement::Assignment(handle, expr) => { 40 | variables.set_value(*handle, evaluate_expr(variables, expr)); 41 | } 42 | AnalyzedStatement::Declaration(_) => {} 43 | AnalyzedStatement::InputOperation(handle) => { 44 | let mut text = String::new(); 45 | eprint!("? "); 46 | std::io::stdin() 47 | .read_line(&mut text) 48 | .expect("Cannot read line."); 49 | let value = text.trim().parse::().unwrap_or(0.); 50 | variables.set_value(*handle, value); 51 | } 52 | AnalyzedStatement::OutputOperation(expr) => { 53 | println!("{}", evaluate_expr(variables, expr)); 54 | } 55 | } 56 | } 57 | 58 | pub fn execute_program(variables: &mut SymbolTable, program: &AnalyzedProgram) { 59 | for statement in program { 60 | execute_statement(variables, statement); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Chapter08/calc_compiler/src/main.rs: -------------------------------------------------------------------------------- 1 | mod analyzer; 2 | mod compiler; 3 | mod executor; 4 | mod parser; 5 | mod symbol_table; 6 | 7 | fn main() { 8 | let mut args = std::env::args(); 9 | let current_program_path = args.next().unwrap(); 10 | let source_path = args.next(); 11 | if source_path.is_none() { 12 | run_interpreter(); 13 | } else { 14 | process_file(¤t_program_path, &source_path.unwrap()); 15 | } 16 | } 17 | 18 | fn process_file(current_program_path: &str, source_path: &str) { 19 | const CALC_SUFFIX: &str = ".calc"; 20 | if !source_path.ends_with(CALC_SUFFIX) { 21 | eprintln!( 22 | "{}: Invalid argument '{}': It must end with {}", 23 | current_program_path, source_path, CALC_SUFFIX 24 | ); 25 | return; 26 | } 27 | let target_path = source_path[0..source_path.len() - CALC_SUFFIX.len()].to_string() + ".rs"; 28 | let source_code = std::fs::read_to_string(&source_path); 29 | if source_code.is_err() { 30 | eprintln!( 31 | "Failed to read from file {}: ({})", 32 | source_path, 33 | source_code.unwrap_err() 34 | ); 35 | return; 36 | } 37 | let source_code = source_code.unwrap(); 38 | 39 | let parsed_program; 40 | match parser::parse_program(&source_code) { 41 | Ok((rest, syntax_tree)) => { 42 | let trimmed_rest = rest.trim(); 43 | if trimmed_rest.len() > 0 { 44 | eprintln!( 45 | "Invalid remaining code in '{}': {}", 46 | source_path, trimmed_rest 47 | ); 48 | return; 49 | } 50 | parsed_program = syntax_tree; 51 | } 52 | Err(err) => { 53 | eprintln!("Invalid code in '{}': {:?}", source_path, err); 54 | return; 55 | } 56 | } 57 | 58 | let analyzed_program; 59 | let mut variables = symbol_table::SymbolTable::new(); 60 | match analyzer::analyze_program(&mut variables, &parsed_program) { 61 | Ok(analyzed_tree) => { 62 | analyzed_program = analyzed_tree; 63 | } 64 | Err(err) => { 65 | eprintln!("Invalid code in '{}': {}", source_path, err); 66 | return; 67 | } 68 | } 69 | 70 | match std::fs::write( 71 | &target_path, 72 | compiler::translate_to_rust_program(&variables, &analyzed_program), 73 | ) { 74 | Ok(_) => eprintln!("Compiled {} to {}.", source_path, target_path), 75 | Err(err) => eprintln!("Failed to write to file {}: ({})", target_path, err), 76 | } 77 | } 78 | 79 | fn run_interpreter() { 80 | eprintln!("* Calc interactive interpreter *"); 81 | let mut variables = symbol_table::SymbolTable::new(); 82 | loop { 83 | let command = input_command(); 84 | if command.len() == 0 { 85 | break; 86 | } 87 | match command.trim() { 88 | "q" => break, 89 | "c" => { 90 | variables = symbol_table::SymbolTable::new(); 91 | eprintln!("Cleared variables."); 92 | } 93 | "v" => { 94 | eprintln!("Variables:"); 95 | for v in variables.iter() { 96 | eprintln!(" {}: {}", v.0, v.1); 97 | } 98 | } 99 | trimmed_command => match parser::parse_program(&trimmed_command) { 100 | Ok((rest, parsed_program)) => { 101 | if rest.len() > 0 { 102 | eprintln!("Unparsed input: `{}`.", rest) 103 | } else { 104 | match analyzer::analyze_program(&mut variables, &parsed_program) { 105 | Ok(analyzed_program) => { 106 | executor::execute_program(&mut variables, &analyzed_program) 107 | } 108 | Err(err) => eprintln!("Error: {}", err), 109 | } 110 | } 111 | } 112 | Err(err) => eprintln!("Error: {:?}", err), 113 | }, 114 | } 115 | } 116 | } 117 | 118 | fn input_command() -> String { 119 | let mut text = String::new(); 120 | eprint!("> "); 121 | std::io::stdin() 122 | .read_line(&mut text) 123 | .expect("Cannot read line."); 124 | text 125 | } 126 | -------------------------------------------------------------------------------- /Chapter08/calc_compiler/src/parser.rs: -------------------------------------------------------------------------------- 1 | extern crate nom; 2 | use nom::{ 3 | branch::alt, 4 | bytes::complete::tag, 5 | bytes::complete::take_while, 6 | character::complete::{alpha1, char}, 7 | combinator::map, 8 | multi::many0, 9 | number::complete::double, 10 | sequence::{delimited, preceded, tuple}, 11 | IResult, 12 | }; 13 | 14 | #[derive(Debug, PartialEq)] 15 | pub enum ParsedFactor<'a> { 16 | Literal(f64), 17 | Identifier(&'a str), 18 | SubExpression(Box>), 19 | } 20 | 21 | #[derive(Debug, PartialEq, Clone, Copy)] 22 | pub enum TermOperator { 23 | Multiply, 24 | Divide, 25 | } 26 | 27 | #[derive(Debug, PartialEq, Clone, Copy)] 28 | pub enum ExprOperator { 29 | Add, 30 | Subtract, 31 | } 32 | 33 | pub type ParsedTerm<'a> = (ParsedFactor<'a>, Vec<(TermOperator, ParsedFactor<'a>)>); 34 | 35 | pub type ParsedExpr<'a> = (ParsedTerm<'a>, Vec<(ExprOperator, ParsedTerm<'a>)>); 36 | 37 | #[derive(Debug)] 38 | pub enum ParsedStatement<'a> { 39 | Declaration(&'a str), 40 | InputOperation(&'a str), 41 | OutputOperation(ParsedExpr<'a>), 42 | Assignment(&'a str, ParsedExpr<'a>), 43 | } 44 | 45 | pub type ParsedProgram<'a> = Vec>; 46 | 47 | pub fn parse_program(input: &str) -> IResult<&str, ParsedProgram> { 48 | many0(preceded( 49 | skip_spaces, 50 | alt(( 51 | parse_declaration, 52 | parse_input_statement, 53 | parse_output_statement, 54 | parse_assignment, 55 | )), 56 | ))(input) 57 | } 58 | 59 | fn parse_declaration(input: &str) -> IResult<&str, ParsedStatement> { 60 | tuple((char('@'), skip_spaces, parse_identifier))(input) 61 | .map(|(input, output)| (input, ParsedStatement::Declaration(output.2))) 62 | } 63 | 64 | fn parse_input_statement(input: &str) -> IResult<&str, ParsedStatement> { 65 | tuple((char('>'), skip_spaces, parse_identifier))(input) 66 | .map(|(input, output)| (input, ParsedStatement::InputOperation(output.2))) 67 | } 68 | 69 | fn parse_output_statement(input: &str) -> IResult<&str, ParsedStatement> { 70 | tuple((char('<'), skip_spaces, parse_expr))(input) 71 | .map(|(input, output)| (input, ParsedStatement::OutputOperation(output.2))) 72 | } 73 | 74 | fn parse_assignment(input: &str) -> IResult<&str, ParsedStatement> { 75 | tuple(( 76 | parse_identifier, 77 | skip_spaces, 78 | tag(":="), 79 | skip_spaces, 80 | parse_expr, 81 | ))(input) 82 | .map(|(input, output)| (input, ParsedStatement::Assignment(output.0, output.4))) 83 | } 84 | 85 | fn parse_identifier(input: &str) -> IResult<&str, &str> { 86 | alpha1(input) 87 | } 88 | 89 | fn parse_subexpr(input: &str) -> IResult<&str, ParsedExpr> { 90 | delimited( 91 | preceded(skip_spaces, char('(')), 92 | parse_expr, 93 | preceded(skip_spaces, char(')')), 94 | )(input) 95 | } 96 | 97 | fn parse_factor(input: &str) -> IResult<&str, ParsedFactor> { 98 | preceded( 99 | skip_spaces, 100 | alt(( 101 | map(parse_identifier, ParsedFactor::Identifier), 102 | map(double, ParsedFactor::Literal), 103 | map(parse_subexpr, |expr| { 104 | ParsedFactor::SubExpression(Box::new(expr)) 105 | }), 106 | )), 107 | )(input) 108 | } 109 | 110 | fn parse_term(input: &str) -> IResult<&str, ParsedTerm> { 111 | tuple(( 112 | parse_factor, 113 | many0(tuple(( 114 | preceded( 115 | skip_spaces, 116 | alt(( 117 | map(char('*'), |_| TermOperator::Multiply), 118 | map(char('/'), |_| TermOperator::Divide), 119 | )), 120 | ), 121 | parse_factor, 122 | ))), 123 | ))(input) 124 | } 125 | 126 | fn parse_expr(input: &str) -> IResult<&str, ParsedExpr> { 127 | tuple(( 128 | parse_term, 129 | many0(tuple(( 130 | preceded( 131 | skip_spaces, 132 | alt(( 133 | map(char('+'), |_| ExprOperator::Add), 134 | map(char('-'), |_| ExprOperator::Subtract), 135 | )), 136 | ), 137 | parse_term, 138 | ))), 139 | ))(input) 140 | } 141 | 142 | fn skip_spaces(input: &str) -> IResult<&str, &str> { 143 | let chars = " \t\r\n"; 144 | take_while(move |ch| chars.contains(ch))(input) 145 | } 146 | -------------------------------------------------------------------------------- /Chapter08/calc_compiler/src/symbol_table.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct SymbolTable { 3 | entries: Vec<(String, f64)>, 4 | } 5 | 6 | impl SymbolTable { 7 | pub fn new() -> SymbolTable { 8 | SymbolTable { 9 | entries: Vec::<(String, f64)>::new(), 10 | } 11 | } 12 | pub fn insert_symbol(&mut self, identifier: &str) -> Result { 13 | if self 14 | .entries 15 | .iter() 16 | .find(|item| item.0 == identifier) 17 | .is_some() 18 | { 19 | Err(format!( 20 | "Error: Identifier '{}' declared several times.", 21 | identifier 22 | )) 23 | } else { 24 | self.entries.push((identifier.to_string(), 0.)); 25 | Ok(self.entries.len() - 1) 26 | } 27 | } 28 | pub fn find_symbol(&self, identifier: &str) -> Result { 29 | if let Some(pos) = self.entries.iter().position(|item| item.0 == identifier) { 30 | Ok(pos) 31 | } else { 32 | Err(format!( 33 | "Error: Identifier '{}' used before having been declared.", 34 | identifier 35 | )) 36 | } 37 | } 38 | pub fn get_value(&self, handle: usize) -> f64 { 39 | self.entries[handle].1 40 | } 41 | pub fn set_value(&mut self, handle: usize, value: f64) { 42 | self.entries[handle].1 = value; 43 | } 44 | pub fn get_name(&self, handle: usize) -> String { 45 | self.entries[handle].0.clone() 46 | } 47 | pub fn iter(&self) -> std::slice::Iter<(String, f64)> { 48 | self.entries.iter() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Chapter08/calc_interpreter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calc_interpreter" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | nom = "5" 9 | -------------------------------------------------------------------------------- /Chapter08/calc_interpreter/src/analyzer.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::{ 2 | ExprOperator, ParsedExpr, ParsedFactor, ParsedProgram, ParsedStatement, ParsedTerm, 3 | TermOperator, 4 | }; 5 | use crate::symbol_table::SymbolTable; 6 | 7 | extern crate nom; 8 | 9 | #[derive(Debug, PartialEq)] 10 | pub enum AnalyzedFactor { 11 | Literal(f64), 12 | Identifier(usize), 13 | SubExpression(Box), 14 | } 15 | 16 | pub type AnalyzedTerm = (AnalyzedFactor, Vec<(TermOperator, AnalyzedFactor)>); 17 | 18 | pub type AnalyzedExpr = (AnalyzedTerm, Vec<(ExprOperator, AnalyzedTerm)>); 19 | 20 | #[derive(Debug)] 21 | pub enum AnalyzedStatement { 22 | Declaration(usize), 23 | InputOperation(usize), 24 | OutputOperation(AnalyzedExpr), 25 | Assignment(usize, AnalyzedExpr), 26 | } 27 | 28 | pub type AnalyzedProgram = Vec; 29 | 30 | pub fn analyze_program( 31 | variables: &mut SymbolTable, 32 | parsed_program: &ParsedProgram, 33 | ) -> Result { 34 | let mut analyzed_program = AnalyzedProgram::new(); 35 | for statement in parsed_program { 36 | analyzed_program.push(analyze_statement(variables, statement)?); 37 | } 38 | Ok(analyzed_program) 39 | } 40 | 41 | fn analyze_factor( 42 | variables: &mut SymbolTable, 43 | parsed_factor: &ParsedFactor, 44 | ) -> Result { 45 | match parsed_factor { 46 | ParsedFactor::Literal(value) => Ok(AnalyzedFactor::Literal(*value)), 47 | ParsedFactor::Identifier(name) => { 48 | Ok(AnalyzedFactor::Identifier(variables.find_symbol(name)?)) 49 | } 50 | ParsedFactor::SubExpression(expr) => Ok(AnalyzedFactor::SubExpression( 51 | Box::::new(analyze_expr(variables, expr)?), 52 | )), 53 | } 54 | } 55 | 56 | fn analyze_term( 57 | variables: &mut SymbolTable, 58 | parsed_term: &ParsedTerm, 59 | ) -> Result { 60 | let first_factor = analyze_factor(variables, &parsed_term.0)?; 61 | let mut other_factors = Vec::<(TermOperator, AnalyzedFactor)>::new(); 62 | for factor in &parsed_term.1 { 63 | other_factors.push((factor.0, analyze_factor(variables, &factor.1)?)); 64 | } 65 | Ok((first_factor, other_factors)) 66 | } 67 | 68 | fn analyze_expr( 69 | variables: &mut SymbolTable, 70 | parsed_expr: &ParsedExpr, 71 | ) -> Result { 72 | let first_term = analyze_term(variables, &parsed_expr.0)?; 73 | let mut other_terms = Vec::<(ExprOperator, AnalyzedTerm)>::new(); 74 | for term in &parsed_expr.1 { 75 | other_terms.push((term.0, analyze_term(variables, &term.1)?)); 76 | } 77 | Ok((first_term, other_terms)) 78 | } 79 | 80 | fn analyze_statement( 81 | variables: &mut SymbolTable, 82 | parsed_statement: &ParsedStatement, 83 | ) -> Result { 84 | match parsed_statement { 85 | ParsedStatement::Assignment(identifier, expr) => { 86 | let handle = variables.find_symbol(identifier)?; 87 | let analyzed_expr = analyze_expr(variables, expr)?; 88 | Ok(AnalyzedStatement::Assignment(handle, analyzed_expr)) 89 | } 90 | ParsedStatement::Declaration(identifier) => { 91 | let handle = variables.insert_symbol(identifier)?; 92 | Ok(AnalyzedStatement::Declaration(handle)) 93 | } 94 | ParsedStatement::InputOperation(identifier) => { 95 | let handle = variables.find_symbol(identifier)?; 96 | Ok(AnalyzedStatement::InputOperation(handle)) 97 | } 98 | ParsedStatement::OutputOperation(expr) => { 99 | let analyzed_expr = analyze_expr(variables, expr)?; 100 | Ok(AnalyzedStatement::OutputOperation(analyzed_expr)) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Chapter08/calc_interpreter/src/executor.rs: -------------------------------------------------------------------------------- 1 | use crate::analyzer::{ 2 | AnalyzedExpr, AnalyzedFactor, AnalyzedProgram, AnalyzedStatement, AnalyzedTerm, 3 | }; 4 | use crate::parser::{ExprOperator, TermOperator}; 5 | use crate::symbol_table::SymbolTable; 6 | 7 | fn evaluate_factor(variables: &SymbolTable, factor: &AnalyzedFactor) -> f64 { 8 | match factor { 9 | AnalyzedFactor::Literal(value) => *value, 10 | AnalyzedFactor::Identifier(handle) => variables.get_value(*handle), 11 | AnalyzedFactor::SubExpression(expr) => evaluate_expr(variables, expr), 12 | } 13 | } 14 | 15 | fn evaluate_term(variables: &SymbolTable, term: &AnalyzedTerm) -> f64 { 16 | let mut result = evaluate_factor(variables, &term.0); 17 | for factor in &term.1 { 18 | match factor.0 { 19 | TermOperator::Multiply => result *= evaluate_factor(variables, &factor.1), 20 | TermOperator::Divide => result /= evaluate_factor(variables, &factor.1), 21 | } 22 | } 23 | result 24 | } 25 | 26 | fn evaluate_expr(variables: &SymbolTable, expr: &AnalyzedExpr) -> f64 { 27 | let mut result = evaluate_term(variables, &expr.0); 28 | for term in &expr.1 { 29 | match term.0 { 30 | ExprOperator::Add => result += evaluate_term(variables, &term.1), 31 | ExprOperator::Subtract => result -= evaluate_term(variables, &term.1), 32 | } 33 | } 34 | result 35 | } 36 | 37 | fn execute_statement(variables: &mut SymbolTable, statement: &AnalyzedStatement) { 38 | match statement { 39 | AnalyzedStatement::Assignment(handle, expr) => { 40 | variables.set_value(*handle, evaluate_expr(variables, expr)); 41 | } 42 | AnalyzedStatement::Declaration(_) => {} 43 | AnalyzedStatement::InputOperation(handle) => { 44 | let mut text = String::new(); 45 | eprint!("? "); 46 | std::io::stdin() 47 | .read_line(&mut text) 48 | .expect("Cannot read line."); 49 | let value = text.trim().parse::().unwrap_or(0.); 50 | variables.set_value(*handle, value); 51 | } 52 | AnalyzedStatement::OutputOperation(expr) => { 53 | println!("{}", evaluate_expr(variables, expr)); 54 | } 55 | } 56 | } 57 | 58 | pub fn execute_program(variables: &mut SymbolTable, program: &AnalyzedProgram) { 59 | for statement in program { 60 | execute_statement(variables, statement); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Chapter08/calc_interpreter/src/main.rs: -------------------------------------------------------------------------------- 1 | mod analyzer; 2 | mod executor; 3 | mod parser; 4 | mod symbol_table; 5 | 6 | fn main() { 7 | run_interpreter(); 8 | } 9 | 10 | fn run_interpreter() { 11 | eprintln!("* Calc interactive interpreter *"); 12 | let mut variables = symbol_table::SymbolTable::new(); 13 | loop { 14 | let command = input_command(); 15 | if command.len() == 0 { 16 | break; 17 | } 18 | match command.trim() { 19 | "q" => break, 20 | "c" => { 21 | variables = symbol_table::SymbolTable::new(); 22 | eprintln!("Cleared variables."); 23 | } 24 | "v" => { 25 | eprintln!("Variables:"); 26 | for v in variables.iter() { 27 | eprintln!(" {}: {}", v.0, v.1); 28 | } 29 | } 30 | trimmed_command => match parser::parse_program(&trimmed_command) { 31 | Ok((rest, parsed_program)) => { 32 | if rest.len() > 0 { 33 | eprintln!("Unparsed input: `{}`.", rest) 34 | } else { 35 | match analyzer::analyze_program(&mut variables, &parsed_program) { 36 | Ok(analyzed_program) => { 37 | executor::execute_program(&mut variables, &analyzed_program) 38 | } 39 | Err(err) => eprintln!("Error: {}", err), 40 | } 41 | } 42 | } 43 | Err(err) => eprintln!("Error: {:?}", err), 44 | }, 45 | } 46 | } 47 | } 48 | 49 | fn input_command() -> String { 50 | let mut text = String::new(); 51 | eprint!("> "); 52 | std::io::stdin() 53 | .read_line(&mut text) 54 | .expect("Cannot read line."); 55 | text 56 | } 57 | -------------------------------------------------------------------------------- /Chapter08/calc_interpreter/src/parser.rs: -------------------------------------------------------------------------------- 1 | extern crate nom; 2 | use nom::{ 3 | branch::alt, 4 | bytes::complete::tag, 5 | bytes::complete::take_while, 6 | character::complete::{alpha1, char}, 7 | combinator::map, 8 | multi::many0, 9 | number::complete::double, 10 | sequence::{delimited, preceded, tuple}, 11 | IResult, 12 | }; 13 | 14 | #[derive(Debug, PartialEq)] 15 | pub enum ParsedFactor<'a> { 16 | Literal(f64), 17 | Identifier(&'a str), 18 | SubExpression(Box>), 19 | } 20 | 21 | #[derive(Debug, PartialEq, Clone, Copy)] 22 | pub enum TermOperator { 23 | Multiply, 24 | Divide, 25 | } 26 | 27 | #[derive(Debug, PartialEq, Clone, Copy)] 28 | pub enum ExprOperator { 29 | Add, 30 | Subtract, 31 | } 32 | 33 | pub type ParsedTerm<'a> = (ParsedFactor<'a>, Vec<(TermOperator, ParsedFactor<'a>)>); 34 | 35 | pub type ParsedExpr<'a> = (ParsedTerm<'a>, Vec<(ExprOperator, ParsedTerm<'a>)>); 36 | 37 | #[derive(Debug)] 38 | pub enum ParsedStatement<'a> { 39 | Declaration(&'a str), 40 | InputOperation(&'a str), 41 | OutputOperation(ParsedExpr<'a>), 42 | Assignment(&'a str, ParsedExpr<'a>), 43 | } 44 | 45 | pub type ParsedProgram<'a> = Vec>; 46 | 47 | pub fn parse_program(input: &str) -> IResult<&str, ParsedProgram> { 48 | many0(preceded( 49 | skip_spaces, 50 | alt(( 51 | parse_declaration, 52 | parse_input_statement, 53 | parse_output_statement, 54 | parse_assignment, 55 | )), 56 | ))(input) 57 | } 58 | 59 | fn parse_declaration(input: &str) -> IResult<&str, ParsedStatement> { 60 | tuple((char('@'), skip_spaces, parse_identifier))(input) 61 | .map(|(input, output)| (input, ParsedStatement::Declaration(output.2))) 62 | } 63 | 64 | fn parse_input_statement(input: &str) -> IResult<&str, ParsedStatement> { 65 | tuple((char('>'), skip_spaces, parse_identifier))(input) 66 | .map(|(input, output)| (input, ParsedStatement::InputOperation(output.2))) 67 | } 68 | 69 | fn parse_output_statement(input: &str) -> IResult<&str, ParsedStatement> { 70 | tuple((char('<'), skip_spaces, parse_expr))(input) 71 | .map(|(input, output)| (input, ParsedStatement::OutputOperation(output.2))) 72 | } 73 | 74 | fn parse_assignment(input: &str) -> IResult<&str, ParsedStatement> { 75 | tuple(( 76 | parse_identifier, 77 | skip_spaces, 78 | tag(":="), 79 | skip_spaces, 80 | parse_expr, 81 | ))(input) 82 | .map(|(input, output)| (input, ParsedStatement::Assignment(output.0, output.4))) 83 | } 84 | 85 | fn parse_identifier(input: &str) -> IResult<&str, &str> { 86 | alpha1(input) 87 | } 88 | 89 | fn parse_subexpr(input: &str) -> IResult<&str, ParsedExpr> { 90 | delimited( 91 | preceded(skip_spaces, char('(')), 92 | parse_expr, 93 | preceded(skip_spaces, char(')')), 94 | )(input) 95 | } 96 | 97 | fn parse_factor(input: &str) -> IResult<&str, ParsedFactor> { 98 | preceded( 99 | skip_spaces, 100 | alt(( 101 | map(parse_identifier, ParsedFactor::Identifier), 102 | map(double, ParsedFactor::Literal), 103 | map(parse_subexpr, |expr| { 104 | ParsedFactor::SubExpression(Box::new(expr)) 105 | }), 106 | )), 107 | )(input) 108 | } 109 | 110 | fn parse_term(input: &str) -> IResult<&str, ParsedTerm> { 111 | tuple(( 112 | parse_factor, 113 | many0(tuple(( 114 | preceded( 115 | skip_spaces, 116 | alt(( 117 | map(char('*'), |_| TermOperator::Multiply), 118 | map(char('/'), |_| TermOperator::Divide), 119 | )), 120 | ), 121 | parse_factor, 122 | ))), 123 | ))(input) 124 | } 125 | 126 | fn parse_expr(input: &str) -> IResult<&str, ParsedExpr> { 127 | tuple(( 128 | parse_term, 129 | many0(tuple(( 130 | preceded( 131 | skip_spaces, 132 | alt(( 133 | map(char('+'), |_| ExprOperator::Add), 134 | map(char('-'), |_| ExprOperator::Subtract), 135 | )), 136 | ), 137 | parse_term, 138 | ))), 139 | ))(input) 140 | } 141 | 142 | fn skip_spaces(input: &str) -> IResult<&str, &str> { 143 | let chars = " \t\r\n"; 144 | take_while(move |ch| chars.contains(ch))(input) 145 | } 146 | -------------------------------------------------------------------------------- /Chapter08/calc_interpreter/src/symbol_table.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct SymbolTable { 3 | entries: Vec<(String, f64)>, 4 | } 5 | 6 | impl SymbolTable { 7 | pub fn new() -> SymbolTable { 8 | SymbolTable { 9 | entries: Vec::<(String, f64)>::new(), 10 | } 11 | } 12 | pub fn insert_symbol(&mut self, identifier: &str) -> Result { 13 | if self 14 | .entries 15 | .iter() 16 | .find(|item| item.0 == identifier) 17 | .is_some() 18 | { 19 | Err(format!( 20 | "Error: Identifier '{}' declared several times.", 21 | identifier 22 | )) 23 | } else { 24 | self.entries.push((identifier.to_string(), 0.)); 25 | Ok(self.entries.len() - 1) 26 | } 27 | } 28 | pub fn find_symbol(&self, identifier: &str) -> Result { 29 | if let Some(pos) = self.entries.iter().position(|item| item.0 == identifier) { 30 | Ok(pos) 31 | } else { 32 | Err(format!( 33 | "Error: Identifier '{}' used before having been declared.", 34 | identifier 35 | )) 36 | } 37 | } 38 | pub fn get_value(&self, handle: usize) -> f64 { 39 | self.entries[handle].1 40 | } 41 | pub fn set_value(&mut self, handle: usize, value: f64) { 42 | self.entries[handle].1 = value; 43 | } 44 | pub fn iter(&self) -> std::slice::Iter<(String, f64)> { 45 | self.entries.iter() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Chapter08/calc_parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calc_parser" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | nom = "5" 9 | -------------------------------------------------------------------------------- /Chapter08/calc_parser/data/bad_sum.calc: -------------------------------------------------------------------------------- 1 | @a 2 | @d 3 | >a 4 | >b 5 | a 4 | >b 5 | .calc", current_program_path); 9 | } else { 10 | process_file(¤t_program_path, &source_path.unwrap()); 11 | } 12 | } 13 | 14 | fn process_file(current_program_path: &str, source_path: &str) { 15 | const CALC_SUFFIX: &str = ".calc"; 16 | if !source_path.ends_with(CALC_SUFFIX) { 17 | eprintln!( 18 | "{}: Invalid argument '{}': It must end with {}", 19 | current_program_path, source_path, CALC_SUFFIX 20 | ); 21 | return; 22 | } 23 | let source_code = std::fs::read_to_string(&source_path); 24 | if source_code.is_err() { 25 | eprintln!( 26 | "Failed to read from file {}: ({})", 27 | source_path, 28 | source_code.unwrap_err() 29 | ); 30 | return; 31 | } 32 | let source_code = source_code.unwrap(); 33 | 34 | let parsed_program; 35 | match parser::parse_program(&source_code) { 36 | Ok((rest, syntax_tree)) => { 37 | let trimmed_rest = rest.trim(); 38 | if trimmed_rest.len() > 0 { 39 | eprintln!( 40 | "Invalid remaining code in '{}': {}", 41 | source_path, trimmed_rest 42 | ); 43 | return; 44 | } 45 | parsed_program = syntax_tree; 46 | } 47 | Err(err) => { 48 | eprintln!("Invalid code in '{}': {:?}", source_path, err); 49 | return; 50 | } 51 | } 52 | 53 | println!("Parsed program: {:#?}", parsed_program); 54 | } 55 | -------------------------------------------------------------------------------- /Chapter08/calc_parser/src/parser.rs: -------------------------------------------------------------------------------- 1 | extern crate nom; 2 | use nom::{ 3 | branch::alt, 4 | bytes::complete::tag, 5 | bytes::complete::take_while, 6 | character::complete::{alpha1, char}, 7 | combinator::map, 8 | multi::many0, 9 | number::complete::double, 10 | sequence::{delimited, preceded, tuple}, 11 | IResult, 12 | }; 13 | 14 | #[derive(Debug, PartialEq)] 15 | pub enum ParsedFactor<'a> { 16 | Literal(f64), 17 | Identifier(&'a str), 18 | SubExpression(Box>), 19 | } 20 | 21 | #[derive(Debug, PartialEq, Clone, Copy)] 22 | pub enum TermOperator { 23 | Multiply, 24 | Divide, 25 | } 26 | 27 | #[derive(Debug, PartialEq, Clone, Copy)] 28 | pub enum ExprOperator { 29 | Add, 30 | Subtract, 31 | } 32 | 33 | pub type ParsedTerm<'a> = (ParsedFactor<'a>, Vec<(TermOperator, ParsedFactor<'a>)>); 34 | 35 | pub type ParsedExpr<'a> = (ParsedTerm<'a>, Vec<(ExprOperator, ParsedTerm<'a>)>); 36 | 37 | #[derive(Debug)] 38 | pub enum ParsedStatement<'a> { 39 | Declaration(&'a str), 40 | InputOperation(&'a str), 41 | OutputOperation(ParsedExpr<'a>), 42 | Assignment(&'a str, ParsedExpr<'a>), 43 | } 44 | 45 | pub type ParsedProgram<'a> = Vec>; 46 | 47 | pub fn parse_program(input: &str) -> IResult<&str, ParsedProgram> { 48 | many0(preceded( 49 | skip_spaces, 50 | alt(( 51 | parse_declaration, 52 | parse_input_statement, 53 | parse_output_statement, 54 | parse_assignment, 55 | )), 56 | ))(input) 57 | } 58 | 59 | fn parse_declaration(input: &str) -> IResult<&str, ParsedStatement> { 60 | tuple((char('@'), skip_spaces, parse_identifier))(input) 61 | .map(|(input, output)| (input, ParsedStatement::Declaration(output.2))) 62 | } 63 | 64 | fn parse_input_statement(input: &str) -> IResult<&str, ParsedStatement> { 65 | tuple((char('>'), skip_spaces, parse_identifier))(input) 66 | .map(|(input, output)| (input, ParsedStatement::InputOperation(output.2))) 67 | } 68 | 69 | fn parse_output_statement(input: &str) -> IResult<&str, ParsedStatement> { 70 | tuple((char('<'), skip_spaces, parse_expr))(input) 71 | .map(|(input, output)| (input, ParsedStatement::OutputOperation(output.2))) 72 | } 73 | 74 | fn parse_assignment(input: &str) -> IResult<&str, ParsedStatement> { 75 | tuple(( 76 | parse_identifier, 77 | skip_spaces, 78 | tag(":="), 79 | skip_spaces, 80 | parse_expr, 81 | ))(input) 82 | .map(|(input, output)| (input, ParsedStatement::Assignment(output.0, output.4))) 83 | } 84 | 85 | fn parse_identifier(input: &str) -> IResult<&str, &str> { 86 | alpha1(input) 87 | } 88 | 89 | fn parse_subexpr(input: &str) -> IResult<&str, ParsedExpr> { 90 | delimited( 91 | preceded(skip_spaces, char('(')), 92 | parse_expr, 93 | preceded(skip_spaces, char(')')), 94 | )(input) 95 | } 96 | 97 | fn parse_factor(input: &str) -> IResult<&str, ParsedFactor> { 98 | preceded( 99 | skip_spaces, 100 | alt(( 101 | map(parse_identifier, ParsedFactor::Identifier), 102 | map(double, ParsedFactor::Literal), 103 | map(parse_subexpr, |expr| { 104 | ParsedFactor::SubExpression(Box::new(expr)) 105 | }), 106 | )), 107 | )(input) 108 | } 109 | 110 | fn parse_term(input: &str) -> IResult<&str, ParsedTerm> { 111 | tuple(( 112 | parse_factor, 113 | many0(tuple(( 114 | preceded( 115 | skip_spaces, 116 | alt(( 117 | map(char('*'), |_| TermOperator::Multiply), 118 | map(char('/'), |_| TermOperator::Divide), 119 | )), 120 | ), 121 | parse_factor, 122 | ))), 123 | ))(input) 124 | } 125 | 126 | fn parse_expr(input: &str) -> IResult<&str, ParsedExpr> { 127 | tuple(( 128 | parse_term, 129 | many0(tuple(( 130 | preceded( 131 | skip_spaces, 132 | alt(( 133 | map(char('+'), |_| ExprOperator::Add), 134 | map(char('-'), |_| ExprOperator::Subtract), 135 | )), 136 | ), 137 | parse_term, 138 | ))), 139 | ))(input) 140 | } 141 | 142 | fn skip_spaces(input: &str) -> IResult<&str, &str> { 143 | let chars = " \t\r\n"; 144 | take_while(move |ch| chars.contains(ch))(input) 145 | } 146 | -------------------------------------------------------------------------------- /Chapter09/nom_byte_machine/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nom_byte_machine" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | nom = "5" 9 | -------------------------------------------------------------------------------- /Chapter09/nom_disassembler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nom_disassembler" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | nom = "5" 9 | -------------------------------------------------------------------------------- /Chapter09/word_machine_convert/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "word_machine_convert" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /Chapter09/word_machine_convert/src/main.rs: -------------------------------------------------------------------------------- 1 | fn input_line(buffer: &mut [u16]) { 2 | let mut text = String::new(); 3 | std::io::stdin() 4 | .read_line(&mut text) 5 | .expect("Cannot read line."); 6 | let text_size = text.len().min(buffer.len()); 7 | for (i, word) in buffer.iter_mut().enumerate().take(text_size) { 8 | *word = text.as_bytes()[i].into(); 9 | } 10 | for word in buffer.iter_mut().skip(text.len()) { 11 | *word = 0; 12 | } 13 | } 14 | 15 | fn execute(program: &[u16]) -> u16 { 16 | let mut acc: u16 = 0; 17 | let mut process = vec![0u16; program[0] as usize]; 18 | process[..program.len()].copy_from_slice(program); 19 | let mut ip = 1; 20 | loop { 21 | let opcode = process[ip]; 22 | let operand = process[ip + 1]; 23 | //println!("ip: {} opcode: {} operand: {} acc: {}", 24 | //ip, opcode, operand, acc); 25 | ip += 2; 26 | match opcode { 27 | 0 => 28 | // terminate 29 | { 30 | return operand 31 | } 32 | 1 => 33 | // set 34 | { 35 | acc = operand 36 | } 37 | 2 => 38 | // load 39 | { 40 | acc = process[operand as usize] 41 | } 42 | 3 => 43 | // store 44 | { 45 | process[operand as usize] = acc 46 | } 47 | 4 => { 48 | // indirect_load 49 | let address = process[operand as usize] as usize; 50 | acc = process[address]; 51 | } 52 | 5 => { 53 | // indirect_store 54 | let address = process[operand as usize] as usize; 55 | process[address] = acc; 56 | } 57 | 6 => { 58 | // input 59 | let address = acc as usize; 60 | input_line(&mut process[address..address + operand as usize]); 61 | } 62 | 7 => { 63 | // output 64 | let address = acc as usize; 65 | for &word in &process[address..address + operand as usize] { 66 | print!("{}", if word == 0 { ' ' } else { word as u8 as char }); 67 | } 68 | } 69 | 8 => 70 | // add 71 | { 72 | acc = acc.wrapping_add(process[operand as usize]) 73 | } 74 | 9 => 75 | // subtract 76 | { 77 | acc = acc.wrapping_sub(process[operand as usize]) 78 | } 79 | 10 => 80 | // multiply 81 | { 82 | acc = acc.wrapping_mul(process[operand as usize]) 83 | } 84 | 11 => 85 | // divide 86 | { 87 | acc = acc.wrapping_div(process[operand as usize]) 88 | } 89 | 12 => 90 | // remainder 91 | { 92 | acc = acc.wrapping_rem(process[operand as usize]) 93 | } 94 | 13 => 95 | // jump 96 | { 97 | ip = operand as usize 98 | } 99 | 14 => 100 | // jump_if_zero 101 | { 102 | if acc == 0 { 103 | ip = operand as usize 104 | } 105 | } 106 | 15 => 107 | // jump_if_nonzero 108 | { 109 | if acc != 0 { 110 | ip = operand as usize 111 | } 112 | } 113 | 16 => 114 | // jump_if_positive 115 | { 116 | if (acc as i16) > 0 { 117 | ip = operand as usize 118 | } 119 | } 120 | 17 => 121 | // jump_if_negative 122 | { 123 | if (acc as i16) < 0 { 124 | ip = operand as usize 125 | } 126 | } 127 | 18 => 128 | // jump_if_nonpositive 129 | { 130 | if (acc as i16) <= 0 { 131 | ip = operand as usize 132 | } 133 | } 134 | 19 => 135 | // jump_if_nonnegative 136 | { 137 | if (acc as i16) >= 0 { 138 | ip = operand as usize 139 | } 140 | } 141 | _ => {} 142 | } 143 | } 144 | } 145 | 146 | fn main() { 147 | let prog: Vec = vec![ 148 | 43, 1, 39, 3, 39, 2, 39, 9, 42, 3, 39, 2, 33, 12, 40, 8, 41, 5, 39, 2, 33, 11, 40, 3, 33, 149 | 15, 5, 1, 34, 7, 5, 0, 0, 6710, 0, 0, 0, 0, 0, 0, 10, 48, 1, 150 | ]; 151 | execute(&prog); 152 | } 153 | -------------------------------------------------------------------------------- /Chapter09/word_machine_sieve/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "word_machine_sieve" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /Chapter10/allocating/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "allocating" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["staticlib"] 9 | 10 | [dependencies] 11 | linux-kernel-module = { path = "../linux-fw" } 12 | 13 | [profile.release] 14 | panic = "abort" 15 | lto = true 16 | 17 | [profile.dev] 18 | panic = "abort" 19 | -------------------------------------------------------------------------------- /Chapter10/allocating/Makefile: -------------------------------------------------------------------------------- 1 | obj-m := allocating.o 2 | ifeq ($(RELEASE),1) 3 | BUILD = release 4 | else 5 | BUILD = debug 6 | endif 7 | 8 | ifeq ($(TARGET),armv7l-linux-kernel-module) 9 | allocating-objs := target/$(TARGET)/$(BUILD)/liballocating.a 10 | else 11 | allocating-objs := target/x86_64-linux-kernel-module/$(BUILD)/liballocating.a 12 | endif 13 | EXTRA_LDFLAGS += --entry=init_module 14 | 15 | ifeq ($(TARGET),armv7l-linux-kernel-module) 16 | CMD_LINE = ARCH=arm CROSS_COMPILE=$(CROSS) 17 | else 18 | KDIR = /lib/modules/$(shell uname -r)/build 19 | CMD_LINE = 20 | endif 21 | 22 | all: 23 | $(MAKE) $(CMD_LINE) -C $(KDIR) M=$(CURDIR) 24 | 25 | clean: 26 | $(MAKE) -C $(KDIR) M=$(CURDIR) clean 27 | -------------------------------------------------------------------------------- /Chapter10/allocating/bd: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cur_dir=$(pwd) 3 | cd ../linux-fw 4 | cargo build 5 | cd $cur_dir 6 | RUST_TARGET_PATH=$(pwd)/../linux-fw cargo xbuild --target x86_64-linux-kernel-module && make 7 | 8 | -------------------------------------------------------------------------------- /Chapter10/allocating/br: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cur_dir=$(pwd) 3 | cd ../linux-fw 4 | cargo build --release 5 | cd $cur_dir 6 | RUST_TARGET_PATH=$(pwd)/../linux-fw cargo xbuild --target x86_64-linux-kernel-module --release && make RELEASE=1 7 | 8 | -------------------------------------------------------------------------------- /Chapter10/allocating/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | extern crate alloc; 4 | use crate::alloc::string::String; 5 | use crate::alloc::vec::Vec; 6 | 7 | use linux_kernel_module::c_types; 8 | use linux_kernel_module::println; 9 | 10 | struct GlobalData { 11 | n: u16, 12 | msg: String, 13 | values: Vec, 14 | } 15 | 16 | static mut GLOBAL: GlobalData = GlobalData { 17 | n: 1000, 18 | msg: String::new(), 19 | values: Vec::new(), 20 | }; 21 | 22 | #[no_mangle] 23 | pub extern "C" fn init_module() -> c_types::c_int { 24 | println!("allocating: Loaded"); 25 | unsafe { 26 | GLOBAL.n += 1; 27 | GLOBAL.msg += "abcd"; 28 | GLOBAL.values.push(500_000); 29 | } 30 | 0 31 | } 32 | 33 | #[no_mangle] 34 | pub extern "C" fn cleanup_module() { 35 | unsafe { 36 | println!("allocating: Unloaded {} {} {}", 37 | GLOBAL.n, 38 | GLOBAL.msg, 39 | GLOBAL.values[0] 40 | ); 41 | } 42 | } 43 | 44 | #[link_section = ".modinfo"] 45 | #[used] 46 | pub static MODINFO: [u8; 12] = *b"license=GPL\0"; 47 | 48 | -------------------------------------------------------------------------------- /Chapter10/boilerplate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "boilerplate" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["staticlib"] 9 | 10 | [dependencies] 11 | linux-kernel-module = { path = "../linux-fw" } 12 | 13 | [profile.release] 14 | panic = "abort" 15 | lto = true 16 | 17 | [profile.dev] 18 | panic = "abort" 19 | -------------------------------------------------------------------------------- /Chapter10/boilerplate/Makefile: -------------------------------------------------------------------------------- 1 | obj-m := boilerplate.o 2 | ifeq ($(RELEASE),1) 3 | BUILD = release 4 | else 5 | BUILD = debug 6 | endif 7 | 8 | ifeq ($(TARGET),armv7l-linux-kernel-module) 9 | boilerplate-objs := target/$(TARGET)/$(BUILD)/libboilerplate.a 10 | else 11 | boilerplate-objs := target/x86_64-linux-kernel-module/$(BUILD)/libboilerplate.a 12 | endif 13 | EXTRA_LDFLAGS += --entry=init_module 14 | 15 | ifeq ($(TARGET),armv7l-linux-kernel-module) 16 | CMD_LINE = ARCH=arm CROSS_COMPILE=$(CROSS) 17 | else 18 | KDIR = /lib/modules/$(shell uname -r)/build 19 | CMD_LINE = 20 | endif 21 | 22 | all: 23 | $(MAKE) $(CMD_LINE) -C $(KDIR) M=$(CURDIR) 24 | 25 | clean: 26 | $(MAKE) -C $(KDIR) M=$(CURDIR) clean 27 | -------------------------------------------------------------------------------- /Chapter10/boilerplate/bd: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cur_dir=$(pwd) 3 | cd ../linux-fw 4 | cargo build 5 | cd $cur_dir 6 | RUST_TARGET_PATH=$(pwd)/../linux-fw cargo xbuild --target x86_64-linux-kernel-module && make 7 | 8 | -------------------------------------------------------------------------------- /Chapter10/boilerplate/br: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cur_dir=$(pwd) 3 | cd ../linux-fw 4 | cargo build --release 5 | cd $cur_dir 6 | RUST_TARGET_PATH=$(pwd)/../linux-fw cargo xbuild --target x86_64-linux-kernel-module --release && make RELEASE=1 7 | 8 | -------------------------------------------------------------------------------- /Chapter10/boilerplate/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use linux_kernel_module::c_types; 4 | use linux_kernel_module::println; 5 | 6 | #[no_mangle] 7 | pub extern "C" fn init_module() -> c_types::c_int { 8 | println!("boilerplate: Loaded"); 9 | 0 10 | } 11 | 12 | #[no_mangle] 13 | pub extern "C" fn cleanup_module() { 14 | println!("boilerplate: Unloaded"); 15 | } 16 | 17 | #[link_section = ".modinfo"] 18 | #[used] 19 | pub static MODINFO: [u8; 12] = *b"license=GPL\0"; 20 | -------------------------------------------------------------------------------- /Chapter10/dots/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dots" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["staticlib"] 9 | 10 | [dependencies] 11 | linux-kernel-module = { path = "../linux-fw" } 12 | 13 | [profile.release] 14 | panic = "abort" 15 | lto = true 16 | 17 | [profile.dev] 18 | panic = "abort" 19 | -------------------------------------------------------------------------------- /Chapter10/dots/Makefile: -------------------------------------------------------------------------------- 1 | obj-m := dots.o 2 | ifeq ($(RELEASE),1) 3 | BUILD = release 4 | else 5 | BUILD = debug 6 | endif 7 | 8 | ifeq ($(TARGET),armv7l-linux-kernel-module) 9 | dots-objs := target/$(TARGET)/$(BUILD)/libdots.a 10 | else 11 | dots-objs := target/x86_64-linux-kernel-module/$(BUILD)/libdots.a 12 | endif 13 | EXTRA_LDFLAGS += --entry=init_module 14 | 15 | ifeq ($(TARGET),armv7l-linux-kernel-module) 16 | CMD_LINE = ARCH=arm CROSS_COMPILE=$(CROSS) 17 | else 18 | KDIR = /lib/modules/$(shell uname -r)/build 19 | CMD_LINE = 20 | endif 21 | 22 | all: 23 | $(MAKE) $(CMD_LINE) -C $(KDIR) M=$(CURDIR) 24 | 25 | clean: 26 | $(MAKE) -C $(KDIR) M=$(CURDIR) clean 27 | -------------------------------------------------------------------------------- /Chapter10/dots/bd: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cur_dir=$(pwd) 3 | cd ../linux-fw 4 | cargo build 5 | cd $cur_dir 6 | RUST_TARGET_PATH=$(pwd)/../linux-fw cargo xbuild --target x86_64-linux-kernel-module && make 7 | 8 | -------------------------------------------------------------------------------- /Chapter10/dots/br: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cur_dir=$(pwd) 3 | cd ../linux-fw 4 | cargo build --release 5 | cd $cur_dir 6 | RUST_TARGET_PATH=$(pwd)/../linux-fw cargo xbuild --target x86_64-linux-kernel-module --release && make RELEASE=1 7 | 8 | -------------------------------------------------------------------------------- /Chapter10/dots/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | extern crate alloc; 4 | 5 | use crate::alloc::boxed::Box; 6 | use linux_kernel_module::bindings::{ 7 | __register_chrdev, __unregister_chrdev, _copy_to_user, file, file_operations, loff_t, 8 | }; 9 | use linux_kernel_module::c_types; 10 | use linux_kernel_module::println; 11 | 12 | struct CharDeviceGlobalData { 13 | major: c_types::c_uint, 14 | name: &'static str, 15 | fops: Option>, 16 | count: u64, 17 | } 18 | 19 | static mut GLOBAL: CharDeviceGlobalData = CharDeviceGlobalData { 20 | major: 0, 21 | name: "dots\0", 22 | fops: None, 23 | count: 0, 24 | }; 25 | 26 | #[no_mangle] 27 | pub extern "C" fn init_module() -> c_types::c_int { 28 | let mut fops = Box::new(file_operations::default()); 29 | fops.read = Some(read_dot); 30 | let major = unsafe { 31 | __register_chrdev( 32 | 0, 33 | 0, 34 | 256, 35 | GLOBAL.name.as_bytes().as_ptr() as *const i8, 36 | &*fops, 37 | ) 38 | }; 39 | if major < 0 { 40 | return 1; 41 | } 42 | unsafe { 43 | GLOBAL.major = major as c_types::c_uint; 44 | } 45 | println!("dots: Loaded with major device number {}", major); 46 | unsafe { 47 | GLOBAL.fops = Some(fops); 48 | } 49 | 0 50 | } 51 | 52 | #[no_mangle] 53 | pub extern "C" fn cleanup_module() { 54 | unsafe { 55 | println!("dots: Unloaded {}", GLOBAL.count); 56 | __unregister_chrdev( 57 | GLOBAL.major, 58 | 0, 59 | 256, 60 | GLOBAL.name.as_bytes().as_ptr() as *const i8, 61 | ) 62 | } 63 | } 64 | 65 | extern "C" fn read_dot( 66 | _arg1: *mut file, 67 | arg2: *mut c_types::c_char, 68 | _arg3: usize, 69 | _arg4: *mut loff_t, 70 | ) -> isize { 71 | unsafe { 72 | GLOBAL.count += 1; 73 | _copy_to_user( 74 | arg2 as *mut c_types::c_void, 75 | if GLOBAL.count % 10 == 0 { "*" } else { "." }.as_ptr() as *const c_types::c_void, 76 | 1, 77 | ); 78 | 1 79 | } 80 | } 81 | 82 | #[link_section = ".modinfo"] 83 | pub static MODINFO: [u8; 12] = *b"license=GPL\0"; 84 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | kernel-cflags-finder/* 5 | !kernel-cflags-finder/Makefile 6 | .cache.mk 7 | *.ko 8 | *.o 9 | *.order 10 | *.cmd 11 | *.mod.c 12 | Module.symvers 13 | .tmp_versions/ 14 | *.swp 15 | *.ur-safe 16 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: rust 3 | cache: cargo 4 | rust: nightly 5 | install: 6 | - sudo apt-get install linux-headers-`uname -r` 7 | - cargo install --force cargo-xbuild 8 | - rustup component add --toolchain=nightly rust-src 9 | - rustup component add rustfmt-preview 10 | jobs: 11 | include: 12 | - stage: hello_world 13 | before_script: cd hello_world 14 | script: 15 | - RUST_TARGET_PATH=$(pwd)/.. cargo xbuild --target x86_64-linux-kernel-module 16 | - make 17 | - stage: yes_chardev 18 | before_script: cd yes_chardev 19 | script: 20 | - RUST_TARGET_PATH=$(pwd)/.. cargo xbuild --target x86_64-linux-kernel-module 21 | - make 22 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linux-kernel-module" 3 | version = "0.1.0" 4 | authors = ["Li Zhuohua "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | # failure = { version = "0.1.3", features = [] } 9 | # libc = { version = "0.2.43", default-features = false } 10 | # spin = "0.4.9" 11 | [build-dependencies] 12 | bindgen = "0.43.0" 13 | cc = "1.0.25" 14 | shlex = "0.1.1" 15 | 16 | [profile.release] 17 | lto = true 18 | panic="abort" 19 | 20 | [profile.dev] 21 | panic="abort" 22 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/armv7l-linux-kernel-module.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi-blacklist": [ 3 | "stdcall", 4 | "fastcall", 5 | "vectorcall", 6 | "thiscall", 7 | "win64", 8 | "sysv64" 9 | ], 10 | "arch": "arm", 11 | "data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64", 12 | "env": "gnu", 13 | "features": "+strict-align,+v7", 14 | "is-builtin": true, 15 | "linker-flavor": "gcc", 16 | "linker-is-gnu": true, 17 | "llvm-target": "arm-unknown-linux-gnueabi", 18 | "max-atomic-width": 64, 19 | "os": "none", 20 | "panic-strategy": "abort", 21 | "position-independent-executables": true, 22 | "pre-link-args": { 23 | "gcc": [ 24 | "-Wl,--as-needed", 25 | "-Wl,-z,noexecstack" 26 | ] 27 | }, 28 | "relocation-model": "static", 29 | "relro-level": "full", 30 | "target-c-int-width": "32", 31 | "target-endian": "little", 32 | "target-family": "unix", 33 | "target-pointer-width": "32", 34 | "vendor": "unknown" 35 | } 36 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/build.rs: -------------------------------------------------------------------------------- 1 | extern crate bindgen; 2 | extern crate cc; 3 | extern crate shlex; 4 | 5 | use std::env; 6 | use std::path::PathBuf; 7 | use std::process::Command; 8 | 9 | const INCLUDED_TYPES: &[&str] = &["file_operations", "ctl_table", "spinlock_t", "mutex", "usb_driver", "usb_device_id", "driver_info"]; 10 | const INCLUDED_FUNCTIONS: &[&str] = &[ 11 | "__register_chrdev", 12 | "__unregister_chrdev", 13 | "_copy_to_user", 14 | "register_sysctl", 15 | "unregister_sysctl_table", 16 | "proc_dointvec_minmax", 17 | "spin_lock", 18 | "usbnet_probe", 19 | "usbnet_disconnect", 20 | "usb_register_driver", 21 | "usb_deregister", 22 | "usbnet_get_endpoints", 23 | "of_get_mac_address", 24 | "skb_pull", 25 | "skb_push", 26 | "skb_trim", 27 | "skb_clone", 28 | "usbnet_skb_return", 29 | "usbnet_read_cmd", 30 | "call_usermodehelper", 31 | "schedule", 32 | //"__purge_module", 33 | //"__rust_delete_module", 34 | ]; 35 | const INCLUDED_VARS: &[&str] = &["__this_module", "THIS_MODULE"]; 36 | 37 | fn main() { 38 | let target = env::var("TARGET").unwrap(); 39 | println!("Target={}", target); 40 | let mut builder = bindgen::Builder::default() 41 | .use_core() 42 | .ctypes_prefix("c_types") 43 | .no_copy(".*") 44 | .derive_default(true) 45 | .rustfmt_bindings(true) 46 | .clang_arg(format!("--target={}", target)); 47 | 48 | let output = String::from_utf8( 49 | Command::new("make") 50 | .arg("-C") 51 | .arg("kernel-cflags-finder") 52 | .arg("-s") 53 | .output() 54 | .unwrap() 55 | .stdout, 56 | ) 57 | .unwrap(); 58 | 59 | Command::new("make") 60 | .arg("-C") 61 | .arg("kernel-cflags-finder") 62 | .arg("clean"); 63 | 64 | println!("get output:{}", output); 65 | // These three arguments are not supported by clang 66 | // output = output.replace("-mapcs", ""); 67 | // output = output.replace("-mno-sched-prolog", ""); 68 | // output = output.replace("-mno-thumb-interwork", ""); 69 | 70 | for arg in shlex::split(&output).unwrap() { 71 | builder = builder.clang_arg(arg.to_string()); 72 | } 73 | 74 | println!("cargo:rerun-if-changed=src/bindgen_helper.h"); 75 | builder = builder.header("src/bindgen_helper.h"); 76 | 77 | for t in INCLUDED_TYPES { 78 | builder = builder.whitelist_type(t); 79 | } 80 | for f in INCLUDED_FUNCTIONS { 81 | builder = builder.whitelist_function(f); 82 | } 83 | for v in INCLUDED_VARS { 84 | builder = builder.whitelist_var(v); 85 | } 86 | let bindings = builder.generate().expect("Unable to generate bindings"); 87 | 88 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 89 | bindings 90 | .write_to_file(out_path.join("bindings.rs")) 91 | .expect("Couldn't write bindings!"); 92 | } 93 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/kernel-cflags-finder/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 Alex Gaynor, Geoffrey Thomas, and other project authors 2 | # 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License along 14 | # with this program; if not, write to the Free Software Foundation, Inc., 15 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 16 | 17 | ifneq ($(KERNELRELEASE),) 18 | obj-m += dummy.o 19 | clean-files := dummy.c 20 | 21 | # Some systems for installing kernel headers (e.g. Debian's) happen to 22 | # trigger the out-of-tree build code because the kernel headers directly 23 | # actually just recursively invokes another non-arch-specific one. This 24 | # means that they already generate absolute paths for -I by using the 25 | # flags/addtree make functions. Some (e.g. Ubuntu's) do not, and 26 | # generate relative paths. We want absolute paths, but we cannot force 27 | # the out-of-tree build code because it won't work on Debian-style 28 | # kernel headers directory (it will look in the mostly-empty kernel 29 | # headers directory instead of the actual one). So we steal the addtree 30 | # and flags functions from scripts/Kbuild.include, and use them _after_ 31 | # the build system has generated paths - if any remaining paths are 32 | # relative, we make them absolute with respect to CURDIR. (Unlike the 33 | # upstream addtree function, we prefix -I./foo. We also need to fix 34 | # -include ./include/linux/kconfig.h) 35 | our_addtree = $(if $(patsubst -I%,%,$(1)), \ 36 | $(if $(filter-out -I/% -I../%,$(1)),$(patsubst ./%,$(CURDIR)/%,$(patsubst -I%,-I$(CURDIR)/%,$(1))),$(1)),$(1)) 37 | our_flags = $(foreach o,$($(1)),$(call our_addtree,$(o))) 38 | # Clang doesn't support these arguments, just ignoring them seems to be ok 39 | # clang_unsupported = -mapcs -mno-sched-prolog -mno-thumb-interwork 40 | # @echo $(NOSTDINC_FLAGS) $(call our_flags,LINUXINCLUDE) $(filter-out $(clang_unsupported),$(__c_flags)) $(modkern_cflags) 41 | 42 | $(M)/dummy.c: 43 | @echo $(NOSTDINC_FLAGS) $(call our_flags,LINUXINCLUDE) $(__c_flags) $(modkern_cflags) 44 | @touch $@ 45 | 46 | .PHONY: $(M)/dummy.c 47 | else 48 | 49 | ifeq ($(TARGET),armv7l-linux-kernel-module) 50 | CMD_LINE = ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- 51 | else 52 | KDIR = /lib/modules/$(shell uname -r)/build 53 | endif 54 | 55 | all: 56 | $(MAKE) CC=clang HOSTCC=clang $(CMD_LINE) -C $(KDIR) M=$(CURDIR) 57 | clean: 58 | $(MAKE) -C $(KDIR) M=$(CURDIR) clean 59 | endif 60 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/src/allocator.rs: -------------------------------------------------------------------------------- 1 | use core::alloc::{GlobalAlloc, Layout}; 2 | 3 | use crate::c_types; 4 | use crate::kernel; 5 | 6 | pub struct KernelAllocator; 7 | 8 | unsafe impl GlobalAlloc for KernelAllocator { 9 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 10 | return kernel::krealloc( 11 | 0 as *const c_types::c_void, 12 | layout.size(), 13 | kernel::GFP_KERNEL, 14 | ) as *mut u8; 15 | } 16 | 17 | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { 18 | kernel::kfree(ptr as *const c_types::c_void); 19 | } 20 | } 21 | 22 | #[lang = "oom"] 23 | extern "C" fn oom(_err: Layout) -> ! { 24 | panic!("Out of memory!"); 25 | } 26 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/src/bindgen_helper.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | int usbnet_read_cmd(struct usbnet *dev, u8 cmd, u8 reqtype, u16 value, 17 | u16 index, void *data, u16 size); 18 | 19 | void bug_helper(void); 20 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/src/bindings.rs: -------------------------------------------------------------------------------- 1 | #![allow(safe_packed_borrows, non_camel_case_types, non_upper_case_globals, non_snake_case, improper_ctypes)] 2 | 3 | use crate::c_types; 4 | 5 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 6 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/src/c_types.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Alex Gaynor, Geoffrey Thomas, and other project authors 2 | // 3 | // This program is free software; you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation; either version 2 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License along 14 | // with this program; if not, write to the Free Software Foundation, Inc., 15 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 16 | 17 | #![allow(non_camel_case_types)] 18 | 19 | pub type c_int = i32; 20 | pub type c_char = i8; 21 | // pub type c_long = i64; 22 | pub type c_long = i32; 23 | pub type c_longlong = i64; 24 | pub type c_short = i16; 25 | pub type c_uchar = u8; 26 | pub type c_uint = u32; 27 | // pub type c_ulong = u64; 28 | pub type c_ulong = u32; 29 | pub type c_ulonglong = u64; 30 | pub type c_ushort = u16; 31 | pub type c_schar = i8; 32 | 33 | #[repr(u8)] 34 | pub enum c_void { 35 | #[doc(hidden)] 36 | __nothing_to_see_here, 37 | #[doc(hidden)] 38 | __move_along, 39 | } 40 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/src/c_wrapper.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | long copy_to_user_wrapper(void __user *to, const void *from, unsigned long n) { 13 | return _copy_to_user(to, from, n); 14 | } 15 | 16 | void spin_lock_init_wrapper(spinlock_t *lock) { spin_lock_init(lock); } 17 | void spin_lock_wrapper(spinlock_t *lock) { spin_lock(lock); } 18 | void spin_unlock_wrapper(spinlock_t *lock) { spin_unlock(lock); } 19 | 20 | void mutex_init_wrapper(struct mutex *lock) { mutex_init(lock); } 21 | void mutex_lock_wrapper(struct mutex *lock) { mutex_lock(lock); } 22 | void mutex_unlock_wrapper(struct mutex *lock) { mutex_unlock(lock); } 23 | 24 | void skb_set_tail_pointer_wrapper(struct sk_buff *skb, const int offset) { 25 | skb_set_tail_pointer(skb, offset); 26 | } 27 | 28 | int usbnet_read_cmd_wrapper(struct usbnet *dev, u8 cmd, u8 reqtype, u16 value, 29 | u16 index, void *data, u16 size) { 30 | return 0; 31 | } 32 | 33 | void bug_helper(void) { 34 | schedule(); 35 | } 36 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/src/kernel.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | 3 | use crate::c_types; 4 | 5 | pub type gfp_t = c_types::c_uint; 6 | pub const GFP_KERNEL: gfp_t = 20971712; 7 | 8 | extern "C" { 9 | pub fn krealloc(arg1: *const c_types::c_void, arg2: usize, arg3: gfp_t) 10 | -> *mut c_types::c_void; 11 | } 12 | 13 | extern "C" { 14 | pub fn kfree(arg1: *const c_types::c_void); 15 | } 16 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/src/kernel_module.rs: -------------------------------------------------------------------------------- 1 | use crate::kernel_result::*; 2 | 3 | pub trait KernelModule : Sized { 4 | fn init() -> KernelResult; 5 | } 6 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/src/kernel_result.rs: -------------------------------------------------------------------------------- 1 | pub enum KernelError { 2 | EPERM = 1, // Operation not permitted */ 3 | ENOENT = 2, // No such file or directory */ 4 | ESRCH = 3, // No such process */ 5 | EINTR = 4, // Interrupted system call */ 6 | EIO = 5, // I/O error */ 7 | ENXIO = 6, // No such device or address */ 8 | E2BIG = 7, // Argument list too long */ 9 | ENOEXEC = 8, // Exec format error */ 10 | EBADF = 9, // Bad file number */ 11 | ECHILD = 10, // No child processes */ 12 | EAGAIN = 11, // Try again */ 13 | ENOMEM = 12, // Out of memory */ 14 | EACCES = 13, // Permission denied */ 15 | EFAULT = 14, // Bad address */ 16 | ENOTBLK = 15, // Block device required */ 17 | EBUSY = 16, // Device or resource busy */ 18 | EEXIST = 17, // File exists */ 19 | EXDEV = 18, // Cross-device link */ 20 | ENODEV = 19, // No such device */ 21 | ENOTDIR = 20, // Not a directory */ 22 | EISDIR = 21, // Is a directory */ 23 | EINVAL = 22, // Invalid argument */ 24 | ENFILE = 23, // File table overflow */ 25 | EMFILE = 24, // Too many open files */ 26 | ENOTTY = 25, // Not a typewriter */ 27 | ETXTBSY = 26, // Text file busy */ 28 | EFBIG = 27, // File too large */ 29 | ENOSPC = 28, // No space left on device */ 30 | ESPIPE = 29, // Illegal seek 31 | EROFS = 30, // Read-only file system 32 | EMLINK = 31, // Too many links 33 | EPIPE = 32, // Broken pipe 34 | EDOM = 33, // Math argument out of domain of func 35 | ERANGE = 34, // Math result not representable 36 | } 37 | 38 | pub type KernelResult = Result; 39 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(lang_items, allocator_api)] 3 | 4 | pub mod allocator; 5 | pub mod c_types; 6 | pub mod kernel; 7 | pub mod kernel_module; 8 | pub mod kernel_result; 9 | pub mod printk; 10 | pub mod bindings; 11 | pub mod sync; 12 | pub mod panic; 13 | 14 | pub use self::kernel_module::KernelModule; 15 | pub use self::kernel_result::KernelResult; 16 | pub use self::kernel_result::KernelError; 17 | 18 | #[global_allocator] 19 | static ALLOCATOR: allocator::KernelAllocator = allocator::KernelAllocator; 20 | 21 | #[lang = "eh_personality"] 22 | extern "C" fn eh_personality() {} 23 | 24 | #[no_mangle] 25 | pub extern "C" fn __aeabi_unwind_cpp_pr0() {} 26 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/src/panic.rs: -------------------------------------------------------------------------------- 1 | use core::panic::PanicInfo; 2 | pub use crate::bindings; 3 | pub use crate::println; 4 | 5 | #[panic_handler] 6 | fn panic(_info: &PanicInfo) -> ! { 7 | loop {} 8 | } 9 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/src/printk.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Alex Gaynor, Geoffrey Thomas, and other project authors 2 | // 3 | // This program is free software; you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation; either version 2 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License along 14 | // with this program; if not, write to the Free Software Foundation, Inc., 15 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 16 | 17 | use core::{cmp, fmt}; 18 | use crate::c_types; 19 | 20 | extern "C" { 21 | pub fn printk(fmt: *const c_types::c_char, ...) -> c_types::c_int; 22 | } 23 | 24 | const LOG_LINE_MAX: usize = 1024 - 32; 25 | 26 | pub struct LogLineWriter { 27 | data: [u8; LOG_LINE_MAX], 28 | pos: usize, 29 | } 30 | 31 | impl LogLineWriter { 32 | pub fn new() -> LogLineWriter { 33 | LogLineWriter { 34 | data: [0u8; LOG_LINE_MAX], 35 | pos: 0, 36 | } 37 | } 38 | 39 | pub fn as_bytes(&self) -> &[u8] { 40 | return &self.data[..self.pos]; 41 | } 42 | } 43 | 44 | impl fmt::Write for LogLineWriter { 45 | fn write_str(&mut self, s: &str) -> fmt::Result { 46 | let copy_len = cmp::min(LOG_LINE_MAX - self.pos, s.as_bytes().len()); 47 | self.data[self.pos..self.pos + copy_len].copy_from_slice(&s.as_bytes()[..copy_len]); 48 | self.pos += copy_len; 49 | return Ok(()); 50 | } 51 | } 52 | 53 | #[macro_export] 54 | macro_rules! println { 55 | () => {{ 56 | $crate::printk::printk("\n\0".as_bytes().as_ptr() as *const i8); 57 | }}; 58 | ($fmt:expr) => {{ 59 | unsafe { 60 | $crate::printk::printk(concat!($fmt, "\n\0").as_bytes().as_ptr() as *const i8); 61 | } 62 | }}; 63 | ($fmt:expr, $($arg:tt)*) => ({ 64 | use ::core::fmt; 65 | let mut writer = $crate::printk::LogLineWriter::new(); 66 | let _ = fmt::write(&mut writer, format_args!(concat!($fmt, "\n\0"), $($arg)*)).unwrap(); 67 | unsafe { 68 | $crate::printk::printk(writer.as_bytes().as_ptr() as *const i8); 69 | } 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/src/sync.rs: -------------------------------------------------------------------------------- 1 | use core::cell::UnsafeCell; 2 | use core::ops::{Deref, DerefMut, Drop}; 3 | use crate::bindings; 4 | use crate::println; 5 | 6 | extern "C" { 7 | pub fn spin_lock_init_wrapper(lock: *mut bindings::spinlock_t); 8 | pub fn spin_lock_wrapper(lock: *mut bindings::spinlock_t); 9 | pub fn spin_unlock_wrapper(lock: *mut bindings::spinlock_t); 10 | 11 | pub fn mutex_init_wrapper(lock: *mut bindings::mutex); 12 | pub fn mutex_lock_wrapper(lock: *mut bindings::mutex); 13 | pub fn mutex_unlock_wrapper(lock: *mut bindings::mutex); 14 | } 15 | 16 | pub struct Spinlock { 17 | lock: UnsafeCell, 18 | data: UnsafeCell, 19 | } 20 | 21 | pub struct SpinlockGuard<'a, T: ?Sized + 'a> { 22 | lock: &'a mut bindings::spinlock_t, 23 | data: &'a mut T, 24 | } 25 | 26 | unsafe impl Sync for Spinlock {} 27 | unsafe impl Send for Spinlock {} 28 | 29 | impl Spinlock { 30 | pub fn new(user_data: T) -> Spinlock { 31 | let mut lock = bindings::spinlock_t::default(); 32 | unsafe { 33 | spin_lock_init_wrapper(&mut lock); 34 | } 35 | Spinlock { 36 | lock: UnsafeCell::new(lock), 37 | data: UnsafeCell::new(user_data), 38 | } 39 | } 40 | 41 | pub fn lock(&self) -> SpinlockGuard { 42 | unsafe { 43 | spin_lock_wrapper(self.lock.get()); 44 | } 45 | println!("Spinlock is locked!"); 46 | SpinlockGuard { 47 | lock: unsafe { &mut *self.lock.get() }, 48 | data: unsafe { &mut *self.data.get() }, 49 | } 50 | } 51 | } 52 | 53 | impl<'a, T: ?Sized> Deref for SpinlockGuard<'a, T> { 54 | type Target = T; 55 | fn deref<'b>(&'b self) -> &'b T { 56 | &*self.data 57 | } 58 | } 59 | 60 | impl<'a, T: ?Sized> DerefMut for SpinlockGuard<'a, T> { 61 | fn deref_mut<'b>(&'b mut self) -> &'b mut T { 62 | &mut *self.data 63 | } 64 | } 65 | 66 | impl<'a, T: ?Sized> Drop for SpinlockGuard<'a, T> { 67 | fn drop(&mut self) { 68 | unsafe { spin_unlock_wrapper(self.lock) } 69 | println!("Spinlock is dropped!"); 70 | } 71 | } 72 | 73 | pub struct Mutex { 74 | lock: UnsafeCell, 75 | data: UnsafeCell, 76 | } 77 | 78 | pub struct MutexGuard<'a, T: ?Sized + 'a> { 79 | lock: &'a mut bindings::mutex, 80 | data: &'a mut T, 81 | } 82 | 83 | unsafe impl Sync for Mutex {} 84 | unsafe impl Send for Mutex {} 85 | 86 | impl Mutex { 87 | pub fn new(user_data: T) -> Mutex { 88 | let mut lock = bindings::mutex::default(); 89 | unsafe { 90 | mutex_init_wrapper(&mut lock); 91 | } 92 | Mutex { 93 | lock: UnsafeCell::new(lock), 94 | data: UnsafeCell::new(user_data), 95 | } 96 | } 97 | 98 | pub fn lock(&self) -> MutexGuard { 99 | unsafe { 100 | mutex_lock_wrapper(self.lock.get()); 101 | } 102 | println!("Mutex is locked!"); 103 | MutexGuard { 104 | lock: unsafe { &mut *self.lock.get() }, 105 | data: unsafe { &mut *self.data.get() }, 106 | } 107 | } 108 | } 109 | 110 | impl<'a, T: ?Sized> Deref for MutexGuard<'a, T> { 111 | type Target = T; 112 | fn deref<'b>(&'b self) -> &'b T { 113 | &*self.data 114 | } 115 | } 116 | 117 | impl<'a, T: ?Sized> DerefMut for MutexGuard<'a, T> { 118 | fn deref_mut<'b>(&'b mut self) -> &'b mut T { 119 | &mut *self.data 120 | } 121 | } 122 | 123 | impl<'a, T: ?Sized> Drop for MutexGuard<'a, T> { 124 | fn drop(&mut self) { 125 | unsafe { mutex_unlock_wrapper(self.lock) } 126 | println!("Mutex is dropped!"); 127 | } 128 | } 129 | 130 | pub fn drop(_x: T) { } 131 | -------------------------------------------------------------------------------- /Chapter10/linux-fw/x86_64-linux-kernel-module.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "x86_64", 3 | "cpu": "x86-64", 4 | "code-model": "kernel", 5 | "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", 6 | "disable-redzone": true, 7 | "eliminate-frame-pointer": false, 8 | "env": "gnu", 9 | "features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float", 10 | "linker-flavor": "gcc", 11 | "linker-is-gnu": true, 12 | "llvm-target": "x86_64-elf", 13 | "max-atomic-width": 64, 14 | "no-compiler-rt": true, 15 | "os": "none", 16 | "panic-strategy": "abort", 17 | "position-independent-executables": true, 18 | "pre-link-args": { 19 | "gcc": [ 20 | "-Wl,--as-needed", 21 | "-Wl,-z,noexecstack", 22 | "-m64" 23 | ] 24 | }, 25 | "relocation-model": "static", 26 | "relro-level": "full", 27 | "target-c-int-width": "32", 28 | "target-endian": "little", 29 | "target-family": "unix", 30 | "target-pointer-width": "64", 31 | "vendor": "unknown", 32 | "needs-plt": true 33 | } 34 | -------------------------------------------------------------------------------- /Chapter10/state/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "state" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["staticlib"] 9 | 10 | [dependencies] 11 | linux-kernel-module = { path = "../linux-fw" } 12 | 13 | [profile.release] 14 | panic = "abort" 15 | lto = true 16 | 17 | [profile.dev] 18 | panic = "abort" 19 | -------------------------------------------------------------------------------- /Chapter10/state/Makefile: -------------------------------------------------------------------------------- 1 | obj-m := state.o 2 | ifeq ($(RELEASE),1) 3 | BUILD = release 4 | else 5 | BUILD = debug 6 | endif 7 | 8 | ifeq ($(TARGET),armv7l-linux-kernel-module) 9 | state-objs := target/$(TARGET)/$(BUILD)/libstate.a 10 | else 11 | state-objs := target/x86_64-linux-kernel-module/$(BUILD)/libstate.a 12 | endif 13 | EXTRA_LDFLAGS += --entry=init_module 14 | 15 | ifeq ($(TARGET),armv7l-linux-kernel-module) 16 | CMD_LINE = ARCH=arm CROSS_COMPILE=$(CROSS) 17 | else 18 | KDIR = /lib/modules/$(shell uname -r)/build 19 | CMD_LINE = 20 | endif 21 | 22 | all: 23 | $(MAKE) $(CMD_LINE) -C $(KDIR) M=$(CURDIR) 24 | 25 | clean: 26 | $(MAKE) -C $(KDIR) M=$(CURDIR) clean 27 | -------------------------------------------------------------------------------- /Chapter10/state/bd: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cur_dir=$(pwd) 3 | cd ../linux-fw 4 | cargo build 5 | cd $cur_dir 6 | RUST_TARGET_PATH=$(pwd)/../linux-fw cargo xbuild --target x86_64-linux-kernel-module && make 7 | 8 | -------------------------------------------------------------------------------- /Chapter10/state/br: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cur_dir=$(pwd) 3 | cd ../linux-fw 4 | cargo build --release 5 | cd $cur_dir 6 | RUST_TARGET_PATH=$(pwd)/../linux-fw cargo xbuild --target x86_64-linux-kernel-module --release && make RELEASE=1 7 | 8 | -------------------------------------------------------------------------------- /Chapter10/state/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use linux_kernel_module::c_types; 4 | use linux_kernel_module::println; 5 | 6 | struct GlobalData { n: u16 } 7 | 8 | static mut GLOBAL: GlobalData = GlobalData { n: 1000 }; 9 | 10 | #[no_mangle] 11 | pub extern "C" fn init_module() -> c_types::c_int { 12 | println!("state: Loaded"); 13 | unsafe { GLOBAL.n += 1; } 14 | 0 15 | } 16 | 17 | #[no_mangle] 18 | pub extern "C" fn cleanup_module() { 19 | println!("state: Unloaded {}", unsafe { GLOBAL.n }); 20 | } 21 | 22 | #[link_section = ".modinfo"] 23 | #[used] 24 | pub static MODINFO: [u8; 12] = *b"license=GPL\0"; 25 | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Creative Projects for Rust Programmers 5 | 6 | Creative Projects for Rust Programmers 7 | 8 | This is the code repository for [Creative Projects for Rust Programmers](https://www.packtpub.com/programming/creative-projects-for-rust-programmers?utm_source=github&utm_medium=repository&utm_campaign=), published by Packt. 9 | 10 | **Build exciting projects on domains such as web apps, WebAssembly, games, and parsing** 11 | 12 | ## What is this book about? 13 | Build projects on exciting topics like game development, virtual reality, web assembly, emulators, GUI, and Linux/kernel development. By the end of the book, you will know how to choose the right framework or library for your needs. 14 | 15 | This book covers the following exciting features: 16 | * Access TOML, JSON, and XML files and SQLite, PostgreSQL, and Redis databases 17 | * Develop a RESTful web service using JSON payloads 18 | * Create a web application using HTML templates and JavaScript and a frontend web application or web game using WebAssembly 19 | * Build desktop 2D games 20 | * Develop an interpreter and a compiler for a programming language 21 | * Create a machine language emulator 22 | * Extend the Linux Kernel with loadable modules 23 | 24 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1789346223) today! 25 | 26 | https://www.packtpub.com/ 28 | 29 | ## Instructions and Navigations 30 | All of the code is organized into folders. For example, Chapter02. 31 | 32 | The code will look like the following: 33 | ``` 34 | { 35 | for pos in pos..5 { 36 | print!("{}", digits[pos] as u8 as char); 37 | } 38 | ``` 39 | 40 | **Following is what you need for this book:** 41 | This Rust programming book is for developers who want to get hands-on experience with implementing their knowledge of Rust programming, and are looking for expert advice on which libraries and frameworks they can adopt to develop software that typically uses the Rust language. 42 | 43 | With the following software and hardware list you can run all code files present in the book (Chapter 1-11). 44 | ### Software and Hardware List 45 | | Chapter | Software required | OS required | 46 | | -------- | ------------------------------------ | ----------------------------------- | 47 | | 1-11 | Rust v1.31 | Windows and Linux Mint 19.2 | 48 | 49 | We also provide a PDF file that has color images of the screenshots/diagrams used in this book. [Click here to download it](https://static.packt-cdn.com/downloads/9781789346220_ColorImages.pdf). 50 | 51 | ### Related products 52 | * Rust Programming Cookbook [[Packt]](https://www.packtpub.com/programming/rust-programming-cookbook?utm_source=github&utm_medium=repository&utm_campaign=9781789530667) [[Amazon]](https://www.amazon.com/dp/1789530660) 53 | 54 | * Hands-On Microservices with Rust [[Packt]](https://www.packtpub.com/web-development/hands-microservices-rust?utm_source=github&utm_medium=repository&utm_campaign=9781789342758) [[Amazon]](https://www.amazon.com/dp/1789342759) 55 | 56 | ## Get to Know the Author 57 | **Carlo Milanesi** 58 | has a computer science degree from the State University of Milan, and lives in Bergamo, Italy. He is a software engineer with decades of experience in teaching and developing software for desktop and the web on Windows or Linux, using C, C++, Smalltalk, Delphi, Visual Basic, C#, Java, JavaScript, and Rust. 59 | 60 | He loves writing tests and documentation and has experience in the domains of banking, portfolio management, construction engineering, CAD systems for milling machines, human-machine interface systems for machine tools, and websites and web applications for enterprises and for public administration management. He has written a book on Rust entitled, Beginning Rust: From Novice to Professional. 61 | 62 | ### Suggestions and Feedback 63 | [Click here](https://docs.google.com/forms/d/e/1FAIpQLSdy7dATC6QmEL81FIUuymZ0Wy9vH1jHkvpY57OiMeKGqib_Ow/viewform) if you have any feedback or suggestions. 64 | 65 | 66 | ### Download a free PDF 67 | 68 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
69 |

https://packt.link/free-ebook/9781789346220

--------------------------------------------------------------------------------