Add support for multiple integer types.

This commit is contained in:
2023-06-04 17:31:26 -07:00
parent 7efd2fb796
commit 469fe35e46
19 changed files with 528 additions and 169 deletions

4
examples/basic/test2.ngr Normal file
View File

@@ -0,0 +1,4 @@
// this test is useful for making sure we don't accidentally
// use a signed divide operation anywhere important
a = 96u8 / 160u8;
print a;

View File

@@ -2,12 +2,33 @@
#include <stdio.h> #include <stdio.h>
#include <inttypes.h> #include <inttypes.h>
void print(char *_ignore, char *variable_name, int64_t value) { void print(char *_ignore, char *variable_name, int64_t vtype, int64_t value) {
printf("%s = %" PRId64 "i64\n", variable_name, value); switch(vtype) {
} case /* U8 = */ 10:
printf("%s = %" PRIu8 "u8\n", variable_name, (uint8_t)value);
void caller() { break;
print(NULL, "x", 4); case /* U16 = */ 11:
printf("%s = %" PRIu16 "u16\n", variable_name, (uint16_t)value);
break;
case /* U32 = */ 12:
printf("%s = %" PRIu32 "u32\n", variable_name, (uint32_t)value);
break;
case /* U64 = */ 13:
printf("%s = %" PRIu64 "u64\n", variable_name, (uint64_t)value);
break;
case /* I8 = */ 20:
printf("%s = %" PRIi8 "i8\n", variable_name, (int8_t)value);
break;
case /* I16 = */ 21:
printf("%s = %" PRIi16 "i16\n", variable_name, (int16_t)value);
break;
case /* I32 = */ 22:
printf("%s = %" PRIi32 "i32\n", variable_name, (int32_t)value);
break;
case /* I64 = */ 23:
printf("%s = %" PRIi64 "i64\n", variable_name, value);
break;
}
} }
extern void gogogo(); extern void gogogo();

View File

@@ -31,15 +31,15 @@ mod eval;
mod into_crane; mod into_crane;
mod runtime; mod runtime;
use std::collections::HashMap;
pub use self::error::BackendError; pub use self::error::BackendError;
pub use self::runtime::{RuntimeFunctionError, RuntimeFunctions}; pub use self::runtime::{RuntimeFunctionError, RuntimeFunctions};
use crate::syntax::ConstantType;
use cranelift_codegen::settings::Configurable; use cranelift_codegen::settings::Configurable;
use cranelift_codegen::{isa, settings}; use cranelift_codegen::{isa, settings};
use cranelift_jit::{JITBuilder, JITModule}; use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{default_libcall_names, DataContext, DataId, FuncId, Linkage, Module}; use cranelift_module::{default_libcall_names, DataContext, DataId, FuncId, Linkage, Module};
use cranelift_object::{ObjectBuilder, ObjectModule}; use cranelift_object::{ObjectBuilder, ObjectModule};
use std::collections::HashMap;
use target_lexicon::Triple; use target_lexicon::Triple;
const EMPTY_DATUM: [u8; 8] = [0; 8]; const EMPTY_DATUM: [u8; 8] = [0; 8];
@@ -58,7 +58,7 @@ pub struct Backend<M: Module> {
data_ctx: DataContext, data_ctx: DataContext,
runtime_functions: RuntimeFunctions, runtime_functions: RuntimeFunctions,
defined_strings: HashMap<String, DataId>, defined_strings: HashMap<String, DataId>,
defined_symbols: HashMap<String, DataId>, defined_symbols: HashMap<String, (DataId, ConstantType)>,
output_buffer: Option<String>, output_buffer: Option<String>,
} }
@@ -167,14 +167,18 @@ impl<M: Module> Backend<M> {
/// These variables can be shared between functions, and will be exported from the /// These variables can be shared between functions, and will be exported from the
/// module itself as public data in the case of static compilation. There initial /// module itself as public data in the case of static compilation. There initial
/// value will be null. /// value will be null.
pub fn define_variable(&mut self, name: String) -> Result<DataId, BackendError> { pub fn define_variable(
&mut self,
name: String,
ctype: ConstantType,
) -> Result<DataId, BackendError> {
self.data_ctx.define(Box::new(EMPTY_DATUM)); self.data_ctx.define(Box::new(EMPTY_DATUM));
let id = self let id = self
.module .module
.declare_data(&name, Linkage::Export, true, false)?; .declare_data(&name, Linkage::Export, true, false)?;
self.module.define_data(id, &self.data_ctx)?; self.module.define_data(id, &self.data_ctx)?;
self.data_ctx.clear(); self.data_ctx.clear();
self.defined_symbols.insert(name, id); self.defined_symbols.insert(name, (id, ctype));
Ok(id) Ok(id)
} }

View File

@@ -1,9 +1,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::ir::{Expression, Primitive, Program, Statement, Value, ValueOrRef}; use crate::ir::{Expression, Primitive, Program, Statement, Value, ValueOrRef};
use crate::syntax::ConstantType;
use cranelift_codegen::entity::EntityRef; use cranelift_codegen::entity::EntityRef;
use cranelift_codegen::ir::{ use cranelift_codegen::ir::{
entities, types, Function, GlobalValue, InstBuilder, MemFlags, Signature, UserFuncName, self, entities, types, Function, GlobalValue, InstBuilder, MemFlags, Signature, UserFuncName,
}; };
use cranelift_codegen::isa::CallConv; use cranelift_codegen::isa::CallConv;
use cranelift_codegen::Context; use cranelift_codegen::Context;
@@ -85,12 +86,12 @@ impl<M: Module> Backend<M> {
// Just like with strings, generating the `GlobalValue`s we need can potentially // Just like with strings, generating the `GlobalValue`s we need can potentially
// be a little tricky to do on the fly, so we generate the complete list right // be a little tricky to do on the fly, so we generate the complete list right
// here and then use it later. // here and then use it later.
let pre_defined_symbols: HashMap<String, GlobalValue> = self let pre_defined_symbols: HashMap<String, (GlobalValue, ConstantType)> = self
.defined_symbols .defined_symbols
.iter() .iter()
.map(|(k, v)| { .map(|(k, (v, t))| {
let local_data = self.module.declare_data_in_func(*v, &mut ctx.func); let local_data = self.module.declare_data_in_func(*v, &mut ctx.func);
(k.clone(), local_data) (k.clone(), (local_data, *t))
}) })
.collect(); .collect();
@@ -135,31 +136,47 @@ impl<M: Module> Backend<M> {
// Look up the value for the variable. Because this might be a // Look up the value for the variable. Because this might be a
// global variable (and that requires special logic), we just turn // global variable (and that requires special logic), we just turn
// this into an `Expression` and re-use the logic in that implementation. // this into an `Expression` and re-use the logic in that implementation.
let val = Expression::Reference(ann, var).into_crane( let (val, vtype) = Expression::Reference(ann, var).into_crane(
&mut builder, &mut builder,
&variable_table, &variable_table,
&pre_defined_symbols, &pre_defined_symbols,
)?; )?;
let vtype_repr = builder.ins().iconst(types::I64, vtype as i64);
let casted_val = match vtype {
ConstantType::U64 | ConstantType::I64 => val,
ConstantType::I8 | ConstantType::I16 | ConstantType::I32 => {
builder.ins().sextend(types::I64, val)
}
ConstantType::U8 | ConstantType::U16 | ConstantType::U32 => {
builder.ins().uextend(types::I64, val)
}
};
// Finally, we can generate the call to print. // Finally, we can generate the call to print.
builder builder.ins().call(
.ins() print_func_ref,
.call(print_func_ref, &[buffer_ptr, name_ptr, val]); &[buffer_ptr, name_ptr, vtype_repr, casted_val],
);
} }
// Variable binding is a little more con // Variable binding is a little more con
Statement::Binding(_, var_name, value) => { Statement::Binding(_, var_name, value) => {
// Kick off to the `Expression` implementation to see what value we're going // Kick off to the `Expression` implementation to see what value we're going
// to bind to this variable. // to bind to this variable.
let val = let (val, etype) =
value.into_crane(&mut builder, &variable_table, &pre_defined_symbols)?; value.into_crane(&mut builder, &variable_table, &pre_defined_symbols)?;
// Now the question is: is this a local variable, or a global one? // Now the question is: is this a local variable, or a global one?
if let Some(global_id) = pre_defined_symbols.get(var_name.as_str()) { if let Some((global_id, ctype)) = pre_defined_symbols.get(var_name.as_str()) {
// It's a global variable! In this case, we assume that someone has already // It's a global variable! In this case, we assume that someone has already
// dedicated some space in memory to store this value. We look this location // dedicated some space in memory to store this value. We look this location
// up, and then tell Cranelift to store the value there. // up, and then tell Cranelift to store the value there.
let val_ptr = builder.ins().symbol_value(types::I64, *global_id); assert_eq!(etype, *ctype);
let val_ptr = builder
.ins()
.symbol_value(ir::Type::from(*ctype), *global_id);
builder.ins().store(MemFlags::new(), val, val_ptr, 0); builder.ins().store(MemFlags::new(), val, val_ptr, 0);
} else { } else {
// It's a local variable! In this case, we need to allocate a new Cranelift // It's a local variable! In this case, we need to allocate a new Cranelift
@@ -171,12 +188,10 @@ impl<M: Module> Backend<M> {
next_var_num += 1; next_var_num += 1;
// We can add the variable directly to our local variable map; it's `Copy`. // We can add the variable directly to our local variable map; it's `Copy`.
variable_table.insert(var_name, var); variable_table.insert(var_name, (var, etype));
// Now we tell Cranelift about our new variable, which has type I64 because // Now we tell Cranelift about our new variable!
// everything we have at this point is of type I64. Once it's declare, we builder.declare_var(var, ir::Type::from(etype));
// define it as having the value we computed above.
builder.declare_var(var, types::I64);
builder.def_var(var, val); builder.def_var(var, val);
} }
} }
@@ -231,26 +246,60 @@ impl Expression {
fn into_crane( fn into_crane(
self, self,
builder: &mut FunctionBuilder, builder: &mut FunctionBuilder,
local_variables: &HashMap<ArcIntern<String>, Variable>, local_variables: &HashMap<ArcIntern<String>, (Variable, ConstantType)>,
global_variables: &HashMap<String, GlobalValue>, global_variables: &HashMap<String, (GlobalValue, ConstantType)>,
) -> Result<entities::Value, BackendError> { ) -> Result<(entities::Value, ConstantType), BackendError> {
match self { match self {
// Values are pretty straightforward to compile, mostly because we only // Values are pretty straightforward to compile, mostly because we only
// have one type of variable, and it's an integer type. // have one type of variable, and it's an integer type.
Expression::Value(_, Value::Number(_, v)) => Ok(builder.ins().iconst(types::I64, v)), Expression::Value(_, val) => match val {
Value::I8(_, v) => {
Ok((builder.ins().iconst(types::I8, v as i64), ConstantType::I8))
}
Value::I16(_, v) => Ok((
builder.ins().iconst(types::I16, v as i64),
ConstantType::I16,
)),
Value::I32(_, v) => Ok((
builder.ins().iconst(types::I32, v as i64),
ConstantType::I32,
)),
Value::I64(_, v) => Ok((builder.ins().iconst(types::I64, v), ConstantType::I64)),
Value::U8(_, v) => {
Ok((builder.ins().iconst(types::I8, v as i64), ConstantType::U8))
}
Value::U16(_, v) => Ok((
builder.ins().iconst(types::I16, v as i64),
ConstantType::U16,
)),
Value::U32(_, v) => Ok((
builder.ins().iconst(types::I32, v as i64),
ConstantType::U32,
)),
Value::U64(_, v) => Ok((
builder.ins().iconst(types::I64, v as i64),
ConstantType::U64,
)),
},
Expression::Reference(_, name) => { Expression::Reference(_, name) => {
// first we see if this is a local variable (which is nicer, from an // first we see if this is a local variable (which is nicer, from an
// optimization point of view.) // optimization point of view.)
if let Some(local_var) = local_variables.get(&name) { if let Some((local_var, etype)) = local_variables.get(&name) {
return Ok(builder.use_var(*local_var)); return Ok((builder.use_var(*local_var), *etype));
} }
// then we check to see if this is a global reference, which requires us to // then we check to see if this is a global reference, which requires us to
// first lookup where the value is stored, and then load it. // first lookup where the value is stored, and then load it.
if let Some(global_var) = global_variables.get(name.as_ref()) { if let Some((global_var, etype)) = global_variables.get(name.as_ref()) {
let val_ptr = builder.ins().symbol_value(types::I64, *global_var); let cranelift_type = ir::Type::from(*etype);
return Ok(builder.ins().load(types::I64, MemFlags::new(), val_ptr, 0)); let val_ptr = builder.ins().symbol_value(cranelift_type, *global_var);
return Ok((
builder
.ins()
.load(cranelift_type, MemFlags::new(), val_ptr, 0),
*etype,
));
} }
// this should never happen, because we should have made sure that there are // this should never happen, because we should have made sure that there are
@@ -260,25 +309,27 @@ impl Expression {
Expression::Primitive(_, prim, mut vals) => { Expression::Primitive(_, prim, mut vals) => {
// we're going to use `pop`, so we're going to pull and compile the right value ... // we're going to use `pop`, so we're going to pull and compile the right value ...
let right = let (right, rtype) =
vals.pop() vals.pop()
.unwrap() .unwrap()
.into_crane(builder, local_variables, global_variables)?; .into_crane(builder, local_variables, global_variables)?;
// ... and then the left. // ... and then the left.
let left = let (left, ltype) =
vals.pop() vals.pop()
.unwrap() .unwrap()
.into_crane(builder, local_variables, global_variables)?; .into_crane(builder, local_variables, global_variables)?;
assert_eq!(rtype, ltype, "primitive argument types match");
// then we just need to tell Cranelift how to do each of our primitives! Much // then we just need to tell Cranelift how to do each of our primitives! Much
// like Statements, above, we probably want to eventually shuffle this off into // like Statements, above, we probably want to eventually shuffle this off into
// a separate function (maybe something off `Primitive`), but for now it's simple // a separate function (maybe something off `Primitive`), but for now it's simple
// enough that we just do the `match` here. // enough that we just do the `match` here.
match prim { match prim {
Primitive::Plus => Ok(builder.ins().iadd(left, right)), Primitive::Plus => Ok((builder.ins().iadd(left, right), ltype)),
Primitive::Minus => Ok(builder.ins().isub(left, right)), Primitive::Minus => Ok((builder.ins().isub(left, right), ltype)),
Primitive::Times => Ok(builder.ins().imul(left, right)), Primitive::Times => Ok((builder.ins().imul(left, right), ltype)),
Primitive::Divide => Ok(builder.ins().sdiv(left, right)), Primitive::Divide if rtype.is_signed() => Ok((builder.ins().sdiv(left, right), ltype)),
Primitive::Divide => Ok((builder.ins().udiv(left, right), ltype)),
} }
} }
} }
@@ -291,9 +342,9 @@ impl ValueOrRef {
fn into_crane( fn into_crane(
self, self,
builder: &mut FunctionBuilder, builder: &mut FunctionBuilder,
local_variables: &HashMap<ArcIntern<String>, Variable>, local_variables: &HashMap<ArcIntern<String>, (Variable, ConstantType)>,
global_variables: &HashMap<String, GlobalValue>, global_variables: &HashMap<String, (GlobalValue, ConstantType)>,
) -> Result<entities::Value, BackendError> { ) -> Result<(entities::Value, ConstantType), BackendError> {
Expression::from(self).into_crane(builder, local_variables, global_variables) Expression::from(self).into_crane(builder, local_variables, global_variables)
} }
} }

View File

@@ -8,6 +8,8 @@ use std::fmt::Write;
use target_lexicon::Triple; use target_lexicon::Triple;
use thiserror::Error; use thiserror::Error;
use crate::syntax::ConstantType;
/// An object for querying / using functions built into the runtime. /// An object for querying / using functions built into the runtime.
/// ///
/// Right now, this is a quite a bit of boilerplate for very nebulous /// Right now, this is a quite a bit of boilerplate for very nebulous
@@ -49,7 +51,7 @@ impl RuntimeFunctions {
"print", "print",
Linkage::Import, Linkage::Import,
&Signature { &Signature {
params: vec![string_param, string_param, int64_param], params: vec![string_param, string_param, int64_param, int64_param],
returns: vec![], returns: vec![],
call_conv: CallConv::triple_default(platform), call_conv: CallConv::triple_default(platform),
}, },
@@ -98,13 +100,29 @@ impl RuntimeFunctions {
// we extend with the output, so that multiple JIT'd `Program`s can run concurrently // we extend with the output, so that multiple JIT'd `Program`s can run concurrently
// without stomping over each other's output. If `output_buffer` is NULL, we just print // without stomping over each other's output. If `output_buffer` is NULL, we just print
// to stdout. // to stdout.
extern "C" fn runtime_print(output_buffer: *mut String, name: *const i8, value: i64) { extern "C" fn runtime_print(
output_buffer: *mut String,
name: *const i8,
vtype_repr: i64,
value: i64,
) {
let cstr = unsafe { CStr::from_ptr(name) }; let cstr = unsafe { CStr::from_ptr(name) };
let reconstituted = cstr.to_string_lossy(); let reconstituted = cstr.to_string_lossy();
let vtype = match vtype_repr.try_into() {
Ok(ConstantType::I8) => "i8",
Ok(ConstantType::I16) => "i16",
Ok(ConstantType::I32) => "i32",
Ok(ConstantType::I64) => "i64",
Ok(ConstantType::U8) => "u8",
Ok(ConstantType::U16) => "u16",
Ok(ConstantType::U32) => "u32",
Ok(ConstantType::U64) => "u64",
Err(_) => "<unknown type>",
};
if let Some(output_buffer) = unsafe { output_buffer.as_mut() } { if let Some(output_buffer) = unsafe { output_buffer.as_mut() } {
writeln!(output_buffer, "{} = {}i64", reconstituted, value).unwrap(); writeln!(output_buffer, "{} = {}{}", reconstituted, value, vtype).unwrap();
} else { } else {
println!("{} = {}", reconstituted, value); println!("{} = {}{}", reconstituted, value, vtype);
} }
} }

View File

@@ -87,9 +87,9 @@ mod tests {
let tester = tester.extend(arced("bar"), 2i64.into()); let tester = tester.extend(arced("bar"), 2i64.into());
let tester = tester.extend(arced("goo"), 5i64.into()); let tester = tester.extend(arced("goo"), 5i64.into());
assert_eq!(tester.lookup(arced("foo")), Ok(1.into())); assert_eq!(tester.lookup(arced("foo")), Ok(1i64.into()));
assert_eq!(tester.lookup(arced("bar")), Ok(2.into())); assert_eq!(tester.lookup(arced("bar")), Ok(2i64.into()));
assert_eq!(tester.lookup(arced("goo")), Ok(5.into())); assert_eq!(tester.lookup(arced("goo")), Ok(5i64.into()));
assert!(tester.lookup(arced("baz")).is_err()); assert!(tester.lookup(arced("baz")).is_err());
} }
@@ -103,14 +103,14 @@ mod tests {
check_nested(&tester); check_nested(&tester);
assert_eq!(tester.lookup(arced("foo")), Ok(1.into())); assert_eq!(tester.lookup(arced("foo")), Ok(1i64.into()));
assert!(tester.lookup(arced("bar")).is_err()); assert!(tester.lookup(arced("bar")).is_err());
} }
fn check_nested(env: &EvalEnvironment) { fn check_nested(env: &EvalEnvironment) {
let nested_env = env.extend(arced("bar"), 2i64.into()); let nested_env = env.extend(arced("bar"), 2i64.into());
assert_eq!(nested_env.lookup(arced("foo")), Ok(1.into())); assert_eq!(nested_env.lookup(arced("foo")), Ok(1i64.into()));
assert_eq!(nested_env.lookup(arced("bar")), Ok(2.into())); assert_eq!(nested_env.lookup(arced("bar")), Ok(2i64.into()));
} }
fn arced(s: &str) -> ArcIntern<String> { fn arced(s: &str) -> ArcIntern<String> {

View File

@@ -61,15 +61,69 @@ macro_rules! run_op {
impl Value { impl Value {
fn binary_op(operation: &str, left: &Value, right: &Value) -> Result<Value, PrimOpError> { fn binary_op(operation: &str, left: &Value, right: &Value) -> Result<Value, PrimOpError> {
match left { match left {
// for now we only have one type, but in the future this is Value::I8(x) => match right {
// going to be very irritating. Value::I8(y) => run_op!(operation, x, *y),
_ => Err(PrimOpError::TypeMismatch(
operation.to_string(),
left.clone(),
right.clone(),
)),
},
Value::I16(x) => match right {
Value::I16(y) => run_op!(operation, x, *y),
_ => Err(PrimOpError::TypeMismatch(
operation.to_string(),
left.clone(),
right.clone(),
)),
},
Value::I32(x) => match right {
Value::I32(y) => run_op!(operation, x, *y),
_ => Err(PrimOpError::TypeMismatch(
operation.to_string(),
left.clone(),
right.clone(),
)),
},
Value::I64(x) => match right { Value::I64(x) => match right {
Value::I64(y) => run_op!(operation, x, *y), Value::I64(y) => run_op!(operation, x, *y),
// _ => Err(PrimOpError::TypeMismatch( _ => Err(PrimOpError::TypeMismatch(
// operation.to_string(), operation.to_string(),
// left.clone(), left.clone(),
// right.clone(), right.clone(),
// )), )),
},
Value::U8(x) => match right {
Value::U8(y) => run_op!(operation, x, *y),
_ => Err(PrimOpError::TypeMismatch(
operation.to_string(),
left.clone(),
right.clone(),
)),
},
Value::U16(x) => match right {
Value::U16(y) => run_op!(operation, x, *y),
_ => Err(PrimOpError::TypeMismatch(
operation.to_string(),
left.clone(),
right.clone(),
)),
},
Value::U32(x) => match right {
Value::U32(y) => run_op!(operation, x, *y),
_ => Err(PrimOpError::TypeMismatch(
operation.to_string(),
left.clone(),
right.clone(),
)),
},
Value::U64(x) => match right {
Value::U64(y) => run_op!(operation, x, *y),
_ => Err(PrimOpError::TypeMismatch(
operation.to_string(),
left.clone(),
right.clone(),
)),
}, },
} }
} }

View File

@@ -7,19 +7,75 @@ use std::fmt::Display;
/// by type so that we don't mix them up. /// by type so that we don't mix them up.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Value { pub enum Value {
I8(i8),
I16(i16),
I32(i32),
I64(i64), I64(i64),
U8(u8),
U16(u16),
U32(u32),
U64(u64),
} }
impl Display for Value { impl Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Value::I8(x) => write!(f, "{}i8", x),
Value::I16(x) => write!(f, "{}i16", x),
Value::I32(x) => write!(f, "{}i32", x),
Value::I64(x) => write!(f, "{}i64", x), Value::I64(x) => write!(f, "{}i64", x),
Value::U8(x) => write!(f, "{}u8", x),
Value::U16(x) => write!(f, "{}u16", x),
Value::U32(x) => write!(f, "{}u32", x),
Value::U64(x) => write!(f, "{}u64", x),
} }
} }
} }
impl From<i8> for Value {
fn from(value: i8) -> Self {
Value::I8(value)
}
}
impl From<i16> for Value {
fn from(value: i16) -> Self {
Value::I16(value)
}
}
impl From<i32> for Value {
fn from(value: i32) -> Self {
Value::I32(value)
}
}
impl From<i64> for Value { impl From<i64> for Value {
fn from(value: i64) -> Self { fn from(value: i64) -> Self {
Value::I64(value) Value::I64(value)
} }
} }
impl From<u8> for Value {
fn from(value: u8) -> Self {
Value::U8(value)
}
}
impl From<u16> for Value {
fn from(value: u16) -> Self {
Value::U16(value)
}
}
impl From<u32> for Value {
fn from(value: u32) -> Self {
Value::U32(value)
}
}
impl From<u64> for Value {
fn from(value: u64) -> Self {
Value::U64(value)
}
}

View File

@@ -1,4 +1,4 @@
use crate::syntax::Location; use crate::syntax::{self, ConstantType, Location};
use internment::ArcIntern; use internment::ArcIntern;
use pretty::{DocAllocator, Pretty}; use pretty::{DocAllocator, Pretty};
use proptest::{ use proptest::{
@@ -224,14 +224,21 @@ impl From<ValueOrRef> for Expression {
} }
/// A constant in the IR. /// A constant in the IR.
#[derive(Debug)] ///
/// The optional argument in numeric types is the base that was used by the
/// user to input the number. By retaining it, we can ensure that if we need
/// to print the number back out, we can do so in the form that the user
/// entered it.
#[derive(Clone, Debug)]
pub enum Value { pub enum Value {
/// A numerical constant. I8(Option<u8>, i8),
/// I16(Option<u8>, i16),
/// The optional argument is the base that was used by the user to input I32(Option<u8>, i32),
/// the number. By retaining it, we can ensure that if we need to print the I64(Option<u8>, i64),
/// number back out, we can do so in the form that the user entered it. U8(Option<u8>, u8),
Number(Option<u8>, i64), U16(Option<u8>, u16),
U32(Option<u8>, u32),
U64(Option<u8>, u64),
} }
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Value impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Value
@@ -240,19 +247,39 @@ where
D: ?Sized + DocAllocator<'a, A>, D: ?Sized + DocAllocator<'a, A>,
{ {
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> { fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
match self { let pretty_internal = |opt_base: &Option<u8>, x, t| {
Value::Number(opt_base, value) => { syntax::Value::Number(*opt_base, Some(t), x).pretty(allocator)
let value_str = match opt_base { };
None => format!("{}", value),
Some(2) => format!("0b{:b}", value),
Some(8) => format!("0o{:o}", value),
Some(10) => format!("0d{}", value),
Some(16) => format!("0x{:x}", value),
Some(_) => format!("!!{:x}!!", value),
};
allocator.text(value_str) let pretty_internal_signed = |opt_base, x: i64, t| {
let base = pretty_internal(opt_base, x.unsigned_abs(), t);
allocator.text("-").append(base)
};
match self {
Value::I8(opt_base, value) => {
pretty_internal_signed(opt_base, *value as i64, ConstantType::I8)
} }
Value::I16(opt_base, value) => {
pretty_internal_signed(opt_base, *value as i64, ConstantType::I16)
}
Value::I32(opt_base, value) => {
pretty_internal_signed(opt_base, *value as i64, ConstantType::I32)
}
Value::I64(opt_base, value) => {
pretty_internal_signed(opt_base, *value, ConstantType::I64)
}
Value::U8(opt_base, value) => {
pretty_internal(opt_base, *value as u64, ConstantType::U8)
}
Value::U16(opt_base, value) => {
pretty_internal(opt_base, *value as u64, ConstantType::U16)
}
Value::U32(opt_base, value) => {
pretty_internal(opt_base, *value as u64, ConstantType::U32)
}
Value::U64(opt_base, value) => pretty_internal(opt_base, *value, ConstantType::U64),
} }
} }
} }

View File

@@ -35,7 +35,14 @@ impl Expression {
fn eval(&self, env: &EvalEnvironment) -> Result<Value, EvalError> { fn eval(&self, env: &EvalEnvironment) -> Result<Value, EvalError> {
match self { match self {
Expression::Value(_, v) => match v { Expression::Value(_, v) => match v {
super::Value::Number(_, v) => Ok(Value::I64(*v)), super::Value::I8(_, v) => Ok(Value::I8(*v)),
super::Value::I16(_, v) => Ok(Value::I16(*v)),
super::Value::I32(_, v) => Ok(Value::I32(*v)),
super::Value::I64(_, v) => Ok(Value::I64(*v)),
super::Value::U8(_, v) => Ok(Value::U8(*v)),
super::Value::U16(_, v) => Ok(Value::U16(*v)),
super::Value::U32(_, v) => Ok(Value::U32(*v)),
super::Value::U64(_, v) => Ok(Value::U64(*v)),
}, },
Expression::Reference(_, n) => Ok(env.lookup(n.clone())?), Expression::Reference(_, n) => Ok(env.lookup(n.clone())?),
@@ -49,8 +56,8 @@ impl Expression {
for arg in args.iter() { for arg in args.iter() {
match arg { match arg {
ValueOrRef::Ref(_, n) => arg_values.push(env.lookup(n.clone())?), ValueOrRef::Ref(_, n) => arg_values.push(env.lookup(n.clone())?),
ValueOrRef::Value(_, super::Value::Number(_, v)) => { ValueOrRef::Value(loc, val) => {
arg_values.push(Value::I64(*v)) arg_values.push(Expression::Value(loc.clone(), val.clone()).eval(env)?)
} }
} }
} }
@@ -73,7 +80,7 @@ fn two_plus_three() {
let input = crate::syntax::Program::parse(0, "x = 2 + 3; print x;").expect("parse works"); let input = crate::syntax::Program::parse(0, "x = 2 + 3; print x;").expect("parse works");
let ir = Program::from(input); let ir = Program::from(input);
let output = ir.eval().expect("runs successfully"); let output = ir.eval().expect("runs successfully");
assert_eq!("x = 5i64\n", &output); assert_eq!("x = 5u64\n", &output);
} }
#[test] #[test]
@@ -82,5 +89,5 @@ fn lotsa_math() {
crate::syntax::Program::parse(0, "x = 2 + 3 * 10 / 5 - 1; print x;").expect("parse works"); crate::syntax::Program::parse(0, "x = 2 + 3 * 10 / 5 - 1; print x;").expect("parse works");
let ir = Program::from(input); let ir = Program::from(input);
let output = ir.eval().expect("runs successfully"); let output = ir.eval().expect("runs successfully");
assert_eq!("x = 7i64\n", &output); assert_eq!("x = 7u64\n", &output);
} }

View File

@@ -149,7 +149,17 @@ impl syntax::Expression {
impl From<syntax::Value> for ir::Value { impl From<syntax::Value> for ir::Value {
fn from(value: syntax::Value) -> Self { fn from(value: syntax::Value) -> Self {
match value { match value {
syntax::Value::Number(base, val) => ir::Value::Number(base, val), syntax::Value::Number(base, ty, val) => match ty {
None => ir::Value::U64(base, val),
Some(syntax::ConstantType::I8) => ir::Value::I8(base, val as i8),
Some(syntax::ConstantType::I16) => ir::Value::I16(base, val as i16),
Some(syntax::ConstantType::I32) => ir::Value::I32(base, val as i32),
Some(syntax::ConstantType::I64) => ir::Value::I64(base, val as i64),
Some(syntax::ConstantType::U8) => ir::Value::U8(base, val as u8),
Some(syntax::ConstantType::U16) => ir::Value::U16(base, val as u16),
Some(syntax::ConstantType::U32) => ir::Value::U32(base, val as u32),
Some(syntax::ConstantType::U64) => ir::Value::U64(base, val),
},
} }
} }
} }

View File

@@ -1,6 +1,6 @@
use crate::backend::{Backend, BackendError}; use crate::backend::{Backend, BackendError};
use crate::ir::Program as IR; use crate::ir::Program as IR;
use crate::syntax::{Location, ParserError, Statement}; use crate::syntax::{ConstantType, Location, ParserError, Statement};
use codespan_reporting::diagnostic::Diagnostic; use codespan_reporting::diagnostic::Diagnostic;
use codespan_reporting::files::SimpleFiles; use codespan_reporting::files::SimpleFiles;
use codespan_reporting::term::{self, Config}; use codespan_reporting::term::{self, Config};
@@ -135,7 +135,8 @@ impl REPL {
if let Statement::Binding(_, ref name, _) = syntax { if let Statement::Binding(_, ref name, _) = syntax {
if !self.variable_binding_sites.contains_key(name.as_str()) { if !self.variable_binding_sites.contains_key(name.as_str()) {
self.jitter.define_string(name)?; self.jitter.define_string(name)?;
self.jitter.define_variable(name.clone())?; self.jitter
.define_variable(name.clone(), ConstantType::U64)?;
} }
}; };

View File

@@ -299,18 +299,18 @@ fn order_of_operations() {
Location::new(testfile, 6), Location::new(testfile, 6),
"+".to_string(), "+".to_string(),
vec![ vec![
Expression::Value(Location::new(testfile, 4), Value::Number(None, 1)), Expression::Value(Location::new(testfile, 4), Value::Number(None, None, 1),),
Expression::Primitive( Expression::Primitive(
Location::new(testfile, 10), Location::new(testfile, 10),
"*".to_string(), "*".to_string(),
vec![ vec![
Expression::Value( Expression::Value(
Location::new(testfile, 8), Location::new(testfile, 8),
Value::Number(None, 2), Value::Number(None, None, 2),
), ),
Expression::Value( Expression::Value(
Location::new(testfile, 12), Location::new(testfile, 12),
Value::Number(None, 3), Value::Number(None, None, 3),
), ),
] ]
) )

View File

@@ -1,12 +1,11 @@
use std::collections::HashSet; use crate::syntax::ast::{ConstantType, Expression, Program, Statement, Value};
use crate::syntax::ast::{Expression, Program, Statement, Value};
use crate::syntax::location::Location; use crate::syntax::location::Location;
use proptest::sample::select; use proptest::sample::select;
use proptest::{ use proptest::{
prelude::{Arbitrary, BoxedStrategy, Strategy}, prelude::{Arbitrary, BoxedStrategy, Strategy},
strategy::{Just, Union}, strategy::{Just, Union},
}; };
use std::collections::HashMap;
const VALID_VARIABLE_NAMES: &str = r"[a-z][a-zA-Z0-9_]*"; const VALID_VARIABLE_NAMES: &str = r"[a-z][a-zA-Z0-9_]*";
@@ -27,36 +26,38 @@ impl Arbitrary for Program {
type Strategy = BoxedStrategy<Self>; type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
let optionals = Vec::<Option<Name>>::arbitrary(); let optionals = Vec::<Option<(Name, ConstantType)>>::arbitrary();
optionals optionals
.prop_flat_map(|mut possible_names| { .prop_flat_map(|mut possible_names| {
let mut statements = Vec::new(); let mut statements = Vec::new();
let mut defined_variables: HashSet<String> = HashSet::new(); let mut defined_variables: HashMap<String, ConstantType> = HashMap::new();
for possible_name in possible_names.drain(..) { for possible_name in possible_names.drain(..) {
match possible_name { match possible_name {
None if defined_variables.is_empty() => continue, None if defined_variables.is_empty() => continue,
None => statements.push( None => statements.push(
Union::new(defined_variables.iter().map(|name| { Union::new(defined_variables.keys().map(|name| {
Just(Statement::Print(Location::manufactured(), name.to_string())) Just(Statement::Print(Location::manufactured(), name.to_string()))
})) }))
.boxed(), .boxed(),
), ),
Some(new_name) => { Some((new_name, new_type)) => {
let closures_name = new_name.0.clone(); let closures_name = new_name.0.clone();
let retval = let retval = Expression::arbitrary_with((
Expression::arbitrary_with(Some(defined_variables.clone())) Some(defined_variables.clone()),
.prop_map(move |exp| { Some(new_type),
Statement::Binding( ))
Location::manufactured(), .prop_map(move |exp| {
closures_name.clone(), Statement::Binding(
exp, Location::manufactured(),
) closures_name.clone(),
}) exp,
.boxed(); )
})
.boxed();
defined_variables.insert(new_name.0); defined_variables.insert(new_name.0, new_type);
statements.push(retval); statements.push(retval);
} }
} }
@@ -70,7 +71,7 @@ impl Arbitrary for Program {
} }
impl Arbitrary for Statement { impl Arbitrary for Statement {
type Parameters = Option<HashSet<String>>; type Parameters = Option<HashMap<String, ConstantType>>;
type Strategy = BoxedStrategy<Self>; type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
@@ -79,7 +80,7 @@ impl Arbitrary for Statement {
let binding_strategy = ( let binding_strategy = (
VALID_VARIABLE_NAMES, VALID_VARIABLE_NAMES,
Expression::arbitrary_with(duplicated_args), Expression::arbitrary_with((duplicated_args, None)),
) )
.prop_map(|(name, exp)| Statement::Binding(Location::manufactured(), name, exp)) .prop_map(|(name, exp)| Statement::Binding(Location::manufactured(), name, exp))
.boxed(); .boxed();
@@ -89,7 +90,7 @@ impl Arbitrary for Statement {
} else { } else {
let print_strategy = Union::new( let print_strategy = Union::new(
defined_variables defined_variables
.iter() .keys()
.map(|x| Just(Statement::Print(Location::manufactured(), x.to_string()))), .map(|x| Just(Statement::Print(Location::manufactured(), x.to_string()))),
) )
.boxed(); .boxed();
@@ -100,20 +101,25 @@ impl Arbitrary for Statement {
} }
impl Arbitrary for Expression { impl Arbitrary for Expression {
type Parameters = Option<HashSet<String>>; type Parameters = (Option<HashMap<String, ConstantType>>, Option<ConstantType>);
type Strategy = BoxedStrategy<Self>; type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { fn arbitrary_with((env, target_type): Self::Parameters) -> Self::Strategy {
let defined_variables = args.unwrap_or_default(); let defined_variables = env.unwrap_or_default();
let mut acceptable_variables = defined_variables
.iter()
.filter(|(_, ctype)| Some(**ctype) == target_type)
.map(|(x, _)| x)
.peekable();
let value_strategy = Value::arbitrary() let value_strategy = Value::arbitrary_with(target_type)
.prop_map(move |x| Expression::Value(Location::manufactured(), x)) .prop_map(move |x| Expression::Value(Location::manufactured(), x))
.boxed(); .boxed();
let leaf_strategy = if defined_variables.is_empty() { let leaf_strategy = if acceptable_variables.peek().is_none() {
value_strategy value_strategy
} else { } else {
let reference_strategy = Union::new(defined_variables.iter().map(|x| { let reference_strategy = Union::new(acceptable_variables.map(|x| {
Just(Expression::Reference( Just(Expression::Reference(
Location::manufactured(), Location::manufactured(),
x.to_owned(), x.to_owned(),
@@ -138,10 +144,10 @@ impl Arbitrary for Expression {
} }
impl Arbitrary for Value { impl Arbitrary for Value {
type Parameters = (); type Parameters = Option<ConstantType>;
type Strategy = BoxedStrategy<Self>; type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { fn arbitrary_with(target_type: Self::Parameters) -> Self::Strategy {
let base_strategy = Union::new([ let base_strategy = Union::new([
Just(None::<u8>), Just(None::<u8>),
Just(Some(2)), Just(Some(2)),
@@ -150,10 +156,47 @@ impl Arbitrary for Value {
Just(Some(16)), Just(Some(16)),
]); ]);
let value_strategy = i64::arbitrary(); let type_strategy = if target_type.is_some() {
Just(target_type).boxed()
} else {
proptest::option::of(ConstantType::arbitrary()).boxed()
};
let value_strategy = u64::arbitrary();
(base_strategy, value_strategy) (base_strategy, type_strategy, value_strategy)
.prop_map(move |(base, value)| Value::Number(base, value)) .prop_map(move |(base, ty, value)| {
let converted_value = match ty {
Some(ConstantType::I8) => value % (i8::MAX as u64),
Some(ConstantType::U8) => value % (u8::MAX as u64),
Some(ConstantType::I16) => value % (i16::MAX as u64),
Some(ConstantType::U16) => value % (u16::MAX as u64),
Some(ConstantType::I32) => value % (i32::MAX as u64),
Some(ConstantType::U32) => value % (u32::MAX as u64),
Some(ConstantType::I64) => value % (i64::MAX as u64),
Some(ConstantType::U64) => value,
None => value,
};
Value::Number(base, ty, converted_value)
})
.boxed() .boxed()
} }
} }
impl Arbitrary for ConstantType {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
Union::new([
Just(ConstantType::I8),
Just(ConstantType::I16),
Just(ConstantType::I32),
Just(ConstantType::I64),
Just(ConstantType::U8),
Just(ConstantType::U16),
Just(ConstantType::U32),
Just(ConstantType::U64),
])
.boxed()
}
}

View File

@@ -1,3 +1,4 @@
pub use crate::syntax::tokens::ConstantType;
use crate::syntax::Location; use crate::syntax::Location;
/// The set of valid binary operators. /// The set of valid binary operators.
@@ -76,7 +77,7 @@ impl PartialEq for Expression {
Expression::Cast(_, t1, e1) => match other { Expression::Cast(_, t1, e1) => match other {
Expression::Cast(_, t2, e2) => t1 == t2 && e1 == e2, Expression::Cast(_, t2, e2) => t1 == t2 && e1 == e2,
_ => false, _ => false,
} },
Expression::Primitive(_, prim1, args1) => match other { Expression::Primitive(_, prim1, args1) => match other {
Expression::Primitive(_, prim2, args2) => prim1 == prim2 && args1 == args2, Expression::Primitive(_, prim2, args2) => prim1 == prim2 && args1 == args2,
_ => false, _ => false,
@@ -88,6 +89,12 @@ impl PartialEq for Expression {
/// A value from the source syntax /// A value from the source syntax
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum Value { pub enum Value {
/// The value of the number, and an optional base that it was written in /// The value of the number, an optional base that it was written in, and any
Number(Option<u8>, i64), /// type information provided.
///
/// u64 is chosen because it should be big enough to carry the amount of
/// information we need, and technically we interpret -4 as the primitive unary
/// operation "-" on the number 4. We'll translate this into a type-specific
/// number at a later time.
Number(Option<u8>, Option<ConstantType>, u64),
} }

View File

@@ -1,7 +1,7 @@
use internment::ArcIntern; use internment::ArcIntern;
use crate::eval::{EvalEnvironment, EvalError, Value}; use crate::eval::{EvalEnvironment, EvalError, Value};
use crate::syntax::{Expression, Program, Statement}; use crate::syntax::{ConstantType, Expression, Program, Statement};
impl Program { impl Program {
/// Evaluate the program, returning either an error or what it prints out when run. /// Evaluate the program, returning either an error or what it prints out when run.
@@ -43,7 +43,18 @@ impl Expression {
fn eval(&self, env: &EvalEnvironment) -> Result<Value, EvalError> { fn eval(&self, env: &EvalEnvironment) -> Result<Value, EvalError> {
match self { match self {
Expression::Value(_, v) => match v { Expression::Value(_, v) => match v {
super::Value::Number(_, v) => Ok(Value::I64(*v)), super::Value::Number(_, ty, v) => match ty {
None => Ok(Value::U64(*v)),
// FIXME: make these types validate their input size
Some(ConstantType::I8) => Ok(Value::I8(*v as i8)),
Some(ConstantType::I16) => Ok(Value::I16(*v as i16)),
Some(ConstantType::I32) => Ok(Value::I32(*v as i32)),
Some(ConstantType::I64) => Ok(Value::I64(*v as i64)),
Some(ConstantType::U8) => Ok(Value::U8(*v as u8)),
Some(ConstantType::U16) => Ok(Value::U16(*v as u16)),
Some(ConstantType::U32) => Ok(Value::U32(*v as u32)),
Some(ConstantType::U64) => Ok(Value::U64(*v)),
},
}, },
Expression::Reference(_, n) => Ok(env.lookup(ArcIntern::new(n.clone()))?), Expression::Reference(_, n) => Ok(env.lookup(ArcIntern::new(n.clone()))?),
@@ -68,12 +79,12 @@ impl Expression {
fn two_plus_three() { fn two_plus_three() {
let input = Program::parse(0, "x = 2 + 3; print x;").expect("parse works"); let input = Program::parse(0, "x = 2 + 3; print x;").expect("parse works");
let output = input.eval().expect("runs successfully"); let output = input.eval().expect("runs successfully");
assert_eq!("x = 5i64\n", &output); assert_eq!("x = 5u64\n", &output);
} }
#[test] #[test]
fn lotsa_math() { fn lotsa_math() {
let input = Program::parse(0, "x = 2 + 3 * 10 / 5 - 1; print x;").expect("parse works"); let input = Program::parse(0, "x = 2 + 3 * 10 / 5 - 1; print x;").expect("parse works");
let output = input.eval().expect("runs successfully"); let output = input.eval().expect("runs successfully");
assert_eq!("x = 7i64\n", &output); assert_eq!("x = 7u64\n", &output);
} }

View File

@@ -46,7 +46,7 @@ extern {
// to name and use "their value", you get their source location. // to name and use "their value", you get their source location.
// For these, we want "their value" to be their actual contents, // For these, we want "their value" to be their actual contents,
// which is why we put their types in angle brackets. // which is why we put their types in angle brackets.
"<num>" => Token::Number((<Option<u8>>,<Option<ConstantType>>,<i64>)), "<num>" => Token::Number((<Option<u8>>,<Option<ConstantType>>,<u64>)),
"<var>" => Token::Variable(<ArcIntern<String>>), "<var>" => Token::Variable(<ArcIntern<String>>),
} }
} }
@@ -150,20 +150,7 @@ AtomicExpression: Expression = {
// just a variable reference // just a variable reference
<l:@L> <v:"<var>"> => Expression::Reference(Location::new(file_idx, l), v.to_string()), <l:@L> <v:"<var>"> => Expression::Reference(Location::new(file_idx, l), v.to_string()),
// just a number // just a number
<l:@L> <n:"<num>"> => { <l:@L> <n:"<num>"> => Expression::Value(Location::new(file_idx, l), Value::Number(n.0, n.1, n.2)),
let val = Value::Number(n.0, n.2);
Expression::Value(Location::new(file_idx, l), val)
},
// a tricky case: also just a number, but using a negative sign. an
// alternative way to do this -- and we may do this eventually -- is
// to implement a unary negation expression. this has the odd effect
// that the user never actually writes down a negative number; they just
// write positive numbers which are immediately sent to a negation
// primitive!
<l:@L> "-" <n:"<num>"> => {
let val = Value::Number(n.0, -n.2);
Expression::Value(Location::new(file_idx, l), val)
},
// finally, let people parenthesize expressions and get back to a // finally, let people parenthesize expressions and get back to a
// lower precedence // lower precedence
"(" <e:Expression> ")" => e, "(" <e:Expression> ")" => e,

View File

@@ -1,6 +1,8 @@
use crate::syntax::ast::{Expression, Program, Statement, Value, BINARY_OPERATORS}; use crate::syntax::ast::{Expression, Program, Statement, Value, BINARY_OPERATORS};
use pretty::{DocAllocator, DocBuilder, Pretty}; use pretty::{DocAllocator, DocBuilder, Pretty};
use super::ConstantType;
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Program impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Program
where where
A: 'a, A: 'a,
@@ -50,10 +52,10 @@ where
match self { match self {
Expression::Value(_, val) => val.pretty(allocator), Expression::Value(_, val) => val.pretty(allocator),
Expression::Reference(_, var) => allocator.text(var.to_string()), Expression::Reference(_, var) => allocator.text(var.to_string()),
Expression::Cast(_, t, e) => Expression::Cast(_, t, e) => allocator
allocator.text(t.clone()) .text(t.clone())
.angles() .angles()
.append(e.pretty(allocator)), .append(e.pretty(allocator)),
Expression::Primitive(_, op, exprs) if BINARY_OPERATORS.contains(&op.as_ref()) => { Expression::Primitive(_, op, exprs) if BINARY_OPERATORS.contains(&op.as_ref()) => {
assert_eq!( assert_eq!(
exprs.len(), exprs.len(),
@@ -88,15 +90,14 @@ where
{ {
fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> {
match self { match self {
Value::Number(opt_base, value) => { Value::Number(opt_base, ty, value) => {
let sign = if *value < 0 { "-" } else { "" };
let value_str = match opt_base { let value_str = match opt_base {
None => format!("{}", value), None => format!("{}{}", value, type_suffix(ty)),
Some(2) => format!("{}0b{:b}", sign, value.abs()), Some(2) => format!("0b{:b}{}", value, type_suffix(ty)),
Some(8) => format!("{}0o{:o}", sign, value.abs()), Some(8) => format!("0o{:o}{}", value, type_suffix(ty)),
Some(10) => format!("{}0d{}", sign, value.abs()), Some(10) => format!("0d{}{}", value, type_suffix(ty)),
Some(16) => format!("{}0x{:x}", sign, value.abs()), Some(16) => format!("0x{:x}{}", value, type_suffix(ty)),
Some(_) => format!("!!{}{:x}!!", sign, value.abs()), Some(_) => format!("!!{:x}{}!!", value, type_suffix(ty)),
}; };
allocator.text(value_str) allocator.text(value_str)
@@ -105,6 +106,20 @@ where
} }
} }
fn type_suffix(x: &Option<ConstantType>) -> &'static str {
match x {
None => "",
Some(ConstantType::I8) => "i8",
Some(ConstantType::I16) => "i16",
Some(ConstantType::I32) => "i32",
Some(ConstantType::I64) => "i64",
Some(ConstantType::U8) => "u8",
Some(ConstantType::U16) => "u16",
Some(ConstantType::U32) => "u32",
Some(ConstantType::U64) => "u64",
}
}
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct CommaSep {} struct CommaSep {}

View File

@@ -66,7 +66,7 @@ pub enum Token {
#[regex(r"0d[0-9]+(u8|i8|u16|i16|u32|i32|u64|i64)?", |v| parse_number(Some(10), v))] #[regex(r"0d[0-9]+(u8|i8|u16|i16|u32|i32|u64|i64)?", |v| parse_number(Some(10), v))]
#[regex(r"0x[0-9a-fA-F]+(u8|i8|u16|i16|u32|i32|u64|i64)?", |v| parse_number(Some(16), v))] #[regex(r"0x[0-9a-fA-F]+(u8|i8|u16|i16|u32|i32|u64|i64)?", |v| parse_number(Some(16), v))]
#[regex(r"[0-9]+(u8|i8|u16|i16|u32|i32|u64|i64)?", |v| parse_number(None, v))] #[regex(r"[0-9]+(u8|i8|u16|i16|u32|i32|u64|i64)?", |v| parse_number(None, v))]
Number((Option<u8>, Option<ConstantType>, i64)), Number((Option<u8>, Option<ConstantType>, u64)),
// Variables; this is a very standard, simple set of characters // Variables; this is a very standard, simple set of characters
// for variables, but feel free to experiment with more complicated // for variables, but feel free to experiment with more complicated
@@ -143,16 +143,59 @@ impl Token {
} }
} }
#[repr(i64)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ConstantType { pub enum ConstantType {
U8, U8 = 10,
U16, U16 = 11,
U32, U32 = 12,
U64, U64 = 13,
I8, I8 = 20,
I16, I16 = 21,
I32, I32 = 22,
I64, I64 = 23,
}
impl From<ConstantType> for cranelift_codegen::ir::Type {
fn from(value: ConstantType) -> Self {
match value {
ConstantType::I8 | ConstantType::U8 => cranelift_codegen::ir::types::I8,
ConstantType::I16 | ConstantType::U16 => cranelift_codegen::ir::types::I16,
ConstantType::I32 | ConstantType::U32 => cranelift_codegen::ir::types::I32,
ConstantType::I64 | ConstantType::U64 => cranelift_codegen::ir::types::I64,
}
}
}
impl ConstantType {
/// Returns true if the given type is (a) numeric and (b) signed;
pub fn is_signed(&self) -> bool {
matches!(self, ConstantType::I8 | ConstantType::I16 | ConstantType::I32 | ConstantType::I64)
}
}
#[derive(Debug, Error, PartialEq)]
pub enum InvalidConstantType {
#[error("Unrecognized constant {0} for constant type")]
Value(i64),
}
impl TryFrom<i64> for ConstantType {
type Error = InvalidConstantType;
fn try_from(value: i64) -> Result<Self, Self::Error> {
match value {
10 => Ok(ConstantType::U8),
11 => Ok(ConstantType::U16),
12 => Ok(ConstantType::U32),
13 => Ok(ConstantType::U64),
20 => Ok(ConstantType::I8),
21 => Ok(ConstantType::I16),
22 => Ok(ConstantType::I32),
23 => Ok(ConstantType::I64),
_ => Err(InvalidConstantType::Value(value)),
}
}
} }
/// Parse a number in the given base, return a pair of the base and the /// Parse a number in the given base, return a pair of the base and the
@@ -162,7 +205,7 @@ pub enum ConstantType {
fn parse_number( fn parse_number(
base: Option<u8>, base: Option<u8>,
value: &Lexer<Token>, value: &Lexer<Token>,
) -> Result<(Option<u8>, Option<ConstantType>, i64), ParseIntError> { ) -> Result<(Option<u8>, Option<ConstantType>, u64), ParseIntError> {
let (radix, strval) = match base { let (radix, strval) = match base {
None => (10, value.slice()), None => (10, value.slice()),
Some(radix) => (radix, &value.slice()[2..]), Some(radix) => (radix, &value.slice()[2..]),
@@ -188,7 +231,7 @@ fn parse_number(
(None, strval) (None, strval)
}; };
let intval = i64::from_str_radix(strval, radix as u32)?; let intval = u64::from_str_radix(strval, radix as u32)?;
Ok((base, declared_type, intval)) Ok((base, declared_type, intval))
} }