diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 8bd8c08..f8a6939 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -19,7 +19,7 @@ use std::cmp::Eq; use std::cmp::Ordering; use std::cmp::PartialEq; use std::cmp::PartialOrd; -use std::convert::Into; +use std::convert::{Into, TryFrom, TryInto}; use std::fmt; use std::hash::Hash; use std::hash::Hasher; @@ -30,6 +30,7 @@ use abortable_parser; use crate::build::scope::Scope; use crate::build::Val; +use crate::error::BuildError; pub mod printer; pub mod walk; @@ -218,21 +219,58 @@ macro_rules! make_expr { /// This is usually used as the body of a tuple in the UCG AST. pub type FieldList = Vec<(Token, Expression)>; // Token is expected to be a symbol -/// Represents a Value in the UCG parsed AST. -#[derive(Debug, PartialEq, Clone)] -pub enum Value { - // Constant Values - Empty(Position), - Boolean(PositionedItem), - Int(PositionedItem), - Float(PositionedItem), - Str(PositionedItem), - Symbol(PositionedItem), - // Complex Values - Tuple(PositionedItem), - List(ListDef), +pub type ShapeTuple = Vec<(Token, Shape)>; +pub type ShapeList = Vec; + +#[derive(PartialEq, Debug, Clone)] +pub struct FuncShapeDef { + args: Vec, + ret: Box, } +#[derive(PartialEq, Debug, Clone)] +pub struct ModuleShapeDef { + items: ShapeTuple, + ret: Box, +} + +macro_rules! value_enum { + ($doc:meta $i:tt, $t:ty, $l:ty, $($extra:tt)*) => { + #[$doc] + #[derive(PartialEq, Debug, Clone)] + pub enum $i { + // Simple Values + Empty(Position), + Boolean(PositionedItem), + Int(PositionedItem), + Float(PositionedItem), + Str(PositionedItem), + Symbol(PositionedItem), + // Complex Values + Tuple(PositionedItem<$t>), + List($l), + // Extra items + $( $extra )* + } + } +} + +value_enum!( + doc="Value types represent the Values that UCG can have." + Value, + FieldList, + ListDef, +); + +value_enum!( + doc="Shapes represent the types that UCG values or expressions can have." + Shape, + ShapeTuple, + PositionedItem, + Func(FuncShapeDef), + Module(ModuleShapeDef), +); + impl Value { /// Returns the type name of the Value it is called on as a string. pub fn type_name(&self) -> String { @@ -307,6 +345,43 @@ impl Value { &Value::List(_) ) } + + fn derive_shape(&self) -> Result { + let shape = match self { + Value::Empty(p) => Shape::Empty(p.clone()), + Value::Boolean(p) => Shape::Boolean(p.clone()), + Value::Int(p) => Shape::Int(p.clone()), + Value::Float(p) => Shape::Float(p.clone()), + Value::Str(p) => Shape::Str(p.clone()), + // Symbols in a shape are placeholder. They allow a form of genericity + // in the shape. They can be any type and are only refined down. + // by their presence in an expression. + Value::Symbol(p) => Shape::Symbol(p.clone()), + Value::Tuple(flds) => { + let mut field_shapes = Vec::new(); + for &(ref tok, ref expr) in &flds.val { + field_shapes.push((tok.clone(), expr.try_into()?)); + } + Shape::Tuple(PositionedItem::new(field_shapes, flds.pos.clone())) + } + Value::List(flds) => { + let mut field_shapes = Vec::new(); + for f in &flds.elems { + field_shapes.push(f.try_into()?); + } + Shape::List(PositionedItem::new(field_shapes, flds.pos.clone())) + } + }; + Ok(shape) + } +} + +impl TryFrom<&Value> for Shape { + type Error = crate::error::BuildError; + + fn try_from(v: &Value) -> Result { + v.derive_shape() + } } /// Represents an expansion of a Macro that is expected to already have been @@ -769,6 +844,28 @@ impl Expression { &Expression::Debug(ref def) => &def.pos, } } + + fn derive_shape(&self) -> Result { + // FIXME(jwall): Implement this + let shape = match self { + Expression::Simple(ref v) => v.try_into()?, + Expression::Format(def) => { + Shape::Str(PositionedItem::new("".to_owned(), def.pos.clone())) + } + Expression::Not(def) => Shape::Boolean(PositionedItem::new(true, def.pos.clone())), + Expression::Grouped(v, _pos) => v.as_ref().try_into()?, + _ => Shape::Empty(Position::new(0, 0, 0)), + }; + Ok(shape) + } +} + +impl TryFrom<&Expression> for Shape { + type Error = crate::error::BuildError; + + fn try_from(e: &Expression) -> Result { + e.derive_shape() + } } impl fmt::Display for Expression { @@ -868,3 +965,6 @@ impl Statement { } } } + +#[cfg(test)] +mod test; diff --git a/src/ast/test.rs b/src/ast/test.rs new file mode 100644 index 0000000..aef79ca --- /dev/null +++ b/src/ast/test.rs @@ -0,0 +1,128 @@ +// Copyright 2020 Jeremy Wall +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::ast::{ + Expression, FormatArgs, FormatDef, ListDef, NotDef, Position, PositionedItem, Shape, Token, + TokenType, Value, +}; + +#[test] +fn derive_shape_values() { + let value_cases = vec![ + ( + Value::Empty(Position::new(0, 0, 0)), + Shape::Empty(Position::new(0, 0, 0)), + ), + ( + Value::Boolean(PositionedItem::new(false, Position::new(0, 1, 2))), + Shape::Boolean(PositionedItem::new(false, Position::new(0, 1, 2))), + ), + ( + Value::Boolean(PositionedItem::new(true, Position::new(0, 1, 2))), + Shape::Boolean(PositionedItem::new(true, Position::new(0, 1, 2))), + ), + ( + Value::Int(PositionedItem::new(1, Position::new(0, 1, 2))), + Shape::Int(PositionedItem::new(1, Position::new(0, 1, 2))), + ), + ( + Value::Float(PositionedItem::new(2.0, Position::new(0, 1, 2))), + Shape::Float(PositionedItem::new(2.0, Position::new(0, 1, 2))), + ), + ( + Value::Str(PositionedItem::new( + "foo".to_owned(), + Position::new(0, 1, 2), + )), + Shape::Str(PositionedItem::new( + "foo".to_owned(), + Position::new(0, 1, 2), + )), + ), + ( + Value::Tuple(PositionedItem::new( + vec![( + Token::new("foo", TokenType::BAREWORD, Position::new(0, 0, 0)), + Expression::Simple(Value::Int(PositionedItem::new(3, Position::new(0, 0, 0)))), + )], + Position::new(0, 0, 0), + )), + Shape::Tuple(PositionedItem::new( + vec![( + Token::new("foo", TokenType::BAREWORD, Position::new(0, 0, 0)), + Shape::Int(PositionedItem::new(3, Position::new(0, 0, 0))), + )], + Position::new(0, 0, 0), + )), + ), + ( + Value::List(ListDef { + elems: vec![Expression::Simple(Value::Int(PositionedItem::new( + 3, + Position::new(0, 0, 0), + )))], + pos: Position::new(0, 0, 0), + }), + Shape::List(PositionedItem::new( + vec![Shape::Int(PositionedItem::new(3, Position::new(0, 0, 0)))], + Position::new(0, 0, 0), + )), + ), + ]; + + for (val, shape) in value_cases { + assert_eq!(val.derive_shape().unwrap(), shape); + } +} + +#[test] +fn derive_shape_expressions() { + let expr_cases = vec![ + ( + Expression::Simple(Value::Int(PositionedItem::new(3, Position::new(0, 0, 0)))), + Shape::Int(PositionedItem::new(3, Position::new(0, 0, 0))), + ), + ( + Expression::Grouped( + Box::new(Expression::Simple(Value::Int(PositionedItem::new( + 3, + Position::new(0, 0, 0), + )))), + Position::new(0, 0, 0), + ), + Shape::Int(PositionedItem::new(3, Position::new(0, 0, 0))), + ), + ( + Expression::Format(FormatDef { + template: "".to_owned(), + args: FormatArgs::List(Vec::new()), + pos: Position::new(0, 0, 0), + }), + Shape::Str(PositionedItem::new("".to_owned(), Position::new(0, 0, 0))), + ), + ( + Expression::Not(NotDef { + expr: Box::new(Expression::Simple(Value::Boolean(PositionedItem::new( + true, + Position::new(0, 0, 0), + )))), + pos: Position::new(1, 0, 0), + }), + Shape::Boolean(PositionedItem::new(true, Position::new(1, 0, 0))), + ), + ]; + + for (expr, shape) in expr_cases { + assert_eq!(expr.derive_shape().unwrap(), shape); + } +} diff --git a/src/build/opcode/translate.rs b/src/build/opcode/translate.rs index bc38fff..5c00c36 100644 --- a/src/build/opcode/translate.rs +++ b/src/build/opcode/translate.rs @@ -402,6 +402,8 @@ impl AST { Expression::Func(def) => { ops.push(Op::InitList, def.pos.clone()); for b in def.argdefs { + // FIXME(jwall): if there is a projection + // then add the projection check here? ops.push(Op::Sym(b.val), b.pos.clone()); ops.push(Op::Element, b.pos); } @@ -460,6 +462,8 @@ impl AST { // Init our module tuple bindings ops.push(Op::InitTuple, def.pos.clone()); for (t, e) in argset { + // FIXME(jwall): if there is a projection + // then add the projection check here? ops.push(Op::Sym(t.fragment), t.pos.clone()); Self::translate_expr(e, &mut ops, root); ops.push(Op::Field, t.pos);