DEV: Implement primitive casting.

This commit is contained in:
Jeremy Wall 2019-11-11 20:06:17 -06:00
parent 392050d9f4
commit 632019ac18
13 changed files with 264 additions and 34 deletions

View File

@ -80,4 +80,34 @@ let foo_string = "foo";
assert t.ok{
test = foo_string in ["foo"],
desc = "List presence checks work",
};
assert t.ok{
test = int("1") == 1,
desc = "You can cast a string into an int",
};
assert t.ok{
test = str(1) == "1",
desc = "You can cast an int into a string",
};
assert t.ok{
test = bool("true") == true,
desc = "You can cast an string into true",
};
assert t.ok{
test = bool("false") == false,
desc = "You can cast a string into false",
};
assert t.ok{
test = str(true) == "true",
desc = "You can cast true into a string",
};
assert t.ok{
test = str(false) == "false",
desc = "You can cast true into a string",
};

View File

@ -318,6 +318,38 @@ pub struct CallDef {
pub pos: Position,
}
/// The allowable types to which you can perform a primitive cast.
#[derive(PartialEq, Debug, Clone)]
pub enum CastType {
Int,
Float,
Str,
Bool,
}
impl fmt::Display for CastType {
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
write!(
w,
"{}",
match self {
CastType::Int => "int",
CastType::Float => "float",
CastType::Bool => "bool",
CastType::Str => "str",
}
)
}
}
/// Represents a cast of a target to a primitive type.
#[derive(PartialEq, Debug, Clone)]
pub struct CastDef {
pub cast_type: CastType,
pub target: Box<Expression>,
pub pos: Position,
}
/// Encodes a select expression in the UCG AST.
#[derive(PartialEq, Debug, Clone)]
pub struct SelectDef {
@ -694,6 +726,7 @@ pub enum Expression {
Include(IncludeDef),
Import(ImportDef),
Call(CallDef),
Cast(CastDef),
Func(FuncDef),
Select(SelectDef),
FuncOp(FuncOpDef),
@ -716,6 +749,7 @@ impl Expression {
&Expression::Grouped(_, ref pos) => pos,
&Expression::Format(ref def) => &def.pos,
&Expression::Call(ref def) => &def.pos,
&Expression::Cast(ref def) => &def.pos,
&Expression::Func(ref def) => &def.pos,
&Expression::Module(ref def) => &def.pos,
&Expression::Select(ref def) => &def.pos,
@ -754,7 +788,10 @@ impl fmt::Display for Expression {
write!(w, "<Format Expr>")?;
}
&Expression::Call(_) => {
write!(w, "<MacroCall>")?;
write!(w, "<FuncCall>")?;
}
&Expression::Cast(_) => {
write!(w, "<Cast>")?;
}
&Expression::Func(_) => {
write!(w, "<Func>")?;

View File

@ -258,6 +258,13 @@ where
}
self.render_expr(&_def.right)?;
}
Expression::Cast(def) => {
self.w.write(format!("{}", def.cast_type).as_bytes())?;
self.w.write("(".as_bytes())?;
self.render_comment_if_needed(def.target.pos().line)?;
self.render_expr(&def.target)?;
self.w.write(")".as_bytes())?;
}
Expression::Call(_def) => {
self.render_value(&_def.funcref)?;
self.w.write("(".as_bytes())?;

View File

@ -42,6 +42,9 @@ pub trait Walker {
self.walk_expression(expr);
}
}
Expression::Cast(ref mut def) => {
self.walk_expression(&mut def.target);
}
Expression::Copy(ref mut def) => {
self.walk_fieldset(&mut def.fields);
}

View File

@ -575,4 +575,4 @@ fn test_bad_import_path_compile_failure() {
Regex::new(r"line: 1 column: 18").unwrap(),
],
)
}
}

View File

@ -17,28 +17,28 @@ use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryInto;
use std::env;
use std::process;
use std::error::Error;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use std::process;
use std::rc::Rc;
use rustyline;
use rustyline::error::ReadlineError;
use atty;
use atty::Stream;
use rustyline;
use rustyline::error::ReadlineError;
use simple_error;
use crate::ast::*;
use crate::error;
use crate::iter::OffsetStrIter;
use crate::parse::parse;
use crate::build::opcode::pointer::OpPointer;
use crate::build::opcode::translate;
use crate::build::opcode::translate::PositionMap;
use crate::build::opcode::Environment;
use crate::build::opcode::VM;
use crate::error;
use crate::iter::OffsetStrIter;
use crate::parse::parse;
pub mod format;
pub mod ir;
@ -216,12 +216,12 @@ where
pub fn repl(&mut self, mut editor: rustyline::Editor<()>, config_home: PathBuf) -> BuildResult {
// loop
let mut lines = crate::io::StatementAccumulator::new();
if atty::is(Stream::Stdin) {
println!("Welcome to the UCG repl. Ctrl-D to exit, Ctrl-C to abort expression.");
println!("Type '#help' for help.");
println!("");
}
let mut lines = crate::io::StatementAccumulator::new();
if atty::is(Stream::Stdin) {
println!("Welcome to the UCG repl. Ctrl-D to exit, Ctrl-C to abort expression.");
println!("Type '#help' for help.");
println!("");
}
// Initialize VM with an empty OpPointer
let mut vm = VM::new(
self.strict,

View File

@ -0,0 +1,93 @@
use std::convert::TryFrom;
use super::Primitive;
use crate::ast::CastType;
pub struct Error {
val: Primitive,
cast_type: CastType,
}
impl Error {
pub fn message(&self) -> String {
format!("No cast from {} to {}", self.val, self.cast_type)
}
}
impl From<&Primitive> for String {
fn from(p: &Primitive) -> Self {
match p {
Primitive::Int(i) => format!("{}", i),
Primitive::Float(f) => format!("{}", f),
Primitive::Str(s) => format!("{}", s),
Primitive::Bool(b) => format!("{}", b),
Primitive::Empty => "NULL".to_owned(),
}
}
}
impl TryFrom<&Primitive> for i64 {
type Error = Error;
fn try_from(p: &Primitive) -> Result<Self, Self::Error> {
match p {
Primitive::Bool(_) | Primitive::Empty => Err(Error {
val: p.clone(),
cast_type: CastType::Int,
}),
Primitive::Str(s) => match s.parse::<i64>() {
Ok(i) => Ok(i),
Err(_) => Err(Error {
val: Primitive::Str(s.clone()),
cast_type: CastType::Int,
}),
},
Primitive::Float(f) => Ok(*f as i64),
Primitive::Int(i) => Ok(i.clone()),
}
}
}
impl TryFrom<&Primitive> for f64 {
type Error = Error;
fn try_from(p: &Primitive) -> Result<Self, Self::Error> {
match p {
Primitive::Bool(_) | Primitive::Empty => Err(Error {
val: p.clone(),
cast_type: CastType::Int,
}),
Primitive::Str(s) => match s.parse::<f64>() {
Ok(f) => Ok(f),
Err(_) => Err(Error {
val: Primitive::Str(s.clone()),
cast_type: CastType::Int,
}),
},
Primitive::Int(i) => Ok(*i as f64),
Primitive::Float(f) => Ok(f.clone()),
}
}
}
impl TryFrom<&Primitive> for bool {
type Error = Error;
fn try_from(p: &Primitive) -> Result<Self, Self::Error> {
match p {
Primitive::Empty | Primitive::Int(_) | Primitive::Float(_) => Err(Error {
val: p.clone(),
cast_type: CastType::Int,
}),
Primitive::Bool(b) => Ok(*b),
Primitive::Str(s) => match s.as_str() {
"true" => Ok(true),
"false" => Ok(false),
_ => Err(Error {
val: Primitive::Str(s.clone()),
cast_type: CastType::Int,
}),
},
}
}
}

View File

@ -24,11 +24,7 @@ use Value::{C, F, M, P, S, T};
impl fmt::Display for Value {
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
match self {
P(Bool(v)) => write!(w, "{}", v),
P(Int(v)) => write!(w, "{}", v),
P(Float(v)) => write!(w, "{}", v),
P(Str(v)) => write!(w, "\"{}\"", v.replace("\"", "\\\"")),
P(Empty) => write!(w, "NULL"),
P(p) => write!(w, "{}", p),
C(List(ref els, _)) => {
write!(w, "[")?;
for e in els {
@ -50,3 +46,15 @@ impl fmt::Display for Value {
}
}
}
impl fmt::Display for Primitive {
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
match self {
Bool(v) => write!(w, "{}", v),
Int(v) => write!(w, "{}", v),
Float(v) => write!(w, "{}", v),
Str(v) => write!(w, "\"{}\"", v.replace("\"", "\\\"")),
Empty => write!(w, "NULL"),
}
}
}

View File

@ -17,6 +17,7 @@ use std::fmt::Display;
use std::io;
use crate::ast::Position;
use crate::build::opcode::convert;
#[derive(Debug)]
pub struct Error {
@ -91,6 +92,16 @@ impl From<std::io::Error> for Error {
}
}
impl From<convert::Error> for Error {
fn from(e: convert::Error) -> Self {
Error {
message: e.message(),
pos: None,
call_stack: Vec::new(),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref pos) = self.pos {

View File

@ -19,6 +19,7 @@ mod display;
pub mod environment;
#[macro_use]
mod error;
mod convert;
pub mod pointer;
mod runtime;
pub mod scope;
@ -29,11 +30,10 @@ pub use environment::Environment;
pub use error::Error;
pub use vm::VM;
use crate::ast::{CastType, Position};
use pointer::OpPointer;
use scope::Stack;
use crate::ast::Position;
#[derive(Debug, PartialEq, Clone)]
pub enum Primitive {
// Primitive Types
@ -64,18 +64,6 @@ impl Value {
}
}
impl From<&Primitive> for String {
fn from(p: &Primitive) -> Self {
match p {
Int(i) => format!("{}", i),
Float(f) => format!("{}", f),
Str(s) => format!("{}", s),
Bool(b) => format!("{}", b),
Empty => "NULL".to_owned(),
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Composite {
List(Vec<Rc<Value>>, Vec<Position>),
@ -198,6 +186,8 @@ pub enum Op {
Not,
// Primitive Types ops
Val(Primitive),
// Primitive casts
Cast(CastType),
// A bareword for use in bindings or lookups
Sym(String),
// Reference a binding on the heap
@ -274,6 +264,7 @@ impl PartialEq for Value {
}
}
// TODO(jwall): Move all of this into the convert module.
impl From<Rc<Value>> for Val {
fn from(val: Rc<Value>) -> Val {
val.as_ref().into()

View File

@ -530,6 +530,10 @@ impl AST {
Self::translate_value(call_def.funcref, &mut ops, root);
ops.push(Op::FCall, func_pos);
}
Expression::Cast(cast_def) => {
Self::translate_expr(*cast_def.target, &mut ops, root);
ops.push(Op::Cast(cast_def.cast_type), cast_def.pos);
}
Expression::Copy(def) => {
Self::translate_value(def.selector, &mut ops, root);
Self::translate_copy(ops, def.fields, def.pos, root);

View File

@ -13,10 +13,11 @@
// limitations under the License.
use std::cell::RefCell;
use std::collections::BTreeSet;
use std::convert::TryInto;
use std::path::PathBuf;
use std::rc::Rc;
use crate::ast::Position;
use crate::ast::{CastType, Position};
use super::environment::Environment;
use super::pointer::OpPointer;
@ -158,6 +159,7 @@ where
let idx = self.ops.idx()?;
match op {
Op::Val(p) => self.push(Rc::new(P(p.clone())), pos)?,
Op::Cast(t) => self.op_cast(t)?,
Op::Sym(s) => self.push(Rc::new(S(s.clone())), pos)?,
Op::DeRef(s) => self.op_deref(s.clone(), &pos)?,
Op::Add => self.op_add(pos)?,
@ -219,6 +221,22 @@ where
Ok(())
}
fn op_cast(&mut self, t: CastType) -> Result<(), Error> {
let (val, pos) = self.pop()?;
if let Value::P(ref p) = val.as_ref() {
self.push(
Rc::new(match t {
CastType::Str => Value::P(Primitive::Str(format!("{}", p))),
CastType::Int => Value::P(Primitive::Int(p.try_into()?)),
CastType::Float => Value::P(Primitive::Float(p.try_into()?)),
CastType::Bool => Value::P(Primitive::Bool(p.try_into()?)),
}),
pos,
)?;
}
Ok(())
}
fn op_typ(&mut self) -> Result<(), Error> {
let (val, pos) = self.pop()?;
let typ_name = match val.as_ref() {

View File

@ -538,6 +538,32 @@ fn tuple_to_call<'a>(
}
}
make_fn!(
cast_expression<SliceIter<Token>, Expression>,
do_each!(
typ => either!(
word!("int"),
word!("float"),
word!("str"),
word!("bool")
),
_ => punct!("("),
expr => expression,
_ => punct!(")"),
(Expression::Cast(CastDef{
cast_type: match typ.fragment.as_str() {
"int" => CastType::Int,
"float" => CastType::Float,
"str" => CastType::Str,
"bool" => CastType::Bool,
_ => unreachable!(),
},
target: Box::new(expr),
pos: typ.pos,
}))
)
);
fn call_expression(input: SliceIter<Token>) -> Result<SliceIter<Token>, Expression> {
let parsed = do_each!(input.clone(),
callee_name => trace_parse!(symbol),
@ -717,6 +743,8 @@ fn unprefixed_expression(input: SliceIter<Token>) -> ParseResult<Expression> {
trace_parse!(format_expression),
trace_parse!(range_expression),
trace_parse!(simple_expression),
// cast parse attempts must happen before call parse attempts.
trace_parse!(cast_expression),
trace_parse!(call_expression),
trace_parse!(copy_expression)
)