wip: working on the compiler.
This commit is contained in:
parent
e60cf9c4dd
commit
2efe1a3cf1
61 changed files with 799 additions and 665 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -35,14 +35,25 @@ dependencies = [
|
|||
"outrun-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "outrun-common"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "outrun-compiler"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"case",
|
||||
"outrun-common",
|
||||
"outrun-lexer",
|
||||
"outrun-parser",
|
||||
"outrun-vm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "outrun-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"outrun-compiler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -68,6 +79,7 @@ dependencies = [
|
|||
name = "outrun-vm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"outrun-common",
|
||||
"outrun-lexer",
|
||||
"outrun-parser",
|
||||
]
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
[workspace]
|
||||
|
||||
members = [
|
||||
"outrun-common",
|
||||
"outrun-compiler",
|
||||
"outrun-core",
|
||||
"outrun-lexer",
|
||||
"outrun-parser",
|
||||
"outrun-vm",
|
||||
|
|
11
outrun-common/Cargo.toml
Normal file
11
outrun-common/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
authors = ["James Harton <james@harton.nz>"]
|
||||
description = "🌅 common data structures for the most retro-futuristic toy language in the world."
|
||||
edition = "2021"
|
||||
license-file = "../LICENSE.md"
|
||||
name = "outrun-common"
|
||||
version = "0.1.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
32
outrun-common/src/indexed_vec.rs
Normal file
32
outrun-common/src/indexed_vec.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IndexedVec<T: Debug>(Vec<T>);
|
||||
|
||||
impl<T: Debug> IndexedVec<T> {
|
||||
pub fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, value: T) -> IndexedVecKey<T> {
|
||||
let key = IndexedVecKey(self.0.len(), PhantomData);
|
||||
self.0.push(value);
|
||||
key
|
||||
}
|
||||
|
||||
pub fn into_vec(self) -> Vec<T> {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, key: IndexedVecKey<T>) -> Option<&mut T> {
|
||||
self.0.get_mut(key.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct IndexedVecKey<T: Debug>(usize, PhantomData<T>);
|
3
outrun-common/src/lib.rs
Normal file
3
outrun-common/src/lib.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod indexed_vec;
|
||||
pub mod stack;
|
||||
pub mod unique_vec;
|
69
outrun-common/src/stack.rs
Normal file
69
outrun-common/src/stack.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use std::default::Default;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StackError {
|
||||
Overflow,
|
||||
Underflow,
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, StackError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Stack<T>(Vec<T>);
|
||||
|
||||
impl<T> Stack<T> {
|
||||
pub fn new() -> Stack<T> {
|
||||
Stack(Vec::new())
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: T) -> Result<usize> {
|
||||
self.0.push(value);
|
||||
Ok(self.0.len())
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Result<T> {
|
||||
self.0.pop().ok_or(StackError::Underflow)
|
||||
}
|
||||
|
||||
pub fn peek(&self) -> Option<&T> {
|
||||
self.0.last()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Stack<T> {
|
||||
fn default() -> Self {
|
||||
Stack::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LimitedStack<T>(usize, Stack<T>);
|
||||
|
||||
impl<T> LimitedStack<T> {
|
||||
pub fn new(limit: usize) -> LimitedStack<T> {
|
||||
LimitedStack(limit, Stack::new())
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: T) -> Result<usize> {
|
||||
let len = self.1 .0.len();
|
||||
if len >= self.0 {
|
||||
return Err(StackError::Overflow);
|
||||
}
|
||||
|
||||
self.1.push(value)
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Result<T> {
|
||||
self.1.pop()
|
||||
}
|
||||
|
||||
pub fn peek(&self) -> Option<&T> {
|
||||
self.1 .0.last()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for LimitedStack<T> {
|
||||
fn default() -> Self {
|
||||
LimitedStack::new(4096)
|
||||
}
|
||||
}
|
92
outrun-common/src/unique_vec.rs
Normal file
92
outrun-common/src/unique_vec.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
use std::fmt::Debug;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub struct UniqueVec<T: Debug> {
|
||||
data: Vec<T>,
|
||||
cmp: fn(&T, &T) -> bool,
|
||||
}
|
||||
|
||||
impl<T: Debug> UniqueVec<T> {
|
||||
pub fn new(cmp: fn(&T, &T) -> bool) -> Self {
|
||||
Self {
|
||||
data: Vec::new(),
|
||||
cmp,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, value: T) -> UniqueVecKey<T> {
|
||||
let cmp = self.cmp;
|
||||
if let Some(idx) = self.data.iter().position(|x| cmp(x, &value)) {
|
||||
return UniqueVecKey(idx, PhantomData);
|
||||
}
|
||||
let idx = self.data.len();
|
||||
self.data.push(value);
|
||||
UniqueVecKey(idx, PhantomData)
|
||||
}
|
||||
|
||||
pub fn get(&self, key: UniqueVecKey<T>) -> Option<&T> {
|
||||
self.data.get(key.0)
|
||||
}
|
||||
|
||||
pub fn find_by<F>(&self, pred: F) -> Option<UniqueVecKey<T>>
|
||||
where
|
||||
F: Fn(&T) -> bool,
|
||||
{
|
||||
self.data
|
||||
.iter()
|
||||
.position(pred)
|
||||
.map(|i| UniqueVecKey(i, PhantomData))
|
||||
}
|
||||
|
||||
pub fn replace(&mut self, key: UniqueVecKey<T>, value: T) {
|
||||
let elem = self.data.get_mut(key.0).unwrap();
|
||||
*elem = value;
|
||||
}
|
||||
|
||||
pub fn into_vec(self) -> Vec<T> {
|
||||
self.data
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for UniqueVec<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("UniqueVec")
|
||||
.field("data", &self.data)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug + PartialEq> Default for UniqueVec<T> {
|
||||
fn default() -> Self {
|
||||
UniqueVec {
|
||||
data: Vec::new(),
|
||||
cmp: PartialEq::eq,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, Clone, Copy)]
|
||||
pub struct UniqueVecKey<T: Debug>(usize, PhantomData<T>);
|
||||
|
||||
impl<T: Debug> PartialEq for UniqueVecKey<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Hash for UniqueVecKey<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> From<UniqueVecKey<T>> for usize {
|
||||
fn from(key: UniqueVecKey<T>) -> Self {
|
||||
key.0
|
||||
}
|
||||
}
|
|
@ -10,6 +10,6 @@ version = "0.1.0"
|
|||
|
||||
[dependencies]
|
||||
case = "1.0.0"
|
||||
outrun-common = {path = "../outrun-common"}
|
||||
outrun-lexer = {path = "../outrun-lexer"}
|
||||
outrun-parser = {path = "../outrun-parser"}
|
||||
outrun-vm = {path = "../outrun-vm"}
|
||||
|
|
14
outrun-compiler/README.md
Normal file
14
outrun-compiler/README.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
[![Hippocratic License HL3-FULL](https://img.shields.io/static/v1?label=Hippocratic%20License&message=HL3-FULL&labelColor=5e2751&color=bc8c3d)](https://firstdonoharm.dev/version/3/0/full.html)
|
||||
[![Issues](https://img.shields.io/gitlab/issues/open/jimsy/outrun)](https://gitlab.com/jimsy/outrun/-/issues)
|
||||
|
||||
# 🌅 Outrun Lexer
|
||||
|
||||
This crate is the compiler for the Outrun programming language.
|
||||
|
||||
It is deliberately simple as we're still in the bootstrapping phase of development.
|
||||
|
||||
# License
|
||||
|
||||
Outrun Lexer is distributed under the terms of the the Hippocratic Version 3.0 Full License.
|
||||
|
||||
See [LICENSE.md](https://gitlab.com/jimsy/outrun/-/blob/main/outrun-lexer/LICENSE.md) for details.
|
20
outrun-compiler/src/block.rs
Normal file
20
outrun-compiler/src/block.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
//! Block
|
||||
//!
|
||||
//! Holds an instruction sequence, including the arity and types of any values.
|
||||
|
||||
use crate::instructions::Instruction;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Block {
|
||||
code: Vec<Instruction>,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn new() -> Self {
|
||||
Block { code: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn push_instruction(&mut self, instruction: Instruction) {
|
||||
self.code.push(instruction);
|
||||
}
|
||||
}
|
43
outrun-compiler/src/builder.rs
Normal file
43
outrun-compiler/src/builder.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use crate::instructions::Instruction;
|
||||
use crate::module::{Module, StringKey, TypeKey};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Builder<'a> {
|
||||
module: &'a mut Module,
|
||||
}
|
||||
|
||||
impl<'a> Builder<'a> {
|
||||
pub fn new(module: &'a mut Module) -> Builder<'a> {
|
||||
Builder { module }
|
||||
}
|
||||
|
||||
pub fn push_atom(&mut self, value: StringKey) {
|
||||
self.push_instruction(Instruction::PushAtom(value.into()))
|
||||
}
|
||||
|
||||
pub fn push_boolean(&mut self, value: bool) {
|
||||
self.push_instruction(Instruction::PushBoolean(value));
|
||||
}
|
||||
|
||||
pub fn push_constant(&mut self, value: TypeKey) {
|
||||
self.push_instruction(Instruction::PushConstant(value.into()));
|
||||
}
|
||||
|
||||
pub fn push_float(&mut self, value: f64) {
|
||||
self.push_instruction(Instruction::PushFloat(value))
|
||||
}
|
||||
|
||||
pub fn push_integer(&mut self, value: i64) {
|
||||
self.push_instruction(Instruction::PushInteger(value));
|
||||
}
|
||||
|
||||
pub fn push_string<T: ToString>(&mut self, value: T) {
|
||||
let value = self.module.strings.insert(value.to_string());
|
||||
self.push_instruction(Instruction::PushString(value.into()));
|
||||
}
|
||||
|
||||
fn push_instruction(&mut self, inst: Instruction) {
|
||||
let block = self.module.current_block_mut();
|
||||
block.push_instruction(inst);
|
||||
}
|
||||
}
|
9
outrun-compiler/src/instructions.rs
Normal file
9
outrun-compiler/src/instructions.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub enum Instruction {
|
||||
PushAtom(usize),
|
||||
PushBoolean(bool),
|
||||
PushConstant(usize),
|
||||
PushFloat(f64),
|
||||
PushInteger(i64),
|
||||
PushString(usize),
|
||||
}
|
|
@ -1,25 +1,19 @@
|
|||
extern crate outrun_common;
|
||||
extern crate outrun_lexer;
|
||||
extern crate outrun_parser;
|
||||
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use outrun_parser::{Node, NodeKind, NodeValue};
|
||||
use std::path::Path;
|
||||
|
||||
mod block;
|
||||
mod builder;
|
||||
mod error;
|
||||
mod instructions;
|
||||
mod module;
|
||||
|
||||
use builder::Builder;
|
||||
pub use error::Error;
|
||||
|
||||
pub fn init() -> Result<(), Error> {
|
||||
let path = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let path = path.parent().map(|p| p.join("lib")).unwrap();
|
||||
let path = path.as_path();
|
||||
|
||||
let files = collect_files(path, Vec::new())?;
|
||||
|
||||
for path in files.iter() {
|
||||
compile_file(path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub use module::Module;
|
||||
|
||||
pub fn compile_file(path: &Path) -> Result<(), Error> {
|
||||
println!("*** Compiling file: {}", path.to_str().unwrap());
|
||||
|
@ -27,36 +21,42 @@ pub fn compile_file(path: &Path) -> Result<(), Error> {
|
|||
.map_err(|e| Error::from((path, e)))
|
||||
.and_then(|tokens| outrun_parser::parse(&tokens).map_err(|e| Error::from((path, e))))?;
|
||||
// println!("nodes: {:#?}", nodes);
|
||||
|
||||
let mut module = Module::new(Some(path.to_str().unwrap()));
|
||||
for node in nodes {
|
||||
visit(node, &mut module);
|
||||
}
|
||||
println!("MODULE: {:?}", module);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_files(path: &Path, mut files: Vec<PathBuf>) -> Result<Vec<PathBuf>, Error> {
|
||||
let entries = fs::read_dir(path).map_err(|e| Error::from((path, e)))?;
|
||||
for entry in entries {
|
||||
let entry = entry.map_err(|e| Error::from((path, e)))?;
|
||||
let path = entry.path();
|
||||
let path = path.as_path();
|
||||
if path.is_dir() {
|
||||
files = collect_files(&path, files)?;
|
||||
} else if let Some(extension) = path.extension() {
|
||||
if extension == "run" {
|
||||
files.push(path.to_owned());
|
||||
}
|
||||
fn visit(node: Node, module: &mut Module) {
|
||||
match node.kind {
|
||||
NodeKind::Atom => {
|
||||
let atom = module.strings.insert(node.value.as_string().unwrap());
|
||||
Builder::new(module).push_atom(atom);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_init() {
|
||||
let result = init().unwrap();
|
||||
println!("result: {:?}", result);
|
||||
|
||||
assert!(false);
|
||||
NodeKind::Boolean => {
|
||||
let value = node.value.as_bool().unwrap();
|
||||
Builder::new(module).push_boolean(value);
|
||||
}
|
||||
NodeKind::Constant => {
|
||||
let value = module.reference_type(node.value.as_string().unwrap());
|
||||
Builder::new(module).push_constant(value);
|
||||
}
|
||||
NodeKind::Float => {
|
||||
let value = node.value.as_float().unwrap();
|
||||
Builder::new(module).push_float(value);
|
||||
}
|
||||
NodeKind::Integer => {
|
||||
let value = node.value.as_integer().unwrap();
|
||||
Builder::new(module).push_integer(value);
|
||||
}
|
||||
NodeKind::String => {
|
||||
let value = node.value.as_string().unwrap();
|
||||
Builder::new(module).push_string(value);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
220
outrun-compiler/src/module.rs
Normal file
220
outrun-compiler/src/module.rs
Normal file
|
@ -0,0 +1,220 @@
|
|||
use crate::block::Block;
|
||||
use outrun_common::indexed_vec::{IndexedVec, IndexedVecKey};
|
||||
use outrun_common::stack::Stack;
|
||||
use outrun_common::unique_vec::{UniqueVec, UniqueVecKey};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub type TypeKey = UniqueVecKey<Type>;
|
||||
pub type StringKey = UniqueVecKey<String>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Module {
|
||||
pub strings: UniqueVec<String>,
|
||||
location: Option<StringKey>,
|
||||
blocks: IndexedVec<Block>,
|
||||
functions: HashMap<StringKey, Function>,
|
||||
current_block: Stack<IndexedVecKey<Block>>,
|
||||
current_function: Stack<StringKey>,
|
||||
types: UniqueVec<Type>,
|
||||
}
|
||||
|
||||
impl Module {
|
||||
pub fn new<T: ToString>(location: Option<T>) -> Self {
|
||||
let mut strings = UniqueVec::default();
|
||||
|
||||
let location = location.map(|s| strings.insert(s.to_string()));
|
||||
let blocks = IndexedVec::new();
|
||||
let functions = HashMap::new();
|
||||
let current_block = Stack::new();
|
||||
let current_function = Stack::new();
|
||||
let types = UniqueVec::new(|l: &Type, r: &Type| l.name() == r.name());
|
||||
|
||||
Module {
|
||||
strings,
|
||||
location,
|
||||
blocks,
|
||||
functions,
|
||||
current_block,
|
||||
current_function,
|
||||
types,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_function<T: ToString>(
|
||||
&mut self,
|
||||
name: T,
|
||||
arguments: &[(StringKey, TypeKey)],
|
||||
return_type: TypeKey,
|
||||
) {
|
||||
let name = self.strings.insert(name.to_string());
|
||||
let block = Block::new();
|
||||
let block_id = self.blocks.insert(block);
|
||||
let arity = arguments.len();
|
||||
let argument_names = arguments.iter().map(|arg| arg.0.clone()).collect();
|
||||
let argument_types = arguments.iter().map(|arg| arg.1.clone()).collect();
|
||||
self.current_block.push(block_id.clone()).unwrap();
|
||||
let function = Function {
|
||||
name: Some(name.clone()),
|
||||
arity,
|
||||
argument_names,
|
||||
argument_types,
|
||||
entry_block: block_id,
|
||||
return_type,
|
||||
};
|
||||
self.functions.insert(name, function);
|
||||
}
|
||||
|
||||
pub fn pop_function(&mut self) -> Option<StringKey> {
|
||||
self.current_function.pop().ok()
|
||||
}
|
||||
|
||||
pub fn push_block(&mut self) -> IndexedVecKey<Block> {
|
||||
let block = Block::new();
|
||||
let block_id = self.blocks.insert(block);
|
||||
self.current_block.push(block_id.clone()).unwrap();
|
||||
block_id
|
||||
}
|
||||
|
||||
pub fn current_block_mut(&mut self) -> &mut Block {
|
||||
let block_id = self.current_block.peek().unwrap();
|
||||
self.blocks.get_mut(block_id.clone()).unwrap()
|
||||
}
|
||||
|
||||
pub fn integer_type(&mut self) -> TypeKey {
|
||||
let name = self.strings.insert("Outrun.Core.Integer".to_string());
|
||||
self.types.insert(Type::Integer(name))
|
||||
}
|
||||
|
||||
pub fn float_type(&mut self) -> TypeKey {
|
||||
let name = self.strings.insert("Outrun.Core.Float".to_string());
|
||||
self.types.insert(Type::Float(name))
|
||||
}
|
||||
|
||||
pub fn boolean_type(&mut self) -> TypeKey {
|
||||
let name = self.strings.insert("Outrun.Core.Boolean".to_string());
|
||||
self.types.insert(Type::Boolean(name))
|
||||
}
|
||||
|
||||
pub fn new_protocol_type<T: ToString>(
|
||||
&mut self,
|
||||
name: Option<T>,
|
||||
requirements: Vec<TypeKey>,
|
||||
) -> TypeKey {
|
||||
let name = name
|
||||
.map(|s| s.to_string())
|
||||
.or_else(|| {
|
||||
Some(
|
||||
requirements
|
||||
.iter()
|
||||
.map(|k| self.name_of(k.clone()))
|
||||
.collect::<Vec<&str>>()
|
||||
.join("+"),
|
||||
)
|
||||
})
|
||||
.map(|s| self.strings.insert(s))
|
||||
.unwrap();
|
||||
|
||||
let new_type = Type::Protocol {
|
||||
name: name.clone(),
|
||||
requirements,
|
||||
};
|
||||
|
||||
if let Some(key) = self.types.find_by(|t| t.name() == name) {
|
||||
let r#type = self.types.get(key.clone()).unwrap();
|
||||
if r#type.is_reference() {
|
||||
self.types.replace(key.clone(), new_type);
|
||||
return key;
|
||||
}
|
||||
panic!("Attempt to overwrite existing type {:#?}", r#type);
|
||||
}
|
||||
|
||||
self.types.insert(new_type)
|
||||
}
|
||||
|
||||
pub fn new_struct_type<T: ToString>(&mut self, name: T, fields: Vec<(T, TypeKey)>) -> TypeKey {
|
||||
let name = self.strings.insert(name.to_string());
|
||||
let (field_names, field_types) = fields.iter().fold(
|
||||
(Vec::new(), Vec::new()),
|
||||
|(mut field_names, mut field_types), (name, r#type)| {
|
||||
field_names.push(self.strings.insert(name.to_string()));
|
||||
field_types.push(r#type.clone());
|
||||
(field_names, field_types)
|
||||
},
|
||||
);
|
||||
|
||||
let new_type = Type::Struct {
|
||||
name: name.clone(),
|
||||
field_names,
|
||||
field_types,
|
||||
};
|
||||
|
||||
if let Some(key) = self.types.find_by(|t| t.name() == name) {
|
||||
let r#type = self.types.get(key.clone()).unwrap();
|
||||
if r#type.is_reference() {
|
||||
self.types.replace(key.clone(), new_type);
|
||||
return key;
|
||||
}
|
||||
panic!("Attempt to overwrite existing type {:#?}", r#type);
|
||||
}
|
||||
|
||||
self.types.insert(new_type)
|
||||
}
|
||||
|
||||
pub fn reference_type<T: ToString>(&mut self, name: T) -> TypeKey {
|
||||
let name = self.strings.insert(name.to_string());
|
||||
self.types
|
||||
.find_by(|t| t.name() == name)
|
||||
.unwrap_or_else(|| self.types.insert(Type::Reference(name)))
|
||||
}
|
||||
|
||||
fn name_of(&self, typekey: TypeKey) -> &str {
|
||||
self.types
|
||||
.get(typekey)
|
||||
.and_then(|t| self.strings.get(t.name()))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Function {
|
||||
name: Option<StringKey>,
|
||||
arity: usize,
|
||||
entry_block: IndexedVecKey<Block>,
|
||||
argument_names: Vec<StringKey>,
|
||||
argument_types: Vec<TypeKey>,
|
||||
return_type: TypeKey,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Type {
|
||||
Struct {
|
||||
name: StringKey,
|
||||
field_names: Vec<StringKey>,
|
||||
field_types: Vec<TypeKey>,
|
||||
},
|
||||
Protocol {
|
||||
name: StringKey,
|
||||
requirements: Vec<TypeKey>,
|
||||
},
|
||||
Integer(StringKey),
|
||||
Boolean(StringKey),
|
||||
Float(StringKey),
|
||||
Reference(StringKey),
|
||||
}
|
||||
|
||||
impl Type {
|
||||
fn name(&self) -> StringKey {
|
||||
match self {
|
||||
Type::Struct { name, .. } => name.clone(),
|
||||
Type::Protocol { name, .. } => name.clone(),
|
||||
Type::Integer(name) => name.clone(),
|
||||
Type::Boolean(name) => name.clone(),
|
||||
Type::Float(name) => name.clone(),
|
||||
Type::Reference(name) => name.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_reference(&self) -> bool {
|
||||
matches!(self, Type::Reference(_))
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
use outrun_lexer::Span;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NoticeSeverity {
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NoticeKind {
|
||||
AtomCase,
|
||||
ConstantCase,
|
||||
ExprInModule,
|
||||
DuplicateKeyword,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Notice {
|
||||
pub severity: NoticeSeverity,
|
||||
pub error: NoticeKind,
|
||||
pub message: String,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl Notice {
|
||||
pub fn new<T: ToString, U: Into<Span>>(
|
||||
severity: NoticeSeverity,
|
||||
error: NoticeKind,
|
||||
message: T,
|
||||
span: U,
|
||||
) -> Self {
|
||||
Notice {
|
||||
severity,
|
||||
error,
|
||||
message: message.to_string(),
|
||||
span: span.into(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,401 +0,0 @@
|
|||
extern crate outrun_lexer;
|
||||
extern crate outrun_parser;
|
||||
extern crate outrun_vm;
|
||||
|
||||
mod notice;
|
||||
mod ty;
|
||||
mod unique_vec;
|
||||
|
||||
use crate::notice::{Notice, NoticeKind, NoticeSeverity};
|
||||
use crate::ty::Type;
|
||||
use case::CaseExt;
|
||||
use outrun_lexer::{Span, TokenKind};
|
||||
use outrun_parser::{Node, NodeKind, NodeValue};
|
||||
use outrun_vm::{Constant, Instruction, OpCode, Stack};
|
||||
use std::collections::HashMap;
|
||||
use unique_vec::UniqueVec;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
enum Scope {
|
||||
#[default]
|
||||
Module,
|
||||
Protocol(usize),
|
||||
Struct(usize),
|
||||
Function(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Compiler {
|
||||
data: UniqueVec<Constant>,
|
||||
code: Vec<Instruction>,
|
||||
spans: Vec<Span>,
|
||||
notices: Vec<Notice>,
|
||||
types: HashMap<usize, Type>,
|
||||
current_definition: Stack<Scope>,
|
||||
}
|
||||
|
||||
impl Compiler {
|
||||
pub fn new() -> Self {
|
||||
Compiler {
|
||||
data: UniqueVec::new(),
|
||||
code: Vec::new(),
|
||||
spans: Vec::new(),
|
||||
notices: Vec::new(),
|
||||
types: HashMap::new(),
|
||||
current_definition: Stack::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile(&mut self, node: Node) {
|
||||
do_compile(&node, self);
|
||||
}
|
||||
|
||||
pub fn info<T: ToString, U: Into<Span>>(&mut self, kind: NoticeKind, message: T, span: U) {
|
||||
self.notices.push(Notice::new(
|
||||
NoticeSeverity::Info,
|
||||
kind,
|
||||
message.to_string(),
|
||||
span.into(),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn warning<T: ToString, U: Into<Span>>(&mut self, kind: NoticeKind, message: T, span: U) {
|
||||
self.notices.push(Notice::new(
|
||||
NoticeSeverity::Warning,
|
||||
kind,
|
||||
message.to_string(),
|
||||
span.into(),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn error<T: ToString, U: Into<Span>>(&mut self, kind: NoticeKind, message: T, span: U) {
|
||||
self.notices.push(Notice::new(
|
||||
NoticeSeverity::Error,
|
||||
kind,
|
||||
message.to_string(),
|
||||
span.into(),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn push_code<T: Into<Span>>(&mut self, inst: Instruction, span: T) -> usize {
|
||||
let idx = self.code.len();
|
||||
self.code.push(inst);
|
||||
self.spans.push(span.into());
|
||||
idx
|
||||
}
|
||||
|
||||
pub fn push_data<T: Into<Constant>>(&mut self, value: T) -> usize {
|
||||
self.data.push(value.into())
|
||||
}
|
||||
|
||||
fn has_no_errors(&self) -> bool {
|
||||
self.notices
|
||||
.iter()
|
||||
.any(|n| matches!(n.severity, NoticeSeverity::Error))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_camel_case(s: &str) -> bool {
|
||||
s.to_camel() == s
|
||||
}
|
||||
|
||||
fn is_snake_case(s: &str) -> bool {
|
||||
s.to_snake() == s
|
||||
}
|
||||
|
||||
fn do_compile(node: &Node, compiler: &mut Compiler) {
|
||||
match node.kind {
|
||||
NodeKind::Atom => do_compile_atom(node, compiler),
|
||||
NodeKind::Block => do_compile_block(node, compiler),
|
||||
NodeKind::Boolean => do_compile_boolean(node, compiler),
|
||||
NodeKind::Call => do_compile_call(node, compiler),
|
||||
NodeKind::Constant => do_compile_constant(node, compiler),
|
||||
NodeKind::Float => do_compile_float(node, compiler),
|
||||
NodeKind::GetLocal => do_compile_get_local(node, compiler),
|
||||
NodeKind::Infix => do_compile_infix(node, compiler),
|
||||
NodeKind::Integer => do_compile_integer(node, compiler),
|
||||
NodeKind::KeywordPair => do_compile_keyword_pair(node, compiler),
|
||||
NodeKind::List => do_compile_list(node, compiler),
|
||||
NodeKind::Map => do_compile_map(node, compiler),
|
||||
NodeKind::Prefix => do_compile_prefix(node, compiler),
|
||||
NodeKind::SetLocal => do_compile_set_local(node, compiler),
|
||||
NodeKind::Definition => do_compile_definition(node, compiler),
|
||||
NodeKind::String => do_compile_string(node, compiler),
|
||||
NodeKind::Module => do_compile_module(node, compiler),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_compile_atom(node: &Node, compiler: &mut Compiler) {
|
||||
let name = node.value.as_str().unwrap();
|
||||
if !is_snake_case(name) {
|
||||
compiler.info(
|
||||
NoticeKind::AtomCase,
|
||||
format!(":{} should be snake case", name),
|
||||
&node,
|
||||
);
|
||||
}
|
||||
let idx = compiler.push_data(name);
|
||||
let atom = compiler.push_data(Constant::Atom(idx));
|
||||
compiler.push_code(Instruction::new_with_arg(OpCode::PushAtom, atom), node);
|
||||
}
|
||||
|
||||
fn do_compile_block(node: &Node, compiler: &mut Compiler) {}
|
||||
|
||||
fn do_compile_boolean(node: &Node, compiler: &mut Compiler) {
|
||||
let value = node.value.as_bool().unwrap();
|
||||
let op = if value {
|
||||
OpCode::PushTrue
|
||||
} else {
|
||||
OpCode::PushFalse
|
||||
};
|
||||
compiler.push_code(Instruction::new(op), node);
|
||||
}
|
||||
|
||||
fn do_compile_call(node: &Node, compiler: &mut Compiler) {}
|
||||
|
||||
fn do_compile_constant(node: &Node, compiler: &mut Compiler) {
|
||||
let name = node.value.as_strings().unwrap().join(".");
|
||||
if !is_camel_case(&name) {
|
||||
compiler.info(
|
||||
NoticeKind::ConstantCase,
|
||||
format!("{} should be camel case", name),
|
||||
&node,
|
||||
);
|
||||
}
|
||||
let idx = compiler.push_data(name);
|
||||
let constant = compiler.push_data(Constant::Identifier(idx));
|
||||
compiler.push_code(
|
||||
Instruction::new_with_arg(OpCode::PushConstant, constant),
|
||||
node,
|
||||
);
|
||||
}
|
||||
|
||||
fn do_compile_float(node: &Node, compiler: &mut Compiler) {
|
||||
let float = node.value.as_float().unwrap();
|
||||
let idx = compiler.push_data(float);
|
||||
compiler.push_code(Instruction::new_with_arg(OpCode::PushFloat, idx), node);
|
||||
}
|
||||
|
||||
fn do_compile_get_local(node: &Node, compiler: &mut Compiler) {
|
||||
let name = node.value.as_string().unwrap();
|
||||
let idx = compiler.push_data(name);
|
||||
let ident = compiler.push_data(Constant::Identifier(idx));
|
||||
compiler.push_code(Instruction::new_with_arg(OpCode::Load, ident), node);
|
||||
}
|
||||
|
||||
fn do_compile_infix(node: &Node, compiler: &mut Compiler) {
|
||||
match node.value {
|
||||
NodeValue::Infix(ref lhs, ref op, ref rhs) => {
|
||||
do_compile(lhs, compiler);
|
||||
do_compile(rhs, compiler);
|
||||
let inst = infix_operator(*op, compiler);
|
||||
compiler.push_code(inst, node);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_compile_integer(node: &Node, compiler: &mut Compiler) {
|
||||
let integer = node.value.as_integer().unwrap();
|
||||
let idx = compiler.push_data(integer);
|
||||
compiler.push_code(Instruction::new_with_arg(OpCode::PushInteger, idx), node);
|
||||
}
|
||||
|
||||
fn do_compile_keyword_pair(node: &Node, compiler: &mut Compiler) {}
|
||||
|
||||
fn do_compile_list(node: &Node, compiler: &mut Compiler) {
|
||||
let nodes = node.value.as_nodes().unwrap();
|
||||
|
||||
for node in nodes {
|
||||
do_compile(node, compiler);
|
||||
}
|
||||
|
||||
compiler.push_code(
|
||||
Instruction::new_with_arg(OpCode::PushList, nodes.len()),
|
||||
node,
|
||||
);
|
||||
}
|
||||
|
||||
fn do_compile_map(node: &Node, compiler: &mut Compiler) {
|
||||
let nodes = node.value.as_nodes().unwrap();
|
||||
for node in nodes {
|
||||
do_compile(node, compiler);
|
||||
}
|
||||
|
||||
compiler.push_code(
|
||||
Instruction::new_with_arg(OpCode::PushMap, nodes.len()),
|
||||
node,
|
||||
);
|
||||
}
|
||||
|
||||
fn infix_operator(token_kind: TokenKind, _: &mut Compiler) -> Instruction {
|
||||
match token_kind {
|
||||
TokenKind::And => Instruction::new(OpCode::BitwiseAnd),
|
||||
TokenKind::AndAnd => Instruction::new(OpCode::LogicalAnd),
|
||||
TokenKind::BangEq => Instruction::new(OpCode::NotEqual),
|
||||
TokenKind::Dot => Instruction::new(OpCode::Index),
|
||||
TokenKind::Eq => Instruction::new(OpCode::Assign),
|
||||
TokenKind::EqEq => Instruction::new(OpCode::Equal),
|
||||
TokenKind::ForwardSlash => Instruction::new(OpCode::Divide),
|
||||
TokenKind::Gt => Instruction::new(OpCode::Greater),
|
||||
TokenKind::GtEq => Instruction::new(OpCode::GreaterOrEqual),
|
||||
TokenKind::GtGt => Instruction::new(OpCode::BitwiseShiftRight),
|
||||
TokenKind::Lt => Instruction::new(OpCode::Less),
|
||||
TokenKind::LtEq => Instruction::new(OpCode::LessOrEqual),
|
||||
TokenKind::LtLt => Instruction::new(OpCode::BitwiseShiftLeft),
|
||||
TokenKind::Minus => Instruction::new(OpCode::Subtract),
|
||||
TokenKind::Percent => Instruction::new(OpCode::Modulo),
|
||||
TokenKind::Pipe => Instruction::new(OpCode::BitwiseOr),
|
||||
TokenKind::PipePipe => Instruction::new(OpCode::LogicalOr),
|
||||
TokenKind::Plus => Instruction::new(OpCode::Add),
|
||||
TokenKind::Star => Instruction::new(OpCode::Multiply),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_compile_prefix(node: &Node, compiler: &mut Compiler) {
|
||||
match node.value {
|
||||
NodeValue::Prefix(TokenKind::Bang, ref operand) => {
|
||||
do_compile(operand.as_ref(), compiler);
|
||||
compiler.push_code(Instruction::new(OpCode::UnaryNot), node);
|
||||
}
|
||||
NodeValue::Prefix(TokenKind::Minus, ref operand) => {
|
||||
do_compile(operand.as_ref(), compiler);
|
||||
compiler.push_code(Instruction::new(OpCode::UnaryMinus), node);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_compile_set_local(node: &Node, compiler: &mut Compiler) {
|
||||
match node.value {
|
||||
NodeValue::SetLocal(ref name, ref value) => {
|
||||
let idx = compiler.push_data(name);
|
||||
compiler.push_code(Instruction::new_with_arg(OpCode::Store, idx), node);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_keywords(fields: &Vec<Node>, compiler: &mut Compiler) -> HashMap<String, Node> {
|
||||
let mut result = HashMap::new();
|
||||
|
||||
for pair in fields {
|
||||
match &pair.value {
|
||||
NodeValue::KeywordPair(key, value) => {
|
||||
let name = key.value.as_string().unwrap();
|
||||
let value = value.as_ref().clone();
|
||||
|
||||
if result.contains_key(&name) {
|
||||
compiler.warning(
|
||||
NoticeKind::DuplicateKeyword,
|
||||
format!("Ignoring duplicate keyword: {}", name),
|
||||
key.as_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
result.insert(name, value);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn define_public_function(node: &Node, compiler: &mut Compiler) {}
|
||||
|
||||
fn define_private_function(node: &Node, compiler: &mut Compiler) {}
|
||||
|
||||
fn define_anonymous_function(node: &Node, compiler: &mut Compiler) {}
|
||||
|
||||
fn define_impl(node: &Node, compiler: &mut Compiler) {}
|
||||
|
||||
fn define_let(node: &Node, compiler: &mut Compiler) {}
|
||||
|
||||
fn define_protocol(node: &Node, compiler: &mut Compiler) {}
|
||||
|
||||
fn define_struct(node: &Node, compiler: &mut Compiler) {
|
||||
match node.value {
|
||||
NodeValue::Definition {
|
||||
ref name,
|
||||
ref fields,
|
||||
ref arguments,
|
||||
..
|
||||
} => {
|
||||
let name = name.value.as_strings().unwrap().join(".");
|
||||
let idx = compiler.push_data(name);
|
||||
let fields = collect_keywords(fields, compiler);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn define_use(node: &Node, compiler: &mut Compiler) {}
|
||||
|
||||
fn do_compile_definition(node: &Node, compiler: &mut Compiler) {
|
||||
match node.value.as_definition_kind().unwrap() {
|
||||
TokenKind::KeywordDef => define_public_function(node, compiler),
|
||||
TokenKind::KeywordDefPrivate => define_private_function(node, compiler),
|
||||
TokenKind::KeywordFn => define_anonymous_function(node, compiler),
|
||||
TokenKind::KeywordImpl => define_impl(node, compiler),
|
||||
TokenKind::KeywordLet => define_let(node, compiler),
|
||||
TokenKind::KeywordProtocol => define_protocol(node, compiler),
|
||||
TokenKind::KeywordStruct => define_struct(node, compiler),
|
||||
TokenKind::KeywordUse => define_use(node, compiler),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_compile_string(node: &Node, compiler: &mut Compiler) {
|
||||
let value = node.value.as_string().unwrap();
|
||||
let idx = compiler.push_data(value);
|
||||
compiler.push_code(Instruction::new_with_arg(OpCode::PushString, idx), node);
|
||||
}
|
||||
|
||||
fn do_compile_module(node: &Node, compiler: &mut Compiler) {
|
||||
if node.is_module() {
|
||||
compiler.current_definition.push(Scope::Module).unwrap();
|
||||
let nodes = node.value.as_nodes().unwrap();
|
||||
for node in nodes {
|
||||
if !node.is_definition() {
|
||||
compiler.notices.push(Notice::new(
|
||||
NoticeSeverity::Error,
|
||||
NoticeKind::ExprInModule,
|
||||
"Expected statement, found expression",
|
||||
node,
|
||||
))
|
||||
}
|
||||
do_compile(node, compiler);
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use outrun_lexer::{Scanner, Token};
|
||||
use outrun_parser::Parser;
|
||||
|
||||
fn expr(input: &str) -> Node {
|
||||
let tokens = Scanner::new(input).into_iter().collect::<Vec<Token>>();
|
||||
let parser = Parser::new(&tokens);
|
||||
let (_, ast) = parser.parse_expr().unwrap();
|
||||
ast
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_atom() {
|
||||
// let mut module = Module::default();
|
||||
// let node = expr(":scandroid");
|
||||
|
||||
// do_compile_atom(node, &mut module);
|
||||
|
||||
// println!("{:#?}", module);
|
||||
|
||||
// assert!(false);
|
||||
// }
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
#[derive(Debug)]
|
||||
pub struct Type {
|
||||
pub name: Option<String>,
|
||||
pub kind: TypeKind,
|
||||
pub value: TypeValue,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TypeKind {
|
||||
Boolean,
|
||||
Integer,
|
||||
Float,
|
||||
String,
|
||||
List,
|
||||
Map,
|
||||
Struct,
|
||||
Protocol,
|
||||
Function,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TypeValue {
|
||||
None,
|
||||
Protocol,
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct UniqueVec<T: PartialEq>(Vec<T>);
|
||||
|
||||
impl<T: PartialEq> UniqueVec<T> {
|
||||
pub fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: T) -> usize {
|
||||
if let Some(idx) = self.0.iter().position(|element| element == &value) {
|
||||
return idx;
|
||||
}
|
||||
|
||||
let idx = self.0.len();
|
||||
self.0.push(value);
|
||||
idx
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> Default for UniqueVec<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
12
outrun-core/Cargo.toml
Normal file
12
outrun-core/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
authors = ["James Harton <james@harton.nz>"]
|
||||
description = "🌅 The core library for the most retro-futuristic toy language in the world."
|
||||
edition = "2021"
|
||||
license-file = "../LICENSE.md"
|
||||
name = "outrun-core"
|
||||
version = "0.1.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
outrun-compiler = {path = "../outrun-compiler"}
|
14
outrun-core/README.md
Normal file
14
outrun-core/README.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
[![Hippocratic License HL3-FULL](https://img.shields.io/static/v1?label=Hippocratic%20License&message=HL3-FULL&labelColor=5e2751&color=bc8c3d)](https://firstdonoharm.dev/version/3/0/full.html)
|
||||
[![Issues](https://img.shields.io/gitlab/issues/open/jimsy/outrun)](https://gitlab.com/jimsy/outrun/-/issues)
|
||||
|
||||
# 🌅 Outrun Lexer
|
||||
|
||||
This crate provides the core library for Outrun.
|
||||
|
||||
It is provided as a crate so that it can contain pre-compiled Outrun assets.
|
||||
|
||||
# License
|
||||
|
||||
Outrun Lexer is distributed under the terms of the the Hippocratic Version 3.0 Full License.
|
||||
|
||||
See [LICENSE.md](https://gitlab.com/jimsy/outrun/-/blob/main/outrun-lexer/LICENSE.md) for details.
|
9
outrun-core/lib/abs.run
Normal file
9
outrun-core/lib/abs.run
Normal file
|
@ -0,0 +1,9 @@
|
|||
```doc
|
||||
# Absolute value
|
||||
```
|
||||
protocol AbsoluteValue, as: do
|
||||
```doc
|
||||
Return the absolute value (or magnitude) of `self`.
|
||||
```
|
||||
def abs(self: Self): Self
|
||||
end
|
19
outrun-core/lib/float.run
Normal file
19
outrun-core/lib/float.run
Normal file
|
@ -0,0 +1,19 @@
|
|||
```doc
|
||||
# Float
|
||||
|
||||
This protocol defines all the protocols and functions required for a value to masquerade as a floating point number.
|
||||
```
|
||||
protocol Float,
|
||||
requires:
|
||||
AbsoluteValue + Addition + Division + Equality + Greater + GreaterOrEqual +
|
||||
Less + LessOrEqual + Multiplication + Negation,
|
||||
as: do
|
||||
def nan?(self: Self): Boolean
|
||||
def infinite?(self: Self): Boolean
|
||||
def finite?(self: Self): Boolean
|
||||
def subnormal?(self: Self): Boolean
|
||||
def normal?(self: Self): Boolean
|
||||
def sign_positive?(self: Self): Boolean
|
||||
def sign_negative?(self: Self): Boolean
|
||||
def inverse(self: Self): Option
|
||||
end
|
|
@ -1,22 +1,16 @@
|
|||
```doc
|
||||
# Integer
|
||||
|
||||
This protocol defines all the protocols required for a value to masquerade as an integer.
|
||||
This protocol defines all the protocols and functions required for a value to masquerade as an integer.
|
||||
|
||||
For the concrete implementation see `Outrun.Core.Integer`.
|
||||
```
|
||||
protocol Integer,
|
||||
requires:
|
||||
Addition + BitwiseAnd + BitwiseOr + BitwiseXor + Division + Equality +
|
||||
Exponentiation + GreaterOrEqual + Greater + LessOrEqual + Less + Modulus +
|
||||
Multiplication + ShiftLeft + ShiftRight + Subtraction,
|
||||
AbsoluteValue + Addition + BitwiseAnd + BitwiseOr + BitwiseXor + Division +
|
||||
Equality + Exponentiation + GreaterOrEqual + Greater + LessOrEqual + Less +
|
||||
Modulus + Multiplication + Negation + ShiftLeft + ShiftRight + Subtraction,
|
||||
as: do
|
||||
|
||||
```doc
|
||||
Computes the absolute value of `self`.
|
||||
```
|
||||
def abs(self: Self): Option
|
||||
|
||||
```doc
|
||||
Returns whether the value of `self` is greater than zero.
|
||||
```
|
11
outrun-core/lib/negation.run
Normal file
11
outrun-core/lib/negation.run
Normal file
|
@ -0,0 +1,11 @@
|
|||
```doc
|
||||
# Negation
|
||||
|
||||
Perform a unary negation on the value. Used by the unary `-` operator.
|
||||
```
|
||||
protocol Negation, as: do
|
||||
```doc
|
||||
Return the negative value of `self`.
|
||||
```
|
||||
def neg(self: Self): Self
|
||||
end
|
65
outrun-core/lib/outrun/core/float.run
Normal file
65
outrun-core/lib/outrun/core/float.run
Normal file
|
@ -0,0 +1,65 @@
|
|||
```doc
|
||||
# Outrun.Core.Float
|
||||
|
||||
The native implementation of `Float` based around Rust's `f64` type.
|
||||
|
||||
Has no constructor as it is created internally in the virtual machine by literals.
|
||||
```
|
||||
struct Outrun.Core.Float, as: do
|
||||
def nan?(self: Self): Boolean, as: Outrun.NIF.float_is_nan(self)
|
||||
def infinite?(self: Self): Boolean, as: Outrun.NIF.float_is_infinite(self)
|
||||
def finite?(self: Self): Boolean, as: Outrun.NIF.float_is_finite(self)
|
||||
def subnormal?(self: Self): Boolean, as: Outrun.NIF.float_is_subnormal(self)
|
||||
def normal?(self: Self): Boolean, as: Outrun.NIF.float_is_normal(self)
|
||||
def sign_positive?(self: Self): Boolean, as: Outrun.NIF.float_is_sign_positive(self)
|
||||
def sign_negative?(self: Self): Boolean, as: Outrun.NIF.float_is_sign_negative(self)
|
||||
def inverse(self: Self): Option, as: Outrun.NIF.float_inverse(self)
|
||||
end
|
||||
|
||||
impl AbsoluteValue, for: Outrun.Core.Float, as: do
|
||||
def abs(self: Self): Option, as: Outrun.NIF.float_abs(self)
|
||||
end
|
||||
|
||||
impl Addition, for: Outrun.Core.Float, as: do
|
||||
def add(self: Self, other: Self): Option, as: Outrun.NIF.float_add(self, other)
|
||||
def add(_self: Self, _other: Any): Option, as: Option.none()
|
||||
end
|
||||
|
||||
impl Division, for: Outrun.Core.Float, as: do
|
||||
def divide(self: Self, other: Self): Option, as: Outrun.NIF.float_div(self, other)
|
||||
def divide(_self: Self, _other: Any): Option, as: Option.none()
|
||||
end
|
||||
|
||||
impl Equality, for: Outrun.Core.Float, as: do
|
||||
def equal?(self: Self, other: Self): Boolean, as: Outrun.NIF.float_is_eq(self, other)
|
||||
def equal?(_self: Self, _other: Self): Boolean, as: false
|
||||
end
|
||||
|
||||
impl Greater, for: Outrun.Core.Float, as: do
|
||||
def greater?(self: Self, other: Self): Boolean, as: Outrun.NIF.float_gt(self, other)
|
||||
def greater?(_self: Self, _other: Self): Boolean, as: false
|
||||
end
|
||||
|
||||
impl GreaterOrEqual, for: Outrun.Core.Float, as: do
|
||||
def greater_or_equal?(self: Self, other: Self): Boolean, as: Outrun.NIF.float_gte(self, other)
|
||||
def greater_or_equal?(_self: Self, _other: Self): Boolean, as: false
|
||||
end
|
||||
|
||||
impl Less, for: Outrun.Core.Float, as: do
|
||||
def less?(self: Self, other: Self): Boolean, as: Outrun.NIF.float_lt(self, other)
|
||||
def less?(_self: Self, _other: Self): Boolean, as: false
|
||||
end
|
||||
|
||||
impl LessOrEqual, for: Outrun.Core.Float, as: do
|
||||
def less_or_equal?(self: Self, other: Self): Boolean, as: Outrun.NIF.float_lte(self, other)
|
||||
def less_or_equal?(_self: Self, _other: Self): Boolean, as: false
|
||||
end
|
||||
|
||||
impl Multiplication, for: Outrun.Core.Float, as: do
|
||||
def multiply(self: Self, other: Self): Option, as: Outrun.NIF.float_mul(self, other)
|
||||
def multiply(_self: Self, _other: Any): Option, as: Option.none()
|
||||
end
|
||||
|
||||
impl Negation, for: Outrun.Core.Float, as: do
|
||||
def neg(self: Self): Option, as: Outrun.NIF.float_neg(self)
|
||||
end
|
|
@ -29,116 +29,82 @@ struct Outrun.Core.Integer, as: do
|
|||
def negative?(self: Self): Self, as: Outrun.NIF.integer_lt(self, 0)
|
||||
end
|
||||
|
||||
impl Addition, for: Outrun.Core.Integer, as: do
|
||||
def add(self: Self, other: Self): Option, as: do
|
||||
Outrun.NIF.integer_add(self, other)
|
||||
end
|
||||
impl AbsoluteValue, for: Outrun.Core.Integer, as: do
|
||||
def abs(self: Self): Self, as: Outrun.NIF.integer_abs(self)
|
||||
end
|
||||
|
||||
impl Addition, for: Outrun.Core.Integer, as: do
|
||||
def add(self: Self, other: Self): Option, as: Outrun.NIF.integer_add(self, other)
|
||||
def add(_self: Self, _other: Any): Option, as: Option.none()
|
||||
end
|
||||
|
||||
impl BitwiseAnd, for: Outrun.Core.Integer, as: do
|
||||
def and(self: Self, other: Self): Option, as: do
|
||||
Outrun.NIF.integer_band(self, other)
|
||||
end
|
||||
|
||||
def and(self: Self, other: Self): Option, as: Outrun.NIF.integer_band(self, other)
|
||||
def and(_self: Self, _other: Any): Option, as: Option.none()
|
||||
end
|
||||
|
||||
impl BitwiseOr, for: Outrun.Core.Integer, as: do
|
||||
def or(self: Self, other: Self): Option, as: do
|
||||
Outrun.NIF.integer_bor(self, other)
|
||||
end
|
||||
|
||||
def or(self: Self, other: Self): Option, as: Outrun.NIF.integer_bor(self, other)
|
||||
def or(_self: Self, _other: Any): Option, as: Option.none()
|
||||
end
|
||||
|
||||
impl BitwiseXor, for: Outrun.Core.Integer, as: do
|
||||
def xor(self: Self, other: Self): Option, as: do
|
||||
Outrun.NIF.integer_bxor(self, other)
|
||||
end
|
||||
|
||||
def xor(self: Self, other: Self): Option, as: Outrun.NIF.integer_bxor(self, other)
|
||||
def xor(_self: Self, _other: Any): Option, as: Option.none()
|
||||
end
|
||||
|
||||
impl Division, for: Outrun.Core.Integer, as: do
|
||||
def divide(self: Self, other: Self): Option, as: do
|
||||
Outrun.NIF.integer_div(self, other)
|
||||
end
|
||||
|
||||
def divide(self: Self, other: Self): Option, as: Outrun.NIF.integer_div(self, other)
|
||||
def divide(_self: Self, _other: Any): Option, as: Option.none()
|
||||
end
|
||||
|
||||
impl Equality, for: Outrun.Core.Integer, as: do
|
||||
def equal?(self: Self, other: Self): Boolean, as: do
|
||||
Outrun.NIF.integer_eq(self, other)
|
||||
end
|
||||
|
||||
def equal?(self: Self, other: Self): Boolean, as: Outrun.NIF.integer_eq(self, other)
|
||||
def equal?(_self: Self, _other: Any): Boolean, as: false
|
||||
end
|
||||
|
||||
impl Exponentiation, for: Outrun.Core.Integer, as: do
|
||||
def raise(self: Self, other: Self): Option, as: do
|
||||
Outrun.NIF.integer_expo(self, other)
|
||||
end
|
||||
|
||||
def raise(self: Self, other: Self): Option, as: Outrun.NIF.integer_expo(self, other)
|
||||
def raise(_self: Self, _other: Any): Option, as: Option.none()
|
||||
end
|
||||
|
||||
impl GreaterOrEqual, for: Outrun.Core.Integer, as: do
|
||||
def greater_or_equal?(self: Self, other: Self): Boolean, as: do
|
||||
Outrun.NIF.integer_gte(self, other)
|
||||
end
|
||||
|
||||
def greater_or_equal?(self: Self, other: Self): Boolean, as: Outrun.NIF.integer_gte(self, other)
|
||||
def greater_or_equal?(_self: Self, _other: Any): Boolean, as: false
|
||||
end
|
||||
|
||||
impl Greater, for: Outrun.Core.Integer, as: do
|
||||
def greater?(self: Self, other: Self): Boolean, as: do
|
||||
Outrun.NIF.integer_gt(self, other)
|
||||
end
|
||||
|
||||
def greater?(self: Self, other: Self): Boolean, as: Outrun.NIF.integer_gt(self, other)
|
||||
def greater?(_self: Self, _other: Any): Boolean, as: false
|
||||
end
|
||||
|
||||
impl Integer, for: Outrun.Core.Integer
|
||||
|
||||
impl LessOrEqual, for: Outrun.Core.Integer, as: do
|
||||
def less_or_equal?(self: Self, other: Self): Boolean, as: do
|
||||
Outrun.NIF.integer_lte(self, other)
|
||||
end
|
||||
|
||||
def less_or_equal?(self: Self, other: Self): Boolean, as: Outrun.NIF.integer_lte(self, other)
|
||||
def less_or_equal?(_self: Self, _other: Self): Boolean, as: false
|
||||
end
|
||||
|
||||
impl Less, for: Outrun.Core.Integer, as: do
|
||||
def less?(self: Self, other: Self): Boolean, as: do
|
||||
Outrun.NIF.integer_lt(self, other)
|
||||
end
|
||||
|
||||
def less?(self: Self, other: Self): Boolean, as: Outrun.NIF.integer_lt(self, other)
|
||||
def less?(_self: Self, _other: Self): Boolean, as: false
|
||||
end
|
||||
|
||||
impl Modulus, for: Outrun.Core.Integer, as: do
|
||||
def modulo(self: Self, other: Self): Option, as: do
|
||||
Outrun.NIF.integer_mod(self, other)
|
||||
end
|
||||
|
||||
def modulo(self: Self, other: Self): Option, as: Outrun.NIF.integer_mod(self, other)
|
||||
def modulo(_self: Self, _other: Any): Option, as: Option.none()
|
||||
end
|
||||
|
||||
impl Multiplication, for: Outrun.Core.Integer, as: do
|
||||
def multiply(self: Self, other: Self): Option, as: do
|
||||
Outrun.NIF.integer_mul(self, other)
|
||||
end
|
||||
|
||||
def multiply(self: Self, other: Self): Option, as: Outrun.NIF.integer_mul(self, other)
|
||||
def multiply(_self: Self, _other: Any): Option, as: Option.none()
|
||||
end
|
||||
|
||||
impl Subtraction, for: Outrun.Core.Integer, as: do
|
||||
def subtract(self: Self, other: Self): Option, as: do
|
||||
Outrun.NIF.integer_sub(self, other)
|
||||
end
|
||||
impl Negation, for: Outrun.Core.Integer, as: do
|
||||
def neg(self: Self): Option, as: Outrun.NIF.integer_neg(self)
|
||||
end
|
||||
|
||||
impl Subtraction, for: Outrun.Core.Integer, as: do
|
||||
def subtract(self: Self, other: Self): Option, as: Outrun.NIF.integer_sub(self, other)
|
||||
def subtract(_self: Self, _other: Any): Option, as: Option.none()
|
||||
end
|
49
outrun-core/src/lib.rs
Normal file
49
outrun-core/src/lib.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
extern crate outrun_compiler;
|
||||
|
||||
use outrun_compiler::Error;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn init() -> Result<(), Error> {
|
||||
let path = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let path = path.join("lib");
|
||||
let path = path.as_path();
|
||||
|
||||
let files = collect_files(path, Vec::new())?;
|
||||
|
||||
for path in files.iter() {
|
||||
outrun_compiler::compile_file(path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_files(path: &Path, mut files: Vec<PathBuf>) -> Result<Vec<PathBuf>, Error> {
|
||||
let entries = fs::read_dir(path).map_err(|e| Error::from((path, e)))?;
|
||||
for entry in entries {
|
||||
let entry = entry.map_err(|e| Error::from((path, e)))?;
|
||||
let path = entry.path();
|
||||
let path = path.as_path();
|
||||
if path.is_dir() {
|
||||
files = collect_files(&path, files)?;
|
||||
} else if let Some(extension) = path.extension() {
|
||||
if extension == "run" {
|
||||
files.push(path.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_init() {
|
||||
let result = init().unwrap();
|
||||
println!("result: {:?}", result);
|
||||
assert!(false);
|
||||
}
|
||||
}
|
|
@ -324,7 +324,7 @@ mod test {
|
|||
fn match_constant_test() {
|
||||
let scanner = Scanner::new("DarkAllDay");
|
||||
let (_, token) = match_constant(scanner).unwrap();
|
||||
assert_eq!(token.kind, TokenKind::Identifier);
|
||||
assert_eq!(token.kind, TokenKind::Constant);
|
||||
assert_eq!(token.value, TokenValue::String("DarkAllDay".to_string()));
|
||||
assert_eq!(token.span, Span::new(0, 10));
|
||||
}
|
||||
|
|
|
@ -660,12 +660,13 @@ mod test {
|
|||
let tokens = tokens_for("Celldweller.EndOfAnEmpire");
|
||||
let parser = Parser::new(&tokens);
|
||||
let (_, node) = expr_constant(parser).unwrap();
|
||||
assert_eq!(node.kind, NodeKind::Constant);
|
||||
assert_eq!(
|
||||
node.value.as_string().unwrap(),
|
||||
"Celldweller",
|
||||
"EndOfAnEmpire"
|
||||
);
|
||||
|
||||
assert_matches!(node.kind, NodeKind::Infix);
|
||||
assert_matches!(node.value, NodeValue::Infix(ref lhs, ref op, ref rhs) => {
|
||||
assert_matches!(op, TokenKind::Dot);
|
||||
assert_eq!(lhs.value.as_string().unwrap(), "Celldweller");
|
||||
assert_eq!(rhs.value.as_string().unwrap(), "EndOfAnEmpire");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -988,12 +989,17 @@ mod test {
|
|||
let parser = Parser::new(&tokens);
|
||||
let (_, node) = expr(parser).unwrap();
|
||||
|
||||
assert_matches!(node.kind, NodeKind::Call);
|
||||
assert_matches!(node.value, NodeValue::Call { ref arguments } => {
|
||||
assert_eq!(arguments.len(), 3);
|
||||
assert_is_integer(&arguments[0], 1);
|
||||
assert_is_integer(&arguments[1], 2);
|
||||
assert_is_integer(&arguments[2], 3);
|
||||
assert_matches!(node.kind, NodeKind::Infix);
|
||||
assert_matches!(node.value, NodeValue::Infix(ref lhs, ref op, ref rhs) => {
|
||||
assert_is_get_local(&lhs, "example");
|
||||
assert_matches!(op, TokenKind::LeftParen);
|
||||
assert_matches!(rhs.kind, NodeKind::Call);
|
||||
assert_matches!(rhs.value, NodeValue::Call { ref arguments } => {
|
||||
assert_eq!(arguments.len(), 3);
|
||||
assert_is_integer(&arguments[0], 1);
|
||||
assert_is_integer(&arguments[1], 2);
|
||||
assert_is_integer(&arguments[2], 3);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -6,5 +6,6 @@ version = "0.1.0"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
"outrun-common" = {path = "../outrun-common"}
|
||||
"outrun-lexer" = {path = "../outrun-lexer"}
|
||||
"outrun-parser" = {path = "../outrun-parser"}
|
||||
|
|
|
@ -19,11 +19,9 @@ mod ext;
|
|||
mod inst;
|
||||
mod machine;
|
||||
mod module;
|
||||
mod stack;
|
||||
mod table;
|
||||
|
||||
pub use constant::Constant;
|
||||
pub use env::Environment;
|
||||
pub use inst::{Instruction, OpCode};
|
||||
pub use module::Module;
|
||||
pub use stack::Stack;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::ext::{OutrunType, OutrunValue};
|
||||
use crate::stack::Stack;
|
||||
use outrun_common::stack::LimitedStack;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct Machine<'a> {
|
||||
types: HashMap<&'a str, OutrunType>,
|
||||
stack: Stack<Box<OutrunValue>>,
|
||||
stack: LimitedStack<Box<OutrunValue>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
use std::default::Default;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StackError {
|
||||
Overflow,
|
||||
Underflow,
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, StackError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Stack<T> {
|
||||
stack: Vec<T>,
|
||||
limit: usize,
|
||||
}
|
||||
|
||||
impl<T> Stack<T> {
|
||||
pub fn new(limit: usize) -> Stack<T> {
|
||||
Stack {
|
||||
stack: Vec::with_capacity(limit),
|
||||
limit,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: T) -> Result<()> {
|
||||
if self.stack.len() == self.limit {
|
||||
return Err(StackError::Overflow);
|
||||
}
|
||||
|
||||
self.stack.push(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Result<T> {
|
||||
self.stack.pop().ok_or(StackError::Underflow)
|
||||
}
|
||||
|
||||
pub fn peek(&self) -> Option<&T> {
|
||||
self.stack.last()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Stack<T> {
|
||||
fn default() -> Self {
|
||||
Stack::new(4096)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue