Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.3.5 | Error handling #388

Merged
merged 16 commits into from
Dec 28, 2022
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
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on:
push:
branches: [ master, develop ]
pull_request:
release:
types: [ published ]

env:
CARGO_TERM_COLOR: always
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mamba"
version = "0.3.4"
version = "0.3.5"
authors = ["Joël Abrahams <abrahamsjo@gmail.com>"]
description = "A transpiler which converts Mamba files to Python 3 files"
edition = "2018"
Expand Down
54 changes: 30 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@
<a href="/~https://github.com/JSAbrahams/mamba/blob/main/LICENSE">
<img src="https://img.shields.io/github/license/JSAbrahams/mamba.svg?style=for-the-badge" alt="License"/>
</a>
<img src="https://img.shields.io/badge/Built%20with-%E2%99%A5-red.svg?style=for-the-badge" alt="Built with Love"/>
<br/>
<a href="/~https://github.com/JSAbrahams/mamba/milestones">
<img src="https://img.shields.io/github/milestones/open/JSAbrahams/mamba?style=for-the-badge" alt="Active milestones"/>
</a>
<img src="https://img.shields.io/badge/Built%20with-%E2%99%A5-red.svg?style=for-the-badge" alt="Built with Love"/>
</p>

<h1 align="center">Mamba</h1>
Expand Down Expand Up @@ -56,7 +55,7 @@ def factorial(x: Int) -> Int => match x

def num := input("Compute factorial: ")
if num.is_digit() then
def result := factorial(int(num))
def result := factorial(Int(num))
print("Factorial {num} is: {result}.")
else
print("Input was not an integer.")
Expand Down Expand Up @@ -93,17 +92,21 @@ class MyServer(def ip_address: IPv4Address)
def is_connected: Bool := False
def _last_message: String? := None

def last_sent(fin self) -> String raise ServerError => if self._last_message /= None
then self._last_message
else raise ServerError("No last message!")
def last_sent(fin self) -> String raise [ServerError] =>
if self._last_message != None then
self._last_message
else
raise ServerError("No last message!")

def connect(self) =>
self.is_connected := True
print(always_the_same_message)

def send(self, message: String) raise ServerError => if self.is_connected
then self._last_message := message
else raise ServerError("Not connected!")
def send(self, message: String) raise [ServerError] =>
if self.is_connected then
self._last_message := message
else
raise ServerError("Not connected!")

def disconnect(self) => self.is_connected := False
```
Expand All @@ -127,7 +130,7 @@ print("last message sent before disconnect: \"{my_server.last_sent()}\".")
my_server.disconnect()
```

### 🗃 Type refinement (🇻 0.6+)
### 🗃 Type refinement (🇻 0.4.1+)

As shown above Mamba has a type system.
Mamba however also has type refinement features to assign additional properties to types.
Expand All @@ -152,9 +155,11 @@ class MyServer(self: DisConnMyServer, def ip_address: IPv4Address): Server
def is_connected: Bool := False
def _last_message: String? := None

def last_sent(self) -> String raise ServerErr => if self.last_message /= None
then self._last_message
else raise ServerError("No last message!")
def last_sent(self) -> String raise ServerErr =>
if self.last_message != None then
self._last_message
else
raise ServerError("No last message!")

def connect(self: DisConnMyServer) => self.is_connected := True

Expand Down Expand Up @@ -208,7 +213,7 @@ Type refinement allows us to do some additional things:
that certain conditions hold. We can simply ask whether a given object is a certain state by checking whether it is a
certain type.

### 🔒 Pure functions (🇻 0.4.1)
### 🔒 Pure functions (🇻 0.4.1+)

Mamba has features to ensure that functions are pure, meaning that if `x = y`, for any `f`, `f(x) = f(y)`.
(Except if the output of the function is say `None` or `NaN`.)
Expand Down Expand Up @@ -242,7 +247,7 @@ def pure sin(x: Int) =>
ans
```

### ⚠ Error handling (🇻 0.5+)
### ⚠ Error handling

Unlike Python, Mamba does not have `try` `except` and `finally` (or `try` `catch` as it is sometimes known).
Instead, we aim to directly handle errors on-site so the origin of errors is more tracable.
Expand Down Expand Up @@ -274,15 +279,16 @@ This also prevents us from wrapping large code blocks in a `try`, where it might
This is shown below:

```mamba
def a := function_may_throw_err() handle
err: MyErr =>
print("We have a problem: {err.message}.")
return # we return, halting execution
err: MyOtherErr =>
print("We have another problem: {err.message}.")
0 # ... or we assign default value 0 to a

print("a has value {a}.")
def g() =>
def a := function_may_throw_err() handle
err: MyErr =>
print("We have a problem: {err.message}.")
return # we return, halting execution
err: MyOtherErr =>
print("We have another problem: {err.message}.")
0 # ... or we assign default value 0 to a

print("a has value {a}.")
```

If we don't want to use a `handle`, we can simply use `raise` after a statement or exception to show that its execution might result in an exception, but we don't want to handle that here.
Expand Down
1 change: 0 additions & 1 deletion src/check/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ pub enum NodeTy {
Pass,
Question { left: Box<ASTTy>, right: Box<ASTTy> },
QuestionOp { expr: Box<ASTTy> },
Comment { comment: String },
}

#[cfg(test)]
Expand Down
3 changes: 1 addition & 2 deletions src/check/ast/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,7 @@ impl From<&Node> for NodeTy {
Node::ReturnEmpty => NodeTy::ReturnEmpty,
Node::Underscore => NodeTy::Underscore,
Node::Undefined => NodeTy::Undefined,
Node::Pass => NodeTy::Pass,
Node::Comment { comment } => NodeTy::Comment { comment: comment.clone() },
Node::Pass => NodeTy::Pass
}
}
}
12 changes: 8 additions & 4 deletions src/check/constrain/constraint/builder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::check::constrain::constraint::Constraint;
use crate::check::constrain::constraint::{Constraint, ConstrVariant};
use crate::check::constrain::constraint::expected::Expected;
use crate::check::constrain::constraint::iterator::Constraints;
use crate::check::name::string_name::StringName;
Expand All @@ -14,7 +14,7 @@ use crate::common::position::Position;
///
/// The level indicates how deep we are. A level of 0 indicates that we are at
/// the top-level of a script.
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct ConstrBuilder {
pub level: usize,
finished: Vec<(Vec<StringName>, Vec<Constraint>)>,
Expand All @@ -29,7 +29,7 @@ impl ConstrBuilder {
pub fn is_top_level(&self) -> bool { self.level == 0 }

pub fn new_set_in_class(&mut self, inherit_class: bool, class: &StringName) {
self.new_set(false);
self.new_set(true);
if self.level > 0 && inherit_class {
let mut previous = self.constraints[self.level - 1].0.clone();
self.constraints[self.level].0.append(&mut previous);
Expand Down Expand Up @@ -73,8 +73,12 @@ impl ConstrBuilder {
self.add_constr(&Constraint::new(msg, parent, child));
}

pub fn add_var(&mut self, msg: &str, parent: &Expected, child: &Expected, var: ConstrVariant) {
self.add_constr(&Constraint::new_variant(msg, parent, child, var));
}

pub fn add_constr(&mut self, constraint: &Constraint) {
trace!("Constr: {} == {}, {}: {}", constraint.left.pos, constraint.right.pos, self.level, constraint);
trace!("Constr[{}]: {} == {}, {}: {}", self.level, constraint.left.pos, constraint.right.pos, constraint.msg, constraint);
self.constraints[self.level].1.push(constraint.clone())
}

Expand Down
57 changes: 34 additions & 23 deletions src/check/constrain/constraint/expected.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ pub enum Expect {
Expression { ast: AST },
Collection { ty: Box<Expected> },
Tuple { elements: Vec<Expected> },
Raises { name: Name },
Function { name: StringName, args: Vec<Expected> },
Field { name: String },
Access { entity: Box<Expected>, name: Box<Expected> },
Expand Down Expand Up @@ -106,7 +105,6 @@ impl TryFrom<(&AST, &HashMap<String, String>)> for Expect {
Node::Bool { .. } => Type { name: Name::from(BOOL) },
Node::Str { .. } => Type { name: Name::from(STRING) },
Node::Undefined => Expect::none(),
Node::Raise { error } => Raises { name: Name::try_from(error)? },
_ => Expression { ast },
})
}
Expand All @@ -130,7 +128,6 @@ impl Display for Expect {
let elements: Vec<Expected> = elements.iter().map(|a| a.and_or_a(false)).collect();
write!(f, "({})", comma_delm(elements))
}
Raises { name: ty } => write!(f, "Raises {ty}"),
Access { entity, name } => write!(f, "{}.{}", entity.and_or_a(false), name.and_or_a(false)),
Function { name, args } => {
let args: Vec<Expected> = args.iter().map(|a| a.and_or_a(false)).collect();
Expand All @@ -151,12 +148,11 @@ impl Expect {
match (self, other) {
(Collection { ty: l }, Collection { ty: r }) => l.expect.same_value(&r.expect),
(Field { name: l }, Field { name: r }) => l == r,
(Raises { name: l }, Raises { name: r }) | (Type { name: l }, Type { name: r }) => {
l == r
}
(Access { entity: le, name: ln }, Access { entity: re, name: rn }) => {
le == re && ln == rn
}
(Type { name: l }, Type { name: r }) => l == r,

(Function { name: l, args: la }, Function { name: r, args: ra }) => {
l == r
&& la.iter().zip_longest(ra.iter()).all(|pair| {
Expand All @@ -170,23 +166,6 @@ impl Expect {

(Expression { ast: l }, Expression { ast: r }) => l.same_value(r),

(Type { name: ty, .. }, Expression { ast: AST { node: Node::Str { .. }, .. } })
| (Expression { ast: AST { node: Node::Str { .. }, .. } }, Type { name: ty, .. })
if ty == &Name::from(STRING) => {
true
}
(Type { name: ty, .. }, Expression { ast: AST { node: Node::Real { .. }, .. } })
| (Expression { ast: AST { node: Node::Real { .. }, .. } }, Type { name: ty, .. })
if ty == &Name::from(FLOAT) => {
true
}
(Type { name: ty, .. }, Expression { ast: AST { node: Node::Int { .. }, .. } })
| (Expression { ast: AST { node: Node::Int { .. }, .. } }, Type { name: ty, .. })
if ty == &Name::from(INT) =>
{
true
}

_ => self.is_none() && other.is_none(),
}
}
Expand All @@ -202,3 +181,35 @@ impl Expect {
}
}
}

#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::convert::TryFrom;

use crate::check::constrain::constraint::expected::Expect;
use crate::common::position::{CaretPos, Position};
use crate::parse::ast::{AST, Node};
use crate::parse::parse_direct;

#[test]
fn test_expected_from_int_constructor_call() {
let ast = parse_direct("Int(10)").unwrap();
let mappings = HashMap::new();
let expect = Expect::try_from((&ast[0], &mappings)).unwrap();

assert_eq!(expect, Expect::Expression {
ast: AST::new(
Position::new(CaretPos::new(1, 1), CaretPos::new(1, 8)),
Node::FunctionCall {
name: Box::new(AST::new(
Position::new(CaretPos::new(1, 1), CaretPos::new(1, 4)),
Node::Id { lit: String::from("Int") })),
args: vec![AST::new(
Position::new(CaretPos::new(1, 5), CaretPos::new(1, 7)),
Node::Int { lit: String::from("10") })],
},
)
})
}
}
10 changes: 2 additions & 8 deletions src/check/constrain/constraint/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@ impl Constraints {
self.constraints.push_front(constraint)
}

/// Append in_class and constraints of constraints to self
pub fn append(&mut self, constraints: &mut Constraints) {
self.in_class.append(&mut constraints.in_class);
self.constraints.append(&mut constraints.constraints);
}

pub fn push_constr(&mut self, constr: &Constraint) {
self.constraints.push_back(constr.clone())
}
Expand All @@ -50,8 +44,8 @@ impl Constraints {
if constraint.is_flag {
// Can only reinsert constraint once
let msg = format!(
"Cannot infer type. Expected a {}, was {}",
&constraint.left.expect, &constraint.right.expect
"Cannot infer type within {}. Expected a {}, was {}",
constraint.msg, &constraint.left.expect, &constraint.right.expect
);
return Err(vec![TypeErr::new(constraint.left.pos, &msg)]);
}
Expand Down
Loading