Add support for casting.
This commit is contained in:
7
examples/basic/cast1.ngr
Normal file
7
examples/basic/cast1.ngr
Normal file
@@ -0,0 +1,7 @@
|
||||
x8 = 5u8;
|
||||
x16 = <u16>x8 + 1u16;
|
||||
print x16;
|
||||
x32 = <u32>x16 + 1u32;
|
||||
print x32;
|
||||
x64 = <u64>x32 + 1u64;
|
||||
print x64;
|
||||
7
examples/basic/cast2.ngr
Normal file
7
examples/basic/cast2.ngr
Normal file
@@ -0,0 +1,7 @@
|
||||
x8 = 5i8;
|
||||
x16 = <i16>x8 - 1i16;
|
||||
print x16;
|
||||
x32 = <i32>x16 - 1i32;
|
||||
print x32;
|
||||
x64 = <i64>x32 - 1i64;
|
||||
print x64;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::backend::runtime::RuntimeFunctionError;
|
||||
use crate::{backend::runtime::RuntimeFunctionError, eval::PrimitiveType, ir::Type};
|
||||
use codespan_reporting::diagnostic::Diagnostic;
|
||||
use cranelift_codegen::{isa::LookupError, settings::SetError, CodegenError};
|
||||
use cranelift_module::ModuleError;
|
||||
@@ -39,6 +39,8 @@ pub enum BackendError {
|
||||
LookupError(#[from] LookupError),
|
||||
#[error(transparent)]
|
||||
Write(#[from] cranelift_object::object::write::Error),
|
||||
#[error("Invalid type cast from {from} to {to}")]
|
||||
InvalidTypeCast { from: PrimitiveType, to: Type },
|
||||
}
|
||||
|
||||
impl From<BackendError> for Diagnostic<usize> {
|
||||
@@ -64,6 +66,9 @@ impl From<BackendError> for Diagnostic<usize> {
|
||||
BackendError::Write(me) => {
|
||||
Diagnostic::error().with_message(format!("Cranelift object write error: {}", me))
|
||||
}
|
||||
BackendError::InvalidTypeCast { from, to } => Diagnostic::error().with_message(
|
||||
format!("Internal error trying to cast from {} to {}", from, to),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,6 +108,17 @@ impl PartialEq for BackendError {
|
||||
BackendError::Write(b) => a == b,
|
||||
_ => false,
|
||||
},
|
||||
|
||||
BackendError::InvalidTypeCast {
|
||||
from: from1,
|
||||
to: to1,
|
||||
} => match other {
|
||||
BackendError::InvalidTypeCast {
|
||||
from: from2,
|
||||
to: to2,
|
||||
} => from1 == from2 && to1 == to2,
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::ir::{Expression, Primitive, Program, Statement, Value, ValueOrRef};
|
||||
use crate::eval::PrimitiveType;
|
||||
use crate::ir::{Expression, Primitive, Program, Statement, Type, Value, ValueOrRef};
|
||||
use crate::syntax::ConstantType;
|
||||
use cranelift_codegen::entity::EntityRef;
|
||||
use cranelift_codegen::ir::{
|
||||
@@ -307,6 +308,70 @@ impl Expression {
|
||||
Err(BackendError::VariableLookupFailure(name))
|
||||
}
|
||||
|
||||
Expression::Cast(_, target_type, expr) => {
|
||||
let (val, val_type) =
|
||||
expr.into_crane(builder, local_variables, global_variables)?;
|
||||
|
||||
match (val_type, &target_type) {
|
||||
(ConstantType::I8, Type::Primitive(PrimitiveType::I8)) => Ok((val, val_type)),
|
||||
(ConstantType::I8, Type::Primitive(PrimitiveType::I16)) => {
|
||||
Ok((builder.ins().sextend(types::I16, val), ConstantType::I16))
|
||||
}
|
||||
(ConstantType::I8, Type::Primitive(PrimitiveType::I32)) => {
|
||||
Ok((builder.ins().sextend(types::I32, val), ConstantType::I32))
|
||||
}
|
||||
(ConstantType::I8, Type::Primitive(PrimitiveType::I64)) => {
|
||||
Ok((builder.ins().sextend(types::I64, val), ConstantType::I64))
|
||||
}
|
||||
|
||||
(ConstantType::I16, Type::Primitive(PrimitiveType::I16)) => Ok((val, val_type)),
|
||||
(ConstantType::I16, Type::Primitive(PrimitiveType::I32)) => {
|
||||
Ok((builder.ins().sextend(types::I32, val), ConstantType::I32))
|
||||
}
|
||||
(ConstantType::I16, Type::Primitive(PrimitiveType::I64)) => {
|
||||
Ok((builder.ins().sextend(types::I64, val), ConstantType::I64))
|
||||
}
|
||||
|
||||
(ConstantType::I32, Type::Primitive(PrimitiveType::I32)) => Ok((val, val_type)),
|
||||
(ConstantType::I32, Type::Primitive(PrimitiveType::I64)) => {
|
||||
Ok((builder.ins().sextend(types::I64, val), ConstantType::I64))
|
||||
}
|
||||
|
||||
(ConstantType::I64, Type::Primitive(PrimitiveType::I64)) => Ok((val, val_type)),
|
||||
|
||||
(ConstantType::U8, Type::Primitive(PrimitiveType::U8)) => Ok((val, val_type)),
|
||||
(ConstantType::U8, Type::Primitive(PrimitiveType::U16)) => {
|
||||
Ok((builder.ins().uextend(types::I16, val), ConstantType::U16))
|
||||
}
|
||||
(ConstantType::U8, Type::Primitive(PrimitiveType::U32)) => {
|
||||
Ok((builder.ins().uextend(types::I32, val), ConstantType::U32))
|
||||
}
|
||||
(ConstantType::U8, Type::Primitive(PrimitiveType::U64)) => {
|
||||
Ok((builder.ins().uextend(types::I64, val), ConstantType::U64))
|
||||
}
|
||||
|
||||
(ConstantType::U16, Type::Primitive(PrimitiveType::U16)) => Ok((val, val_type)),
|
||||
(ConstantType::U16, Type::Primitive(PrimitiveType::U32)) => {
|
||||
Ok((builder.ins().uextend(types::I32, val), ConstantType::U32))
|
||||
}
|
||||
(ConstantType::U16, Type::Primitive(PrimitiveType::U64)) => {
|
||||
Ok((builder.ins().uextend(types::I64, val), ConstantType::U64))
|
||||
}
|
||||
|
||||
(ConstantType::U32, Type::Primitive(PrimitiveType::U32)) => Ok((val, val_type)),
|
||||
(ConstantType::U32, Type::Primitive(PrimitiveType::U64)) => {
|
||||
Ok((builder.ins().uextend(types::I64, val), ConstantType::U64))
|
||||
}
|
||||
|
||||
(ConstantType::U64, Type::Primitive(PrimitiveType::U64)) => Ok((val, val_type)),
|
||||
|
||||
_ => Err(BackendError::InvalidTypeCast {
|
||||
from: val_type.into(),
|
||||
to: target_type,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
Expression::Primitive(_, prim, mut vals) => {
|
||||
let mut values = vec![];
|
||||
let mut first_type = None;
|
||||
|
||||
@@ -17,7 +17,7 @@ fn main() {
|
||||
let args = CommandLineArguments::parse();
|
||||
let mut compiler = ngr::Compiler::default();
|
||||
|
||||
let output_file = args.output.unwrap_or("output.o".to_string());
|
||||
let output_file = args.output.unwrap_or_else(|| "output.o".to_string());
|
||||
|
||||
if let Some(bytes) = compiler.compile(&args.file) {
|
||||
std::fs::write(&output_file, bytes)
|
||||
|
||||
@@ -35,11 +35,13 @@
|
||||
//!
|
||||
mod env;
|
||||
mod primop;
|
||||
mod primtype;
|
||||
mod value;
|
||||
|
||||
use cranelift_module::ModuleError;
|
||||
pub use env::{EvalEnvironment, LookupError};
|
||||
pub use primop::PrimOpError;
|
||||
pub use primtype::PrimitiveType;
|
||||
pub use value::Value;
|
||||
|
||||
use crate::backend::BackendError;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::eval::primtype::PrimitiveType;
|
||||
use crate::eval::value::Value;
|
||||
|
||||
/// Errors that can occur running primitive operations in the evaluators.
|
||||
@@ -22,6 +23,13 @@ pub enum PrimOpError {
|
||||
BadArgCount(String, usize),
|
||||
#[error("Unknown primitive operation {0}")]
|
||||
UnknownPrimOp(String),
|
||||
#[error("Unsafe cast from {from} to {to}")]
|
||||
UnsafeCast {
|
||||
from: PrimitiveType,
|
||||
to: PrimitiveType,
|
||||
},
|
||||
#[error("Unknown primitive type {0}")]
|
||||
UnknownPrimType(String),
|
||||
}
|
||||
|
||||
// Implementing primitives in an interpreter like this is *super* tedious,
|
||||
|
||||
121
src/eval/primtype.rs
Normal file
121
src/eval/primtype.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use crate::{
|
||||
eval::{PrimOpError, Value},
|
||||
syntax::ConstantType,
|
||||
};
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum PrimitiveType {
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
}
|
||||
|
||||
impl Display for PrimitiveType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
PrimitiveType::I8 => write!(f, "i8"),
|
||||
PrimitiveType::I16 => write!(f, "i16"),
|
||||
PrimitiveType::I32 => write!(f, "i32"),
|
||||
PrimitiveType::I64 => write!(f, "i64"),
|
||||
PrimitiveType::U8 => write!(f, "u8"),
|
||||
PrimitiveType::U16 => write!(f, "u16"),
|
||||
PrimitiveType::U32 => write!(f, "u32"),
|
||||
PrimitiveType::U64 => write!(f, "u64"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Value> for PrimitiveType {
|
||||
fn from(value: &Value) -> Self {
|
||||
match value {
|
||||
Value::I8(_) => PrimitiveType::I8,
|
||||
Value::I16(_) => PrimitiveType::I16,
|
||||
Value::I32(_) => PrimitiveType::I32,
|
||||
Value::I64(_) => PrimitiveType::I64,
|
||||
Value::U8(_) => PrimitiveType::U8,
|
||||
Value::U16(_) => PrimitiveType::U16,
|
||||
Value::U32(_) => PrimitiveType::U32,
|
||||
Value::U64(_) => PrimitiveType::U64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConstantType> for PrimitiveType {
|
||||
fn from(value: ConstantType) -> Self {
|
||||
match value {
|
||||
ConstantType::I8 => PrimitiveType::I8,
|
||||
ConstantType::I16 => PrimitiveType::I16,
|
||||
ConstantType::I32 => PrimitiveType::I32,
|
||||
ConstantType::I64 => PrimitiveType::I64,
|
||||
ConstantType::U8 => PrimitiveType::U8,
|
||||
ConstantType::U16 => PrimitiveType::U16,
|
||||
ConstantType::U32 => PrimitiveType::U32,
|
||||
ConstantType::U64 => PrimitiveType::U64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PrimitiveType {
|
||||
type Err = PrimOpError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"i8" => Ok(PrimitiveType::I8),
|
||||
"i16" => Ok(PrimitiveType::I16),
|
||||
"i32" => Ok(PrimitiveType::I32),
|
||||
"i64" => Ok(PrimitiveType::I64),
|
||||
"u8" => Ok(PrimitiveType::U8),
|
||||
"u16" => Ok(PrimitiveType::U16),
|
||||
"u32" => Ok(PrimitiveType::U32),
|
||||
"u64" => Ok(PrimitiveType::U64),
|
||||
_ => Err(PrimOpError::UnknownPrimType(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimitiveType {
|
||||
/// Try to cast the given value to this type, returning the new value.
|
||||
///
|
||||
/// Returns an error if the cast is not safe *in* *general*. This means that
|
||||
/// this function will error even if the number will actually fit in the target
|
||||
/// type, but it would not be generally safe to cast a member of the given
|
||||
/// type to the target type. (So, for example, "1i64" is a number that could
|
||||
/// work as a "u64", but since negative numbers wouldn't work, a cast from
|
||||
/// "1i64" to "u64" will fail.)
|
||||
pub fn safe_cast(&self, source: &Value) -> Result<Value, PrimOpError> {
|
||||
match (self, source) {
|
||||
(PrimitiveType::U8, Value::U8(x)) => Ok(Value::U8(*x)),
|
||||
(PrimitiveType::U16, Value::U8(x)) => Ok(Value::U16(*x as u16)),
|
||||
(PrimitiveType::U16, Value::U16(x)) => Ok(Value::U16(*x)),
|
||||
(PrimitiveType::U32, Value::U8(x)) => Ok(Value::U32(*x as u32)),
|
||||
(PrimitiveType::U32, Value::U16(x)) => Ok(Value::U32(*x as u32)),
|
||||
(PrimitiveType::U32, Value::U32(x)) => Ok(Value::U32(*x)),
|
||||
(PrimitiveType::U64, Value::U8(x)) => Ok(Value::U64(*x as u64)),
|
||||
(PrimitiveType::U64, Value::U16(x)) => Ok(Value::U64(*x as u64)),
|
||||
(PrimitiveType::U64, Value::U32(x)) => Ok(Value::U64(*x as u64)),
|
||||
(PrimitiveType::U64, Value::U64(x)) => Ok(Value::U64(*x)),
|
||||
|
||||
(PrimitiveType::I8, Value::I8(x)) => Ok(Value::I8(*x)),
|
||||
(PrimitiveType::I16, Value::I8(x)) => Ok(Value::I16(*x as i16)),
|
||||
(PrimitiveType::I16, Value::I16(x)) => Ok(Value::I16(*x)),
|
||||
(PrimitiveType::I32, Value::I8(x)) => Ok(Value::I32(*x as i32)),
|
||||
(PrimitiveType::I32, Value::I16(x)) => Ok(Value::I32(*x as i32)),
|
||||
(PrimitiveType::I32, Value::I32(x)) => Ok(Value::I32(*x)),
|
||||
(PrimitiveType::I64, Value::I8(x)) => Ok(Value::I64(*x as i64)),
|
||||
(PrimitiveType::I64, Value::I16(x)) => Ok(Value::I64(*x as i64)),
|
||||
(PrimitiveType::I64, Value::I32(x)) => Ok(Value::I64(*x as i64)),
|
||||
(PrimitiveType::I64, Value::I64(x)) => Ok(Value::I64(*x)),
|
||||
|
||||
_ => Err(PrimOpError::UnsafeCast {
|
||||
from: source.into(),
|
||||
to: *self,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
use crate::syntax::{self, ConstantType, Location};
|
||||
use crate::{
|
||||
eval::PrimitiveType,
|
||||
syntax::{self, ConstantType, Location},
|
||||
};
|
||||
use internment::ArcIntern;
|
||||
use pretty::{DocAllocator, Pretty};
|
||||
use proptest::{
|
||||
prelude::Arbitrary,
|
||||
strategy::{BoxedStrategy, Strategy},
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
/// We're going to represent variables as interned strings.
|
||||
///
|
||||
@@ -115,6 +119,7 @@ where
|
||||
pub enum Expression {
|
||||
Value(Location, Value),
|
||||
Reference(Location, Variable),
|
||||
Cast(Location, Type, ValueOrRef),
|
||||
Primitive(Location, Primitive, Vec<ValueOrRef>),
|
||||
}
|
||||
|
||||
@@ -127,6 +132,11 @@ where
|
||||
match self {
|
||||
Expression::Value(_, val) => val.pretty(allocator),
|
||||
Expression::Reference(_, var) => allocator.text(var.as_ref().to_string()),
|
||||
Expression::Cast(_, t, e) => allocator
|
||||
.text("<")
|
||||
.append(t.pretty(allocator))
|
||||
.append(allocator.text(">"))
|
||||
.append(e.pretty(allocator)),
|
||||
Expression::Primitive(_, op, exprs) if exprs.len() == 1 => {
|
||||
op.pretty(allocator).append(exprs[0].pretty(allocator))
|
||||
}
|
||||
@@ -283,3 +293,28 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum Type {
|
||||
Primitive(PrimitiveType),
|
||||
}
|
||||
|
||||
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Type
|
||||
where
|
||||
A: 'a,
|
||||
D: ?Sized + DocAllocator<'a, A>,
|
||||
{
|
||||
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
|
||||
match self {
|
||||
Type::Primitive(pt) => allocator.text(format!("{}", pt)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Type::Primitive(pt) => pt.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::eval::{EvalEnvironment, EvalError, Value};
|
||||
use crate::ir::{Expression, Program, Statement};
|
||||
|
||||
use super::{Primitive, ValueOrRef};
|
||||
use super::{Primitive, Type, ValueOrRef};
|
||||
|
||||
impl Program {
|
||||
/// Evaluate the program, returning either an error or a string containing everything
|
||||
@@ -47,6 +47,19 @@ impl Expression {
|
||||
|
||||
Expression::Reference(_, n) => Ok(env.lookup(n.clone())?),
|
||||
|
||||
Expression::Cast(_, t, valref) => {
|
||||
let value = match valref {
|
||||
ValueOrRef::Ref(_, n) => env.lookup(n.clone())?,
|
||||
ValueOrRef::Value(loc, val) => {
|
||||
Expression::Value(loc.clone(), val.clone()).eval(env)?
|
||||
}
|
||||
};
|
||||
|
||||
match t {
|
||||
Type::Primitive(pt) => Ok(pt.safe_cast(&value)?),
|
||||
}
|
||||
}
|
||||
|
||||
Expression::Primitive(_, op, args) => {
|
||||
let mut arg_values = Vec::with_capacity(args.len());
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use internment::ArcIntern;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
|
||||
use crate::eval::PrimitiveType;
|
||||
use crate::ir::ast as ir;
|
||||
use crate::syntax;
|
||||
|
||||
@@ -104,7 +106,20 @@ impl syntax::Expression {
|
||||
(vec![], ValueOrRef::Ref(loc, ArcIntern::new(name)))
|
||||
}
|
||||
|
||||
syntax::Expression::Cast(_, _, _) => unimplemented!(),
|
||||
syntax::Expression::Cast(loc, t, expr) => {
|
||||
let (mut prereqs, new_expr) = expr.rebind(base_name);
|
||||
let new_name = gensym(base_name);
|
||||
prereqs.push(ir::Statement::Binding(
|
||||
loc.clone(),
|
||||
new_name.clone(),
|
||||
ir::Expression::Cast(
|
||||
loc.clone(),
|
||||
ir::Type::Primitive(PrimitiveType::from_str(&t).unwrap()),
|
||||
new_expr,
|
||||
),
|
||||
));
|
||||
(prereqs, ValueOrRef::Ref(loc, new_name))
|
||||
}
|
||||
|
||||
// Primitive expressions are where we do the real work.
|
||||
syntax::Expression::Primitive(loc, prim, mut expressions) => {
|
||||
|
||||
@@ -124,7 +124,30 @@ impl Arbitrary for Expression {
|
||||
Union::new([value_strategy, reference_strategy]).boxed()
|
||||
};
|
||||
|
||||
let cast_strategy = if let Some(bigger_type) = target_type {
|
||||
let mut smaller_types = bigger_type.safe_casts_to();
|
||||
|
||||
if smaller_types.is_empty() {
|
||||
leaf_strategy
|
||||
} else {
|
||||
let duplicated_env = defined_variables.clone();
|
||||
let cast_exp = |t, e| Expression::Cast(Location::manufactured(), t, Box::new(e));
|
||||
|
||||
let smaller_strats: Vec<BoxedStrategy<Expression>> = smaller_types
|
||||
.drain(..)
|
||||
.map(|t| {
|
||||
Expression::arbitrary_with((Some(duplicated_env.clone()), Some(t)))
|
||||
.prop_map(move |e| cast_exp(t.name(), e))
|
||||
.boxed()
|
||||
})
|
||||
.collect();
|
||||
Union::new(smaller_strats).boxed()
|
||||
}
|
||||
} else {
|
||||
leaf_strategy
|
||||
};
|
||||
|
||||
cast_strategy
|
||||
.prop_recursive(3, 64, 2, move |inner| {
|
||||
(select(OPERATORS), proptest::collection::vec(inner, 2)).prop_map(
|
||||
move |((operator, arg_count), mut exprs)| {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use internment::ArcIntern;
|
||||
|
||||
use crate::eval::{EvalEnvironment, EvalError, Value};
|
||||
use crate::eval::{EvalEnvironment, EvalError, PrimitiveType, Value};
|
||||
use crate::syntax::{ConstantType, Expression, Program, Statement};
|
||||
use std::str::FromStr;
|
||||
|
||||
impl Program {
|
||||
/// Evaluate the program, returning either an error or what it prints out when run.
|
||||
@@ -59,7 +60,11 @@ impl Expression {
|
||||
|
||||
Expression::Reference(_, n) => Ok(env.lookup(ArcIntern::new(n.clone()))?),
|
||||
|
||||
Expression::Cast(_, _, _) => unimplemented!(),
|
||||
Expression::Cast(_, target, expr) => {
|
||||
let target_type = PrimitiveType::from_str(target)?;
|
||||
let value = expr.eval(env)?;
|
||||
Ok(target_type.safe_cast(&value)?)
|
||||
}
|
||||
|
||||
Expression::Primitive(_, op, args) => {
|
||||
let mut arg_values = Vec::with_capacity(args.len());
|
||||
|
||||
@@ -175,6 +175,34 @@ impl ConstantType {
|
||||
ConstantType::I8 | ConstantType::I16 | ConstantType::I32 | ConstantType::I64
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the set of types that can be safely casted into this type.
|
||||
pub fn safe_casts_to(self) -> Vec<ConstantType> {
|
||||
match self {
|
||||
ConstantType::I8 => vec![],
|
||||
ConstantType::I16 => vec![ConstantType::I8],
|
||||
ConstantType::I32 => vec![ConstantType::I16, ConstantType::I8],
|
||||
ConstantType::I64 => vec![ConstantType::I32, ConstantType::I16, ConstantType::I8],
|
||||
ConstantType::U8 => vec![],
|
||||
ConstantType::U16 => vec![ConstantType::U8],
|
||||
ConstantType::U32 => vec![ConstantType::U16, ConstantType::U8],
|
||||
ConstantType::U64 => vec![ConstantType::U32, ConstantType::U16, ConstantType::U8],
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the name of the given type, as a string
|
||||
pub fn name(&self) -> String {
|
||||
match self {
|
||||
ConstantType::I8 => "i8".to_string(),
|
||||
ConstantType::I16 => "i16".to_string(),
|
||||
ConstantType::I32 => "i32".to_string(),
|
||||
ConstantType::I64 => "i64".to_string(),
|
||||
ConstantType::U8 => "u8".to_string(),
|
||||
ConstantType::U16 => "u16".to_string(),
|
||||
ConstantType::U32 => "u32".to_string(),
|
||||
ConstantType::U64 => "u64".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
|
||||
@@ -127,7 +127,7 @@ impl Expression {
|
||||
vec![Error::UnboundVariable(loc.clone(), var.clone())],
|
||||
vec![],
|
||||
),
|
||||
Expression::Cast(_, _, _) => unimplemented!(),
|
||||
Expression::Cast(_, _, expr) => expr.validate(variable_map),
|
||||
Expression::Primitive(_, _, args) => {
|
||||
let mut errors = vec![];
|
||||
let mut warnings = vec![];
|
||||
|
||||
Reference in New Issue
Block a user