Remove wasm

This commit is contained in:
Garen Tyler 2022-03-26 00:50:35 -06:00
parent 0be64e5eb3
commit 2f1cfdb329
No known key found for this signature in database
GPG Key ID: E3BF83D66394FD92
14 changed files with 282 additions and 979 deletions

112
Cargo.lock generated
View File

@ -2,118 +2,6 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "base64"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "leb128"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a"
[[package]]
name = "nyst"
version = "0.5.2"
[[package]]
name = "pivot"
version = "0.1.0"
dependencies = [
"nyst",
"ron",
"serde",
"wat",
]
[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ron"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8a58080b7bb83b2ea28c3b7a9a994fd5e310330b7c8ca5258d99b98128ecfe4"
dependencies = [
"base64",
"bitflags",
"serde",
]
[[package]]
name = "serde"
version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c1e438504729046a5cfae47f97c30d6d083c7d91d94603efdae3477fc070d4c"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "wast"
version = "28.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c0586061bfacc035034672c8d760802b428ab4c80a92e2a392425c516df9be1"
dependencies = [
"leb128",
]
[[package]]
name = "wat"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d55b5ec4f9d9396fa99abaafa0688597395e57827dffd89731412ae90c9bf"
dependencies = [
"wast",
]

View File

@ -1,14 +1,8 @@
[package]
name = "pivot"
version = "0.1.0"
authors = ["Garen Tyler <garentyler@gmail.com>"]
authors = ["Garen Tyler <garentyler@garen.dev>"]
edition = "2018"
description = "Pivot is a new programming language built with Rust by Garen Tyler. Pivot is currently in the alpha stage of development."
description = "Pivot is a new programming language built with Rust. Pivot is currently in the alpha stage of development."
license = "MIT"
repository = "https://github.com/garentyler/pivot"
[dependencies]
ron = "0.6.2"
serde = "1.0.117"
wat = "1.0.29"
nyst = { path = "../nyst" }
repository = "https://github.com/garentyler/pivot"

View File

@ -1 +0,0 @@
tokens: [String("a"), Plus, String("b")]

29
package-lock.json generated
View File

@ -1,29 +0,0 @@
{
"name": "pivot",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"requires": {
"file-uri-to-path": "1.0.0"
}
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
},
"wasi": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/wasi/-/wasi-0.0.6.tgz",
"integrity": "sha512-D3f4Q/3LojW1uMloOC1OuFlGy2u22hLqpskAz0OkmoYeWIwVYf8XBuc9ihDGJnNf3lfNvqM4s0030sN0UUXExw==",
"requires": {
"bindings": "^1.5.0"
}
}
}
}

View File

@ -1,27 +0,0 @@
{
"name": "pivot",
"version": "0.1.0",
"description": "Pivot is a new programming language built with Rust by Garen Tyler. Pivot is currently in the alpha stage of development.",
"main": "test.js",
"private": true,
"dependencies": {
"wasi": "0.0.6"
},
"devDependencies": {},
"scripts": {
"clean": "cargo clean && rm -rf node_modules out.wasm",
"build": "cargo build",
"run": "cargo run && node test.js",
"test": "cargo test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ElementG9/pivot.git"
},
"author": "Garen Tyler",
"license": "MIT",
"bugs": {
"url": "https://github.com/ElementG9/pivot/issues"
},
"homepage": "https://github.com/ElementG9/pivot#readme"
}

View File

@ -1,195 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum AstNode {
// Primitives
Integer(i32),
Identifier(String),
String(String),
Boolean(bool),
// Unary operators
Not {
operand: Box<AstNode>,
},
// Infix operators
Equal {
left: Box<AstNode>,
right: Box<AstNode>,
},
Add {
left: Box<AstNode>,
right: Box<AstNode>,
},
Subtract {
left: Box<AstNode>,
right: Box<AstNode>,
},
Multiply {
left: Box<AstNode>,
right: Box<AstNode>,
},
Divide {
left: Box<AstNode>,
right: Box<AstNode>,
},
Assign {
left: Box<AstNode>,
right: Box<AstNode>,
},
// Control flow
Block {
statements: Vec<AstNode>,
},
If {
condition: Box<AstNode>,
consequence: Box<AstNode>,
alternative: Option<Box<AstNode>>,
},
While {
condition: Box<AstNode>,
body: Box<AstNode>,
},
// Functions and variables
FunctionCall {
identifier: Box<AstNode>,
arguments: Vec<AstNode>,
},
FunctionReturn {
value: Box<AstNode>,
},
FunctionDefinition {
identifier: Box<AstNode>,
arguments: Vec<AstNode>,
body: Box<AstNode>,
},
VariableDeclaration {
identifier: Box<AstNode>,
},
// Other
Import {
identifier: Box<AstNode>,
},
Null,
}
impl AstNode {
// Primitives
pub fn integer(value: i32) -> AstNode {
AstNode::Integer(value)
}
pub fn identifier(value: String) -> AstNode {
AstNode::Identifier(value)
}
pub fn string(value: String) -> AstNode {
AstNode::String(value)
}
pub fn boolean(value: bool) -> AstNode {
AstNode::Boolean(value)
}
// Unary operators
pub fn not(operand: AstNode) -> AstNode {
AstNode::Not {
operand: Box::new(operand),
}
}
// Infix operators
pub fn equal(left: AstNode, right: AstNode) -> AstNode {
AstNode::Equal {
left: Box::new(left),
right: Box::new(right),
}
}
pub fn not_equal(left: AstNode, right: AstNode) -> AstNode {
AstNode::not(AstNode::Equal {
left: Box::new(left),
right: Box::new(right),
})
}
pub fn add(left: AstNode, right: AstNode) -> AstNode {
AstNode::Add {
left: Box::new(left),
right: Box::new(right),
}
}
pub fn subtract(left: AstNode, right: AstNode) -> AstNode {
AstNode::Subtract {
left: Box::new(left),
right: Box::new(right),
}
}
pub fn multiply(left: AstNode, right: AstNode) -> AstNode {
AstNode::Multiply {
left: Box::new(left),
right: Box::new(right),
}
}
pub fn divide(left: AstNode, right: AstNode) -> AstNode {
AstNode::Divide {
left: Box::new(left),
right: Box::new(right),
}
}
pub fn assign(left: AstNode, right: AstNode) -> AstNode {
AstNode::Assign {
left: Box::new(left),
right: Box::new(right),
}
}
// Control flow
pub fn block(statements: Vec<AstNode>) -> AstNode {
AstNode::Block { statements }
}
pub fn if_statement(
condition: AstNode,
consequence: AstNode,
alternative: Option<AstNode>,
) -> AstNode {
AstNode::If {
condition: Box::new(condition),
consequence: Box::new(consequence),
alternative: alternative.and_then(|alt| Some(Box::new(alt))),
}
}
pub fn while_loop(condition: AstNode, body: AstNode) -> AstNode {
AstNode::While {
condition: Box::new(condition),
body: Box::new(body),
}
}
// Functions and variables
pub fn function_call(identifier: AstNode, arguments: Vec<AstNode>) -> AstNode {
AstNode::FunctionCall {
identifier: Box::new(identifier),
arguments,
}
}
pub fn function_return(value: AstNode) -> AstNode {
AstNode::FunctionReturn {
value: Box::new(value),
}
}
pub fn function_definition(
identifier: AstNode,
arguments: Vec<AstNode>,
body: AstNode,
) -> AstNode {
AstNode::FunctionDefinition {
identifier: Box::new(identifier),
arguments,
body: Box::new(body),
}
}
pub fn variable_declaration(identifier: AstNode) -> AstNode {
AstNode::VariableDeclaration {
identifier: Box::new(identifier),
}
}
// Other
pub fn import(identifier: AstNode) -> AstNode {
AstNode::Import {
identifier: Box::new(identifier),
}
}
pub fn null() -> AstNode {
AstNode::Null
}
}

View File

@ -1,185 +1,5 @@
use crate::ast::{AstNode, AstNodeKind};
use ron::from_str;
use crate::{parse::AstNode, InterpreterError};
pub struct SymbolGenerator {
counter: usize,
}
impl SymbolGenerator {
pub fn new() -> SymbolGenerator {
SymbolGenerator { counter: 0 }
}
pub fn next(&mut self) -> usize {
self.counter += 1;
self.counter
}
}
pub trait Wasm {
fn emit(&self, symbol_generator: &mut SymbolGenerator) -> String;
}
impl Wasm for AstNode {
fn emit(&self, s: &mut SymbolGenerator) -> String {
use AstNodeKind::*;
match self.kind {
// Primitives
Integer => format!("(i32.const {})", self.value),
Identifier => format!("(get_local ${})", self.value),
// Unary operators
Not => format!("(i32.eq (i32.const 0) {})", self.subnodes[1].emit(s)),
// Infix operators
NotEqual => format!(
"(i32.ne {} {})",
self.subnodes[0].emit(s),
self.subnodes[1].emit(s)
),
Equal => format!(
"(i32.eq {} {})",
self.subnodes[0].emit(s),
self.subnodes[1].emit(s)
),
Add => format!(
"(i32.add {} {})",
self.subnodes[0].emit(s),
self.subnodes[1].emit(s)
),
Subtract => format!(
"(i32.sub {} {})",
self.subnodes[0].emit(s),
self.subnodes[1].emit(s)
),
Multiply => format!(
"(i32.mul {} {})",
self.subnodes[0].emit(s),
self.subnodes[1].emit(s)
),
Divide => format!(
"(i32.div_s {} {})",
self.subnodes[0].emit(s),
self.subnodes[1].emit(s)
),
// Control flow
Block => {
let mut out = String::new();
for node in &self.subnodes {
out += "";
out += &node.emit(s);
}
out
}
IfStatement => {
let mut out = String::new();
out += &format!(
"(if {} (then {})",
self.subnodes[0].emit(s),
self.subnodes[1].emit(s)
); // Emit the conditional and consequence.
if let Some(alternative) = self.subnodes.get(2) {
out += &format!(" (else {})", alternative.emit(s)); // Emit the alternative.
}
out += ")";
out
}
WhileLoop => {
let loop_symbol = format!("while{}", s.next()); // TODO: Make generate unique symbol for nested loops.
let mut out = String::new();
out += &format!("(block ${}_wrapper", loop_symbol);
out += &format!(" (loop ${}_loop", loop_symbol);
out += &format!(" {}", self.subnodes[1].emit(s));
out += &format!(
" (br_if ${}_wrapper (i32.eq (i32.const 0) {}))",
loop_symbol,
self.subnodes[0].emit(s)
);
out += &format!(" (br ${}_loop)", loop_symbol);
out += "))";
out
}
Program => {
let mut out = String::new();
out += "(module";
let mut exported = vec![];
for node in &self.subnodes {
out += " ";
out += &node.emit(s);
if node.kind == FunctionDefinition {
exported.push(node.value.clone());
}
}
for export in exported {
out += &format!(" (export \"{0}\" (func ${0}))", export);
}
out += ")";
out
}
// Functions and variables
FunctionCall => {
let mut out = String::new();
out += &format!("(call ${}", from_str::<AstNode>(&self.value).unwrap().value);
for n in &self.subnodes {
out += " ";
out += &n.emit(s);
}
out += ")";
out
}
FunctionReturn => format!("{} (return)", self.subnodes[0].emit(s)),
FunctionDefinition => {
let mut out = String::new();
out += &format!("(func ${}", self.value);
let body = self.subnodes[0].clone();
for n in &self.subnodes[1..] {
out += &format!(" (param ${} i32)", n.value);
}
let mut func_returns_value = false;
let mut index = 0;
loop {
if index >= body.subnodes.len() {
break;
}
match body.subnodes[index].kind {
AstNodeKind::FunctionReturn => func_returns_value = true,
_ => {}
}
index += 1;
}
if func_returns_value {
out += " (result i32)";
}
for n in &body.subnodes {
out += " ";
out += &n.emit(s);
}
out += ")";
out
}
VariableDeclaration => format!("(local ${} i32)", self.value),
Assign => format!("(set_local ${} {})", self.value, self.subnodes[0].emit(s)),
// Import
Import => {
r#"(import "console" "log" (func $log (param i32)))"#.into()
// let mut out = String::new();
// out += "(import";
// let num_args = self.subnodes[0].clone().value.parse::<u32>().unwrap();
// let returns_value = self.subnodes[1].clone().value.parse::<u32>().unwrap() > 0;
// // (import "console" "log" (func $log (param i32)))
// out += &format!(" \"{}\"", self.subnodes[2].value.clone());
// let mut combined_name = self.subnodes[2].value.clone();
// for path in self.subnodes[3..].iter() {
// out += &format!(" \"{}\"", path.value.clone());
// combined_name += &format!("_{}", path.value.clone());
// }
// out += &format!(" (func ${}", combined_name);
// for _ in 0..num_args {
// out += " (param i32)";
// }
// if returns_value {
// out += " (result i32)";
// }
// out += "))";
// out
}
// Blank node / other
Null | _ => "".into(),
}
}
pub fn codegen(_ast: &AstNode) -> Result<String, InterpreterError> {
Ok(String::new())
}

View File

@ -1,19 +1,32 @@
pub mod ast;
// pub mod codegen;
pub mod codegen;
pub mod parse;
pub mod tokenize;
// use codegen::{SymbolGenerator, Wasm};
pub fn compile(source: &str) -> Vec<u8> {
wat::parse_str(compile_wat(source)).unwrap()
#[derive(Debug)]
pub enum InterpreterError {
/// Error parsing source
ParseError(String),
/// Unexpected token
UnexpectedToken,
/// Mismatched types
MismatchedTypes,
/// Type error
TypeError,
/// Unexpected EOF
UnexpectedEOF,
/// Expected value
ExpectedValue,
/// Unimplemented
Unimplemented,
}
impl<T> From<Option<T>> for InterpreterError {
fn from(_value: Option<T>) -> InterpreterError {
InterpreterError::ExpectedValue
}
}
pub fn compile_wat(source: &str) -> String {
// let mut s = SymbolGenerator::new();
let ast = parse::run(source);
// println!("{:?}", ast);
unimplemented!()
// let wasm = ast.emit(&mut s);
// println!("{}", wasm);
// wasm
pub fn compile(source: &str) -> Result<String, InterpreterError> {
let tokens = tokenize::tokenize(source);
let ast = parse::parse(&tokens?);
codegen::codegen(&ast?)
}

View File

@ -1,13 +1,10 @@
use std::fs::File;
use std::io::prelude::*;
use std::{fs::File, io::Write};
fn main() -> std::io::Result<()> {
// Read the source from a file.
let source = std::fs::read_to_string("test.pvt").unwrap();
// Compile it
let _value = pivot::parse::run(&source);
// let binary = pivot::compile(&source);
let code = pivot::compile(&source).unwrap();
// Write it to a file.
// File::create("out.wasm")?.write_all(&binary)?;
File::create("out.bf")?.write_all(code.as_bytes())?;
Ok(())
}

View File

@ -1,190 +1,4 @@
#[derive(Clone, PartialEq, Debug)]
pub enum Token {
Integer(i64),
Float(f64),
String(String),
Boolean(bool),
Identifier(String),
Keyword(String),
Comma,
Plus,
Minus,
Star,
Slash,
Bang,
Equals,
Semicolon,
Quote(char),
Parenthesis { closing: bool },
Whitespace(String),
Unknown,
}
#[derive(Debug)]
pub enum InterpreterError {
/// Error parsing source
ParseError(String),
/// Unexpected token
UnexpectedToken,
/// Mismatched types
MismatchedTypes,
/// Type error
TypeError,
/// Unexpected EOF
UnexpectedEOF,
/// Expected value
ExpectedValue,
}
impl<T> From<Option<T>> for InterpreterError {
fn from(value: Option<T>) -> InterpreterError {
InterpreterError::ExpectedValue
}
}
pub fn tokenize(source: &str) -> Result<Vec<Token>, InterpreterError> {
fn tokenize_number(chars: &[char]) -> Result<(Token, usize), ()> {
let mut current_value = String::new();
let mut chars_consumed = 0;
for c in chars {
if !c.is_digit(10) && *c != '.' {
break;
}
current_value.push(*c);
chars_consumed += 1;
}
if chars_consumed == 0 {
Err(())
} else if current_value.contains(".") {
Ok((
Token::Float(current_value.parse::<f64>().unwrap()),
chars_consumed,
))
} else {
Ok((
Token::Integer(current_value.parse::<i64>().unwrap()),
chars_consumed,
))
}
}
fn tokenize_identifier(chars: &[char]) -> Result<(Token, usize), ()> {
let mut current_value = String::new();
let mut chars_consumed = 0;
if chars[chars_consumed].is_alphabetic() {
current_value.push(chars[chars_consumed]);
} else {
return Err(());
}
chars_consumed += 1;
while chars_consumed < chars.len()
&& (chars[chars_consumed].is_alphanumeric() || chars[chars_consumed] == '_')
{
current_value.push(chars[chars_consumed]);
chars_consumed += 1;
}
match &current_value[..] {
"true" => Ok((Token::Boolean(true), 4)),
"false" => Ok((Token::Boolean(false), 5)),
"let" => Ok((Token::Keyword(current_value), chars_consumed)),
_ => Ok((Token::Identifier(current_value), chars_consumed)),
}
}
fn tokenize_string(chars: &[char]) -> Result<(Token, usize), ()> {
let start_quote;
let mut current_value = String::new();
let mut chars_consumed = 0;
fn is_quote(c: char) -> bool {
match c {
'\'' | '"' | '`' => true,
_ => false,
}
}
if is_quote(chars[chars_consumed]) {
start_quote = chars[chars_consumed];
} else {
return Err(());
}
chars_consumed += 1;
while chars_consumed < chars.len() && chars[chars_consumed] != start_quote {
current_value.push(chars[chars_consumed]);
chars_consumed += 1;
}
chars_consumed += 1;
Ok((Token::String(current_value), chars_consumed))
}
fn tokenize_whitespace(chars: &[char]) -> Result<(Token, usize), ()> {
let mut current_value = String::new();
let mut chars_consumed = 0;
for c in chars {
if !c.is_whitespace() {
break;
}
chars_consumed += 1;
current_value.push(*c);
}
if chars_consumed == 0 {
Err(())
} else {
Ok((Token::Whitespace(current_value), chars_consumed))
}
}
fn tokenize_operator(chars: &[char]) -> Result<(Token, usize), ()> {
if chars.is_empty() {
Err(())
} else if chars[0] == '+' {
Ok((Token::Plus, 1))
} else if chars[0] == '-' {
Ok((Token::Minus, 1))
} else if chars[0] == '*' {
Ok((Token::Star, 1))
} else if chars[0] == '/' {
Ok((Token::Slash, 1))
} else if chars[0] == '!' {
Ok((Token::Bang, 1))
} else if chars[0] == '=' {
Ok((Token::Equals, 1))
} else {
Err(())
}
}
let source = source.chars().collect::<Vec<char>>();
let mut tokens = vec![];
let mut index = 0;
while index < source.len() {
if let Ok((_whitespace, chars_consumed)) = tokenize_whitespace(&source[index..]) {
// Ignore whitespace
index += chars_consumed;
} else if let Ok((num, chars_consumed)) = tokenize_number(&source[index..]) {
tokens.push(num);
index += chars_consumed;
} else if let Ok((num, chars_consumed)) = tokenize_string(&source[index..]) {
tokens.push(num);
index += chars_consumed;
} else if let Ok((num, chars_consumed)) = tokenize_identifier(&source[index..]) {
tokens.push(num);
index += chars_consumed;
} else if let Ok((operator, chars_consumed)) = tokenize_operator(&source[index..]) {
tokens.push(operator);
index += chars_consumed;
} else if source[index] == ',' {
tokens.push(Token::Comma);
index += 1;
} else if source[index] == ';' {
tokens.push(Token::Semicolon);
index += 1;
} else if source[index] == '(' {
tokens.push(Token::Parenthesis { closing: false });
index += 1;
} else if source[index] == ')' {
tokens.push(Token::Parenthesis { closing: true });
index += 1;
} else {
// Skip if things fail
index += 1;
}
}
Ok(tokens)
}
use crate::{tokenize::Token, InterpreterError};
#[derive(Clone, PartialEq, Debug)]
pub enum AstPrimitive {
@ -192,9 +6,9 @@ pub enum AstPrimitive {
Float(f64),
String(String),
Boolean(bool),
Identifier(String),
Null,
}
#[allow(clippy::derive_hash_xor_eq)]
impl std::hash::Hash for AstPrimitive {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
@ -203,10 +17,23 @@ impl std::hash::Hash for AstPrimitive {
}
}
}
impl std::fmt::Display for AstPrimitive {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
use AstPrimitive::*;
match self {
Integer(n) => write!(f, "{}", n),
Float(n) => write!(f, "{}", n),
String(s) => write!(f, "{}", s),
Boolean(b) => write!(f, "{}", b),
Null => write!(f, "null"),
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum AstNode {
Primitive(AstPrimitive),
Identifier(String),
Negate {
body: Box<AstNode>,
},
@ -233,6 +60,10 @@ pub enum AstNode {
left: Box<AstNode>,
right: Box<AstNode>,
},
FunctionCall {
identifier: String,
arguments: Vec<AstNode>,
},
Program {
statements: Vec<AstNode>,
},
@ -240,6 +71,62 @@ pub enum AstNode {
}
pub fn parse(tokens: &[Token]) -> Result<AstNode, InterpreterError> {
fn parse_function_call(tokens: &[Token]) -> Result<(AstNode, usize), InterpreterError> {
let mut index = 0;
let identifier;
if let Token::Identifier(id) = tokens[index].clone() {
identifier = id;
index += 1;
} else {
return Err(InterpreterError::UnexpectedToken);
}
if !matches!(tokens[index], Token::Parenthesis { closing: false }) {
return Ok((AstNode::Identifier(identifier), 1));
} else {
index += 1;
}
// Check if it closes right away.
if matches!(tokens[index], Token::Parenthesis { closing: true }) {
index += 1;
return Ok((
AstNode::FunctionCall {
identifier,
arguments: vec![],
},
index,
));
}
let mut arguments = vec![];
if let Ok((argument, tokens_consumed)) = parse_expression(&tokens[index..]) {
arguments.push(argument);
index += tokens_consumed;
}
while index + 2 < tokens.len() {
if tokens[index] != Token::Comma {
break;
}
index += 1;
if let Ok((argument, tokens_consumed)) = parse_expression(&tokens[index..]) {
arguments.push(argument);
index += tokens_consumed;
} else {
break;
}
}
if matches!(tokens[index], Token::Parenthesis { closing: true }) {
index += 1;
Ok((
AstNode::FunctionCall {
identifier,
arguments,
},
index,
))
} else {
Err(InterpreterError::UnexpectedToken)
}
}
fn parse_primary_expression(tokens: &[Token]) -> Result<(AstNode, usize), InterpreterError> {
if tokens.is_empty() {
Err(InterpreterError::UnexpectedEOF)
@ -251,8 +138,8 @@ pub fn parse(tokens: &[Token]) -> Result<AstNode, InterpreterError> {
Ok((AstNode::Primitive(AstPrimitive::Boolean(*n)), 1))
} else if let Token::String(s) = &tokens[0] {
Ok((AstNode::Primitive(AstPrimitive::String(s.clone())), 1))
} else if let Token::Identifier(s) = &tokens[0] {
Ok((AstNode::Primitive(AstPrimitive::Identifier(s.clone())), 1))
} else if let Token::Identifier(_) = &tokens[0] {
parse_function_call(tokens)
} else if tokens[0] == Token::Keyword("let".to_owned()) {
if tokens.len() < 2 {
Err(InterpreterError::UnexpectedEOF)
@ -294,7 +181,7 @@ pub fn parse(tokens: &[Token]) -> Result<AstNode, InterpreterError> {
fn parse_unary_expression(tokens: &[Token]) -> Result<(AstNode, usize), InterpreterError> {
let mut index = 0;
if tokens[index] != Token::Minus && tokens[index] != Token::Bang {
return parse_grouped_expression(&tokens[index..]);
parse_grouped_expression(&tokens[index..])
} else {
let operation = tokens[index].clone();
index += 1;
@ -396,180 +283,9 @@ pub fn parse(tokens: &[Token]) -> Result<AstNode, InterpreterError> {
if index >= tokens.len() {
break;
}
let statement = parse_expression(&tokens[index..]);
if let Ok((statement, tokens_consumed)) = statement {
statements.push(statement);
index += tokens_consumed;
} else {
break;
}
let (statement, tokens_consumed) = parse_expression(&tokens[index..])?;
statements.push(statement);
index += tokens_consumed;
}
Ok(AstNode::Program { statements })
}
pub fn interpret(ast: &AstNode) -> Result<(), InterpreterError> {
use std::{collections::HashMap, mem::discriminant};
use AstNode::*;
let mut vars: HashMap<String, Option<AstPrimitive>> = HashMap::new();
if let Program { statements } = ast {
for statement in statements {
let _ = interpret_statement(statement, &mut vars)?;
}
}
fn interpret_statement(
ast: &AstNode,
vars: &mut HashMap<String, Option<AstPrimitive>>,
) -> Result<Option<AstPrimitive>, InterpreterError> {
match ast {
Primitive(p) => {
if let AstPrimitive::Identifier(id) = p {
if let Some(val) = vars.get(id) {
if let Some(val) = val {
Ok(Some(val.clone()))
} else {
Err(InterpreterError::ParseError(
"Variable used before definition".to_owned(),
))
}
} else {
Err(InterpreterError::ParseError(
"Variable used before declaration".to_owned(),
))
}
} else {
Ok(Some(p.clone()))
}
}
Negate { body } => {
if let Some(AstPrimitive::Integer(body)) = interpret_statement(body, vars)? {
Ok(Some(AstPrimitive::Integer(body * -1)))
} else if let Some(AstPrimitive::Boolean(body)) = interpret_statement(body, vars)? {
Ok(Some(AstPrimitive::Boolean(!body)))
} else {
Err(InterpreterError::TypeError)
}
}
Declare { identifier } => {
vars.insert(identifier.clone(), None);
Ok(None)
}
Assign { left, right } => {
if let AstNode::Declare { identifier } = Box::leak(left.clone()) {
let _ = interpret(left)?;
let value = interpret_statement(right, vars)?;
vars.insert(identifier.clone(), value);
Ok(Some(vars.get(identifier).unwrap().clone().unwrap().clone()))
} else if let AstNode::Primitive(AstPrimitive::Identifier(id)) =
Box::leak(left.clone())
{
let id = id.clone();
let value = interpret_statement(right, vars)?;
vars.insert(id.clone(), value);
Ok(Some(vars.get(&id).unwrap().clone().unwrap().clone()))
} else {
Err(InterpreterError::TypeError)
}
}
Add { left, right } => {
let left =
interpret_statement(left, vars)?.ok_or(InterpreterError::ExpectedValue)?;
let right =
interpret_statement(right, vars)?.ok_or(InterpreterError::ExpectedValue)?;
if discriminant(&left) != discriminant(&right) {
Err(InterpreterError::MismatchedTypes)
} else {
if let AstPrimitive::Integer(left) = left {
if let AstPrimitive::Integer(right) = right {
return Ok(Some(AstPrimitive::Integer(left + right)));
}
}
if let AstPrimitive::Float(left) = left {
if let AstPrimitive::Float(right) = right {
return Ok(Some(AstPrimitive::Float(left + right)));
}
}
if let AstPrimitive::String(left) = left {
if let AstPrimitive::String(right) = right {
return Ok(Some(AstPrimitive::String(format!("{}{}", left, right))));
}
}
Err(InterpreterError::TypeError)
}
}
Subtract { left, right } => {
let left =
interpret_statement(left, vars)?.ok_or(InterpreterError::ExpectedValue)?;
let right =
interpret_statement(right, vars)?.ok_or(InterpreterError::ExpectedValue)?;
if discriminant(&left) != discriminant(&right) {
Err(InterpreterError::MismatchedTypes)
} else {
if let AstPrimitive::Integer(left) = left {
if let AstPrimitive::Integer(right) = right {
return Ok(Some(AstPrimitive::Integer(left - right)));
}
}
if let AstPrimitive::Float(left) = left {
if let AstPrimitive::Float(right) = right {
return Ok(Some(AstPrimitive::Float(left - right)));
}
}
Err(InterpreterError::TypeError)
}
}
Multiply { left, right } => {
let left =
interpret_statement(left, vars)?.ok_or(InterpreterError::ExpectedValue)?;
let right =
interpret_statement(right, vars)?.ok_or(InterpreterError::ExpectedValue)?;
if discriminant(&left) != discriminant(&right) {
Err(InterpreterError::MismatchedTypes)
} else {
if let AstPrimitive::Integer(left) = left {
if let AstPrimitive::Integer(right) = right {
return Ok(Some(AstPrimitive::Integer(left * right)));
}
}
if let AstPrimitive::Float(left) = left {
if let AstPrimitive::Float(right) = right {
return Ok(Some(AstPrimitive::Float(left * right)));
}
}
Err(InterpreterError::TypeError)
}
}
Divide { left, right } => {
let left =
interpret_statement(left, vars)?.ok_or(InterpreterError::ExpectedValue)?;
let right =
interpret_statement(right, vars)?.ok_or(InterpreterError::ExpectedValue)?;
if discriminant(&left) != discriminant(&right) {
Err(InterpreterError::MismatchedTypes)
} else {
if let AstPrimitive::Integer(left) = left {
if let AstPrimitive::Integer(right) = right {
return Ok(Some(AstPrimitive::Integer(left / right)));
}
}
if let AstPrimitive::Float(left) = left {
if let AstPrimitive::Float(right) = right {
return Ok(Some(AstPrimitive::Float(left / right)));
}
}
Err(InterpreterError::TypeError)
}
}
_ => Err(InterpreterError::TypeError),
}
}
Ok(())
}
pub fn run(source: &str) -> Result<(), InterpreterError> {
println!("source: {:?}", source);
let tokens = tokenize(source);
println!("tokens: {:?}", tokens);
let ast = parse(&tokens?);
println!("ast: {:?}", ast);
let value = interpret(&ast?);
println!("value: {:?}", value);
Ok(())
}

164
src/tokenize.rs Normal file
View File

@ -0,0 +1,164 @@
use crate::InterpreterError;
#[derive(Clone, PartialEq, Debug)]
pub enum Token {
Integer(i64),
Float(f64),
String(String),
Boolean(bool),
Identifier(String),
Keyword(String),
Comma,
Plus,
Minus,
Star,
Slash,
Bang,
Equals,
Semicolon,
Quote(char),
Parenthesis { closing: bool },
Whitespace(String),
Unknown,
}
pub fn tokenize(source: &str) -> Result<Vec<Token>, InterpreterError> {
fn tokenize_number(chars: &[char]) -> Result<(Token, usize), ()> {
let mut current_value = String::new();
let mut chars_consumed = 0;
for c in chars {
if !c.is_digit(10) && *c != '.' {
break;
}
current_value.push(*c);
chars_consumed += 1;
}
if chars_consumed == 0 {
Err(())
} else if current_value.contains('.') {
Ok((
Token::Float(current_value.parse::<f64>().unwrap()),
chars_consumed,
))
} else {
Ok((
Token::Integer(current_value.parse::<i64>().unwrap()),
chars_consumed,
))
}
}
fn tokenize_identifier(chars: &[char]) -> Result<(Token, usize), ()> {
let mut current_value = String::new();
let mut chars_consumed = 0;
if chars[chars_consumed].is_alphabetic() {
current_value.push(chars[chars_consumed]);
} else {
return Err(());
}
chars_consumed += 1;
while chars_consumed < chars.len()
&& (chars[chars_consumed].is_alphanumeric() || chars[chars_consumed] == '_')
{
current_value.push(chars[chars_consumed]);
chars_consumed += 1;
}
match &current_value[..] {
"true" => Ok((Token::Boolean(true), 4)),
"false" => Ok((Token::Boolean(false), 5)),
"let" => Ok((Token::Keyword(current_value), chars_consumed)),
_ => Ok((Token::Identifier(current_value), chars_consumed)),
}
}
fn tokenize_string(chars: &[char]) -> Result<(Token, usize), ()> {
let mut current_value = String::new();
let mut chars_consumed = 0;
fn is_quote(c: char) -> bool {
matches!(c, '\'' | '"' | '`')
}
let start_quote = if is_quote(chars[chars_consumed]) {
chars[chars_consumed]
} else {
return Err(());
};
chars_consumed += 1;
while chars_consumed < chars.len() && chars[chars_consumed] != start_quote {
current_value.push(chars[chars_consumed]);
chars_consumed += 1;
}
chars_consumed += 1;
Ok((Token::String(current_value), chars_consumed))
}
fn tokenize_whitespace(chars: &[char]) -> Result<(Token, usize), ()> {
let mut current_value = String::new();
let mut chars_consumed = 0;
for c in chars {
if !c.is_whitespace() {
break;
}
chars_consumed += 1;
current_value.push(*c);
}
if chars_consumed == 0 {
Err(())
} else {
Ok((Token::Whitespace(current_value), chars_consumed))
}
}
fn tokenize_operator(chars: &[char]) -> Result<(Token, usize), ()> {
if chars.is_empty() {
Err(())
} else if chars[0] == '+' {
Ok((Token::Plus, 1))
} else if chars[0] == '-' {
Ok((Token::Minus, 1))
} else if chars[0] == '*' {
Ok((Token::Star, 1))
} else if chars[0] == '/' {
Ok((Token::Slash, 1))
} else if chars[0] == '!' {
Ok((Token::Bang, 1))
} else if chars[0] == '=' {
Ok((Token::Equals, 1))
} else {
Err(())
}
}
let source = source.chars().collect::<Vec<char>>();
let mut tokens = vec![];
let mut index = 0;
while index < source.len() {
if let Ok((_whitespace, chars_consumed)) = tokenize_whitespace(&source[index..]) {
// Ignore whitespace
index += chars_consumed;
} else if let Ok((num, chars_consumed)) = tokenize_number(&source[index..]) {
tokens.push(num);
index += chars_consumed;
} else if let Ok((num, chars_consumed)) = tokenize_string(&source[index..]) {
tokens.push(num);
index += chars_consumed;
} else if let Ok((num, chars_consumed)) = tokenize_identifier(&source[index..]) {
tokens.push(num);
index += chars_consumed;
} else if let Ok((operator, chars_consumed)) = tokenize_operator(&source[index..]) {
tokens.push(operator);
index += chars_consumed;
} else if source[index] == ',' {
tokens.push(Token::Comma);
index += 1;
} else if source[index] == ';' {
tokens.push(Token::Semicolon);
index += 1;
} else if source[index] == '(' {
tokens.push(Token::Parenthesis { closing: false });
index += 1;
} else if source[index] == ')' {
tokens.push(Token::Parenthesis { closing: true });
index += 1;
} else {
// Skip if things fail
index += 1;
}
}
Ok(tokens)
}

21
test.js
View File

@ -1,21 +0,0 @@
'use strict';
const fs = require('fs');
const WASI = require('wasi');
const wasi = new WASI({
args: process.argv,
env: process.env,
preopens: {
'/sandbox': __dirname
}
});
const importObject = {
wasi_snapshot_preview1: wasi.wasiImport,
console,
};
(async () => {
const wasm = await WebAssembly.compile(fs.readFileSync('./out.wasm'));
const instance = await WebAssembly.instantiate(wasm, importObject);
instance.exports.main();
})();

View File

@ -1,2 +1 @@
let asdf = 4
asdf + 1
log("Hello world!")

View File

@ -1,15 +0,0 @@
PrimaryExpression = Identifier | Literal
GroupedExpression = PrimaryExpression | `(` Expression `)`
UnaryOperator = `-` | `!`
UnaryExpression = GroupedExpression | UnaryOperator UnaryExpression
MultiplyOperator = `*` | `/`
MultiplyExpression = UnaryExpression | MultiplyExpression MultiplyOperator UnaryExpression
AddOperator = `+` | `-`
AddExpression = MultiplyExpression | AddExpression AddOperator MultiplyExpression
AssignExpression = AddExpression | Identifier `=` AddExpression
Expression = AssignExpression