Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 0 additions & 58 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
32 changes: 21 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
<div align="center">

# 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.
</div>

<p align="center">
<a href="https://www.rust-lang.org/">
<img src="https://img.shields.io/badge/Made%20with-Rust-1f425f.svg?style=flat-square" alt="Made with Rust"></a>
&nbsp;
<a href="https://github.com/Neotamandua/Sub-OCaml/blob/master/LICENSE">
<img src="https://img.shields.io/badge/License-MPL_2.0-brightgreen.svg?style=flat-square" alt="License: MPL 2.0"></a>
&nbsp;
<img src="https://img.shields.io/github/languages/code-size/Neotamandua/Sub-OCaml?style=flat-square" alt="GitHub code size">
&nbsp;
<img src="https://img.shields.io/github/actions/workflow/status/Neotamandua/Sub-OCaml/build.yml?style=flat-square" alt="GitHub CI">
</p>

## 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 |
| -------- | --------------- |
Expand All @@ -27,7 +36,8 @@ thiserror = "2.0"
| Evaluator | ✅ |


### Examples:
### Examples

**1**
```ocaml
let x = 5 in x
Expand Down
187 changes: 108 additions & 79 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = std::result::Result<T, Error>;

#[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());
}
}
Loading