Skip to content

Initial implementation of the Monkey language #1

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

Merged
merged 1 commit into from
Oct 19, 2018
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

.idea/
notes.txt
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "monkey"
version = "0.1.0"
authors = ["Paul Dix <[email protected]>"]
description = "A rust implementation of Thorsten Ball's Monkey programming language."
license = "MIT"

[dependencies]
291 changes: 291 additions & 0 deletions src/ast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
use token;
use std::fmt;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};

#[derive(Debug)]
pub enum Node {
Program(Box<Program>),
Statement(Box<Statement>),
Expression(Box<Expression>),
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub enum Statement {
Let(Box<LetStatement>),
Return(Box<ReturnStatement>),
Expression(Box<ExpressionStatement>),
}

impl fmt::Display for Statement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
Statement::Let(stmt) => format!("{}", stmt),
Statement::Return(ret) => format!("{}", ret),
Statement::Expression(exp) => format!("{}", exp),
};
write!(f, "{}", s)
}
}

#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Expression {
Identifier(String),
Integer(i64),
Prefix(Box<PrefixExpression>),
Infix(Box<InfixExpression>),
Boolean(bool),
String(String),
If(Box<IfExpression>),
Function(Box<FunctionLiteral>),
Call(Box<CallExpression>),
Array(Box<ArrayLiteral>),
Index(Box<IndexExpression>),
Hash(Box<HashLiteral>),
}

impl fmt::Display for Expression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
Expression::Identifier(s) => s.clone(),
Expression::Integer(value) => format!("{}", value),
Expression::Prefix(pref) => pref.to_string(),
Expression::Infix(infix) => infix.to_string(),
Expression::Boolean(b) => b.to_string(),
Expression::String(s) => s.clone(),
Expression::If(exp) => exp.to_string(),
Expression::Function(f) => f.to_string(),
Expression::Call(c) => c.to_string(),
Expression::Array(a) => a.to_string(),
Expression::Index(i) => i.to_string(),
Expression::Hash(h) => h.to_string(),
};
write!(f, "{}", s)
}
}

#[derive(Eq, PartialEq, Clone, Debug)]
pub struct HashLiteral {
pub pairs: HashMap<Expression,Expression>,
}

// Had to implement Hash for this because HashMap doesn't. Doesn't matter what this is because
// a HashLiteral isn't a valid expression as a key in a monkey hash.
impl Hash for HashLiteral {
fn hash<H: Hasher>(&self, _state: &mut H) {
panic!("hash not implemented for HashLiteral");
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this bad form to panic on a method implemented for a trait? This should never get called.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If executing this code should be a bug (the programmer's fault) it's usually recommended to use unreachable!() to signal that this code should never be executed.
(In many cases, this also allows the compiler to do more optimizations, also when using zero-sized types like Never which can never be instantiated.)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

}
}

impl fmt::Display for HashLiteral {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let pairs: Vec<String> = (&self.pairs).into_iter().map(|(k, v)| format!("{}:{}", k.to_string(), v.to_string())).collect();
write!(f, "{{{}}}", pairs.join(", "))
}
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct IfExpression {
pub condition: Expression,
pub consequence: BlockStatement,
pub alternative: Option<BlockStatement>,
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had some confusion about some spots where it seems like you can use Option in lieu of using a Box. Is that true, what's going on here?

Copy link

@Boscop Boscop Oct 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use Option for any type that has a fixed size (that is Sized), e.g. structs, enums. Box is usually used for trait objects (as in Box<dyn Trait> when the trait is "object-safe" (not all traits can be turned into trait objects)) or sometimes boxed slices (Box<[T]> because while a reference to a slice is Sized (it's a fat pointer that also stores the len), a slice itself is not Sized).
Btw, a reference to a trait object is also a fat pointer (pointer to the instance and a pointer to the vtable of its trait impl).
The doc has a detailed explanation of the rules for when a trait is object-safe (sorry, I'm on my phone right now).

Box of course also differs from Option in that it represents a definitely existing T instead of an optional T, and thus automatically dereferences to its content (via the Deref trait).

Btw, I just read your blog post and I'm interested in helping you on your journey of learning Rust and using it in production :)

You mentioned that you're also interested in gRPC and web servers. There are several crates for gRPC (when I tried it there was only one but it was working well) and for Web servers I've been using rocket in production (before actix-web existed) and now also actix-web (because it's faster / async (and it allows serving websockets on the same port)).
There are also some other async web server frameworks like tower-web but they are in early stages.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another +1 on actix/actix-web, I'm surprised at just how solid it is and it seems to be taking over in any place I see such programming styles being used.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Boscop thanks, that's great info. I'll have to read more up on trait objects. Didn't really get a chance to utilize traits in this code, but it's coming up for some future hacking. I'll search through the gRPC crates. I saw in some thread criticism about Rocket because it's not async so I'll probably dig into actix first.

}

impl fmt::Display for IfExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "if {} {}", self.condition, self.consequence)?;

if let Some(ref stmt) = self.alternative {
write!(f, "else {}", stmt)?;
}
Ok(())
}
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct ArrayLiteral {
pub elements: Vec<Expression>,
}

impl fmt::Display for ArrayLiteral {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let elements: Vec<String> = (&self.elements).into_iter().map(|e| e.to_string()).collect();
write!(f, "[{}]", elements.join(", "))
}
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct IndexExpression {
pub left: Expression,
pub index: Expression,
}

impl fmt::Display for IndexExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}[{}])", self.left.to_string(), self.index.to_string())
}
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct BlockStatement {
pub statements: Vec<Statement>,
}

impl fmt::Display for BlockStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for stmt in &self.statements {
write!(f, "{}", stmt)?;
}
Ok(())
}
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct FunctionLiteral {
pub parameters: Vec<IdentifierExpression>,
pub body: BlockStatement,
}

impl fmt::Display for FunctionLiteral {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let param_list: Vec<String> = (&self.parameters).into_iter().map(|p| p.to_string()).collect();
write!(f, "({}) {}", param_list.join(", "), self.body)
}
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct CallExpression {
pub function: Expression,
pub arguments: Vec<Expression>,
}

impl fmt::Display for CallExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let arg_list: Vec<String> = (&self.arguments).into_iter().map(|exp| exp.to_string()).collect();
write!(f, "{}({})", self.function.to_string(), arg_list.join(", "))
}
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct IdentifierExpression {
pub name: String,
}

impl fmt::Display for IdentifierExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.name)
}
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct PrefixExpression {
pub operator: token::Token,
pub right: Expression,
}

impl fmt::Display for PrefixExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}{})", self.operator, self.right)
}
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct InfixExpression {
pub operator: token::Token,
pub left: Expression,
pub right: Expression,
}

impl fmt::Display for InfixExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({} {} {})", self.left, self.operator, self.right)
}
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct IntegerLiteral {
pub value: i64,
}

impl fmt::Display for IntegerLiteral {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.value)
}
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct LetStatement {
pub name: String,
pub value: Expression,
}

impl fmt::Display for LetStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "let {} = {};", self.name, self.value)
}
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct ReturnStatement {
pub value: Expression,
}

impl fmt::Display for ReturnStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "return {};", self.value)
}
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct ExpressionStatement {
pub expression: Expression
}

impl fmt::Display for ExpressionStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.expression)
}
}

#[derive(Debug)]
pub struct Program {
pub statements: Vec<Statement>,
}

impl Program {
pub fn new() -> Program {
Program {
statements: Vec::new(),
}
}
}

impl fmt::Display for Program {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let statements: Vec<String> = (&self.statements).into_iter().map(|stmt| stmt.to_string()).collect();
write!(f, "{}", statements.join(""))
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn display() {

let p = Program{
statements: vec![
Statement::Let(Box::new(
LetStatement{
name: "asdf".to_string(),
value: Expression::Identifier("bar".to_string())}))],
};

let expected = "let asdf = bar;";

if p.to_string() != expected {
panic!("expected {} but got {}", "foo", expected)
}
}
}
Loading