diff --git a/Cargo.lock b/Cargo.lock index 26e6472..07fcab0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,61 +5,3 @@ version = 4 [[package]] name = "Sub-OCaml" version = "0.1.0" -dependencies = [ - "thiserror", -] - -[[package]] -name = "proc-macro2" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "syn" -version = "2.0.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "2.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "unicode-ident" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" diff --git a/Cargo.toml b/Cargo.toml index aa98a2c..da9bd5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,3 @@ version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -thiserror = "2.0" diff --git a/README.md b/README.md index b4a239d..ad860c9 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,32 @@ +
+ # Sub-OCaml -[![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg?style=flat-square)](https://www.rust-lang.org/) -[![License: MPL 2.0](https://img.shields.io/badge/License-MPL_2.0-brightgreen.svg?style=flat-square)](https://github.com/Neotamandua/Sub-OCaml/blob/master/LICENSE) -![License: MPL 2.0](https://img.shields.io/github/languages/code-size/Neotamandua/Sub-OCaml?style=flat-square) -![Github CI](https://img.shields.io/github/actions/workflow/status/Neotamandua/Sub-OCaml/build.yml?style=flat-square) > Interpreter for a subset of the OCaml language. \ > This project did not intend to use idiomatic rust code. I mainly explored the syntactical possibilites of matches, recursion etc. for rust. +
+ +

+ +Made with Rust +  + +License: MPL 2.0 +  +GitHub code size +  +GitHub CI +

## Usage + You can use the [REPL](https://github.com/Neotamandua/Sub-OCaml-REPL/) to directly execute code and try it out -### Dependencies: +### Dependencies -```toml -[dependencies] -thiserror = "2.0" -``` +This project has no external dependencies. -### Features: +### Features | Features | Status | | -------- | --------------- | @@ -27,7 +36,8 @@ thiserror = "2.0" | Evaluator | ✅ | -### Examples: +### Examples + **1** ```ocaml let x = 5 in x diff --git a/src/error.rs b/src/error.rs index 778aed7..4cfc9cd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,94 +2,123 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use thiserror::Error; +use std::{error, fmt}; // std result alias pub type Result = std::result::Result; -#[derive(Debug, Error)] -pub enum Error { - #[error("{0}")] - LexerError(#[from] LexerError), - #[error("{0}")] - ParserError(#[from] ParserError), - #[error("{0}")] - TypeCheckError(#[from] TypeCheckError), - #[error("{0}")] - EvaluatorError(#[from] EvaluatorError), - #[error("{0}")] - UtilsError(#[from] UtilsError), -} +macro_rules! error_enums { + ($($name:ident { $($variant:ident $(($field:ident: $ty:ty))? => $message:literal),* $(,)? })*) => { + #[derive(Debug)] + pub enum Error { $($name($name)),* } -#[derive(Debug, Error)] -pub enum LexerError { - #[error( - "Lexer Error: '<' is forbidden in Identifiers, Keywords and Variables (Syntax Error). \n - Additional information: No LT (<=) supported yet" - )] - ForbiddenCharLEQ, - #[error("Lexer Error: no valid Character found")] - ForbiddenChar, - #[error("Lexer Error: Comment started but does not end")] - CommentError, - #[error("Lexer Error: Identifiers are not allowed to start with a number")] - IdentifierError, - #[error("Lexer Error: unexpected EOF")] - EOFError, - #[error("Lexer Error (take_while): No Matches for Identifier")] - NoMatches, -} + impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { $(Self::$name(error) => fmt::Display::fmt(error, f)),* } + } + } -#[derive(Debug, Error)] -pub enum ParserError { - #[error("Parser Error: Type Error")] - TypeError, - #[error("Parser Error: pexp parse error \n {0}")] - PexpError(String), - #[error("Verify failed: No token")] - NoToken, - #[error("Verify failed: wrong token")] - WrongToken, -} + impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { $(Self::$name(error) => Some(error)),* } + } + } + + $( + impl From<$name> for Error { + fn from(error: $name) -> Self { Self::$name(error) } + } -#[derive(Debug, Error)] -pub enum TypeCheckError { - #[error("Typecheck Error: operator application failed because of ill-typed arguments")] - ArgumentError, - #[error("Typecheck Error: function application failed because of wrong argument type")] - WrongArgument, - #[error( - "Typecheck Error: function application failed because function was expected but none given" - )] - MissingFunction, - #[error("Typecheck Error: variable {0} is unbound")] - UnboundVariable(String), - #[error("Typecheck Error: types for branch cases (if-case, else-case) are not equal")] - UnequalIfTypes, - #[error("Typecheck Error: bool expected for if but got {0}")] - WrongIfType(String), - #[error("Typecheck Error: fun has missing type")] - MissingFunctionType, - #[error("Typecheck Error: missing types for let rec")] - MissingType, - #[error("Typecheck Error: declared type of let rec not matched")] - NoTypeMatch, + #[derive(Debug)] + pub enum $name { $($variant $(($ty))?),* } + + impl fmt::Display for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + $(Self::$variant $(($field))? => write!(f, $message $(, $field)?)),* + } + } + } + + impl error::Error for $name {} + )* + }; } -#[derive(Debug, Error)] -pub enum EvaluatorError { - #[error("Evaluate Error: operator application failed because of ill-typed arguments")] - ArgumentError, - #[error( - "Evaluate Error: function application failed because function was expected but none given" - )] - MissingFunction, - #[error("Evaluate Error: bool expected for if but got {0}")] - WrongIfType(String), +error_enums! { + LexerError { + ForbiddenCharLEQ => "Lexer Error: '<' is forbidden in Identifiers, Keywords and Variables (Syntax Error). \n \n Additional information: No LT (<=) supported yet", + ForbiddenChar => "Lexer Error: no valid Character found", + CommentError => "Lexer Error: Comment started but does not end", + IdentifierError => "Lexer Error: Identifiers are not allowed to start with a number", + EOFError => "Lexer Error: unexpected EOF", + NoMatches => "Lexer Error (take_while): No Matches for Identifier", + } + ParserError { + TypeError => "Parser Error: Type Error", + PexpError(context: String) => "Parser Error: pexp parse error \n {}", + NoToken => "Verify failed: No token", + WrongToken => "Verify failed: wrong token", + } + TypeCheckError { + ArgumentError => "Typecheck Error: operator application failed because of ill-typed arguments", + WrongArgument => "Typecheck Error: function application failed because of wrong argument type", + MissingFunction => "Typecheck Error: function application failed because function was expected but none given", + UnboundVariable(variable: String) => "Typecheck Error: variable {} is unbound", + UnequalIfTypes => "Typecheck Error: types for branch cases (if-case, else-case) are not equal", + WrongIfType(ty: String) => "Typecheck Error: bool expected for if but got {}", + MissingFunctionType => "Typecheck Error: fun has missing type", + MissingType => "Typecheck Error: missing types for let rec", + NoTypeMatch => "Typecheck Error: declared type of let rec not matched", + } + EvaluatorError { + ArgumentError => "Evaluate Error: operator application failed because of ill-typed arguments", + MissingFunction => "Evaluate Error: function application failed because function was expected but none given", + WrongIfType(ty: String) => "Evaluate Error: bool expected for if but got {}", + } + UtilsError { + OutOfBounds => "EOF, out of bounds", + } } -#[derive(Debug, Error)] -pub enum UtilsError { - #[error("EOF, out of bounds")] - OutOfBounds, +#[cfg(test)] +mod tests { + use super::*; + use std::error::Error as _; + + #[test] + fn displays_errors() { + assert_eq!( + LexerError::ForbiddenCharLEQ.to_string(), + "Lexer Error: '<' is forbidden in Identifiers, Keywords and Variables (Syntax Error). \n \n Additional information: No LT (<=) supported yet" + ); + assert_eq!( + LexerError::ForbiddenChar.to_string(), + "Lexer Error: no valid Character found" + ); + assert_eq!( + ParserError::PexpError("context".into()).to_string(), + "Parser Error: pexp parse error \n context" + ); + assert_eq!( + TypeCheckError::UnboundVariable("x".into()).to_string(), + "Typecheck Error: variable x is unbound" + ); + assert_eq!( + EvaluatorError::WrongIfType("int".into()).to_string(), + "Evaluate Error: bool expected for if but got int" + ); + assert_eq!(UtilsError::OutOfBounds.to_string(), "EOF, out of bounds"); + } + + #[test] + fn wraps_error_sources() { + let error: Error = ParserError::WrongToken.into(); + assert_eq!(error.to_string(), "Verify failed: wrong token"); + assert_eq!( + error.source().unwrap().to_string(), + "Verify failed: wrong token" + ); + assert!(error.source().unwrap().source().is_none()); + } }