aiken/crates/aiken-lang/src/expr.rs

666 lines
18 KiB
Rust

use std::sync::Arc;
use vec1::Vec1;
use crate::{
ast::{
Annotation, Arg, AssignmentKind, BinOp, ByteArrayFormatPreference, CallArg,
DefinitionLocation, IfBranch, Pattern, RecordUpdateSpread, Span, TraceKind, TypedClause,
TypedRecordUpdateArg, UnOp, UntypedClause, UntypedRecordUpdateArg,
},
builtins::void,
tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor},
};
#[derive(Debug, Clone, PartialEq)]
pub enum TypedExpr {
Int {
location: Span,
tipo: Arc<Type>,
value: String,
},
String {
location: Span,
tipo: Arc<Type>,
value: String,
},
ByteArray {
location: Span,
tipo: Arc<Type>,
bytes: Vec<u8>,
},
Sequence {
location: Span,
expressions: Vec<Self>,
},
/// A chain of pipe expressions.
/// By this point the type checker has expanded it into a series of
/// assignments and function calls, but we still have a Pipeline AST node as
/// even though it is identical to `Sequence` we want to use different
/// locations when showing it in error messages, etc.
Pipeline {
location: Span,
expressions: Vec<Self>,
},
Var {
location: Span,
constructor: ValueConstructor,
name: String,
},
Fn {
location: Span,
tipo: Arc<Type>,
is_capture: bool,
args: Vec<Arg<Arc<Type>>>,
body: Box<Self>,
return_annotation: Option<Annotation>,
},
List {
location: Span,
tipo: Arc<Type>,
elements: Vec<Self>,
tail: Option<Box<Self>>,
},
Call {
location: Span,
tipo: Arc<Type>,
fun: Box<Self>,
args: Vec<CallArg<Self>>,
},
BinOp {
location: Span,
tipo: Arc<Type>,
name: BinOp,
left: Box<Self>,
right: Box<Self>,
},
Assignment {
location: Span,
tipo: Arc<Type>,
value: Box<Self>,
pattern: Pattern<PatternConstructor, Arc<Type>>,
kind: AssignmentKind,
},
Trace {
location: Span,
tipo: Arc<Type>,
then: Box<Self>,
text: Box<Self>,
},
When {
location: Span,
tipo: Arc<Type>,
subject: Box<Self>,
clauses: Vec<TypedClause>,
},
If {
location: Span,
branches: Vec1<IfBranch<Self>>,
final_else: Box<Self>,
tipo: Arc<Type>,
},
RecordAccess {
location: Span,
tipo: Arc<Type>,
label: String,
index: u64,
record: Box<Self>,
},
ModuleSelect {
location: Span,
tipo: Arc<Type>,
label: String,
module_name: String,
module_alias: String,
constructor: ModuleValueConstructor,
},
Tuple {
location: Span,
tipo: Arc<Type>,
elems: Vec<Self>,
},
TupleIndex {
location: Span,
tipo: Arc<Type>,
index: usize,
tuple: Box<Self>,
},
ErrorTerm {
location: Span,
tipo: Arc<Type>,
},
RecordUpdate {
location: Span,
tipo: Arc<Type>,
spread: Box<Self>,
args: Vec<TypedRecordUpdateArg>,
},
UnOp {
location: Span,
value: Box<Self>,
tipo: Arc<Type>,
op: UnOp,
},
}
impl TypedExpr {
pub fn tipo(&self) -> Arc<Type> {
match self {
Self::Var { constructor, .. } => constructor.tipo.clone(),
Self::Trace { then, .. } => then.tipo(),
Self::Fn { tipo, .. }
| Self::Int { tipo, .. }
| Self::ErrorTerm { tipo, .. }
| Self::When { tipo, .. }
| Self::List { tipo, .. }
| Self::Call { tipo, .. }
| Self::If { tipo, .. }
| Self::UnOp { tipo, .. }
| Self::BinOp { tipo, .. }
| Self::Tuple { tipo, .. }
| Self::String { tipo, .. }
| Self::ByteArray { tipo, .. }
| Self::TupleIndex { tipo, .. }
| Self::Assignment { tipo, .. }
| Self::ModuleSelect { tipo, .. }
| Self::RecordAccess { tipo, .. }
| Self::RecordUpdate { tipo, .. } => tipo.clone(),
Self::Pipeline { expressions, .. } | Self::Sequence { expressions, .. } => {
expressions.last().map(TypedExpr::tipo).unwrap_or_else(void)
}
}
}
pub fn is_literal(&self) -> bool {
matches!(
self,
Self::Int { .. }
| Self::List { .. }
| Self::Tuple { .. }
| Self::String { .. }
| Self::ByteArray { .. }
)
}
/// Returns `true` if the typed expr is [`Assignment`].
pub fn is_assignment(&self) -> bool {
matches!(self, Self::Assignment { .. })
}
pub fn definition_location(&self) -> Option<DefinitionLocation<'_>> {
match self {
TypedExpr::Fn { .. }
| TypedExpr::Int { .. }
| TypedExpr::Trace { .. }
| TypedExpr::List { .. }
| TypedExpr::Call { .. }
| TypedExpr::When { .. }
| TypedExpr::ErrorTerm { .. }
| TypedExpr::BinOp { .. }
| TypedExpr::Tuple { .. }
| TypedExpr::UnOp { .. }
| TypedExpr::String { .. }
| TypedExpr::Sequence { .. }
| TypedExpr::Pipeline { .. }
| TypedExpr::ByteArray { .. }
| TypedExpr::Assignment { .. }
| TypedExpr::TupleIndex { .. }
| TypedExpr::RecordAccess { .. } => None,
TypedExpr::If { .. } => None,
// TODO: test
// TODO: definition
TypedExpr::RecordUpdate { .. } => None,
// TODO: test
TypedExpr::ModuleSelect {
module_name,
constructor,
..
} => Some(DefinitionLocation {
module: Some(module_name.as_str()),
span: constructor.location(),
}),
// TODO: test
TypedExpr::Var { constructor, .. } => Some(constructor.definition_location()),
}
}
pub fn type_defining_location(&self) -> Span {
match self {
Self::Fn { location, .. }
| Self::Int { location, .. }
| Self::Var { location, .. }
| Self::Trace { location, .. }
| Self::ErrorTerm { location, .. }
| Self::When { location, .. }
| Self::Call { location, .. }
| Self::List { location, .. }
| Self::BinOp { location, .. }
| Self::Tuple { location, .. }
| Self::String { location, .. }
| Self::UnOp { location, .. }
| Self::Pipeline { location, .. }
| Self::ByteArray { location, .. }
| Self::Assignment { location, .. }
| Self::TupleIndex { location, .. }
| Self::ModuleSelect { location, .. }
| Self::RecordAccess { location, .. }
| Self::RecordUpdate { location, .. } => *location,
Self::If { branches, .. } => branches.first().body.type_defining_location(),
Self::Sequence {
expressions,
location,
..
} => expressions
.last()
.map(TypedExpr::location)
.unwrap_or(*location),
}
}
pub fn location(&self) -> Span {
match self {
Self::Fn { location, .. }
| Self::Int { location, .. }
| Self::Trace { location, .. }
| Self::Var { location, .. }
| Self::ErrorTerm { location, .. }
| Self::When { location, .. }
| Self::Call { location, .. }
| Self::If { location, .. }
| Self::List { location, .. }
| Self::BinOp { location, .. }
| Self::Tuple { location, .. }
| Self::String { location, .. }
| Self::UnOp { location, .. }
| Self::Sequence { location, .. }
| Self::Pipeline { location, .. }
| Self::ByteArray { location, .. }
| Self::Assignment { location, .. }
| Self::TupleIndex { location, .. }
| Self::ModuleSelect { location, .. }
| Self::RecordAccess { location, .. }
| Self::RecordUpdate { location, .. } => *location,
}
}
// This could be optimised in places to exit early if the first of a series
// of expressions is after the byte index.
pub fn find_node(&self, byte_index: usize) -> Option<&Self> {
if !self.location().contains(byte_index) {
return None;
}
match self {
TypedExpr::ErrorTerm { .. }
| TypedExpr::Var { .. }
| TypedExpr::Int { .. }
| TypedExpr::String { .. }
| TypedExpr::ByteArray { .. }
| TypedExpr::ModuleSelect { .. } => Some(self),
TypedExpr::Trace { text, then, .. } => text
.find_node(byte_index)
.or_else(|| then.find_node(byte_index))
.or(Some(self)),
TypedExpr::Pipeline { expressions, .. } | TypedExpr::Sequence { expressions, .. } => {
expressions.iter().find_map(|e| e.find_node(byte_index))
}
TypedExpr::Fn { body, .. } => body.find_node(byte_index).or(Some(self)),
TypedExpr::Tuple {
elems: elements, ..
}
| TypedExpr::List { elements, .. } => elements
.iter()
.find_map(|e| e.find_node(byte_index))
.or(Some(self)),
TypedExpr::Call { fun, args, .. } => args
.iter()
.find_map(|arg| arg.find_node(byte_index))
.or_else(|| fun.find_node(byte_index))
.or(Some(self)),
TypedExpr::BinOp { left, right, .. } => left
.find_node(byte_index)
.or_else(|| right.find_node(byte_index)),
TypedExpr::Assignment { value, .. } => value.find_node(byte_index),
TypedExpr::When {
subject, clauses, ..
} => subject
.find_node(byte_index)
.or_else(|| {
clauses
.iter()
.find_map(|clause| clause.find_node(byte_index))
})
.or(Some(self)),
TypedExpr::RecordAccess {
record: expression, ..
}
| TypedExpr::TupleIndex {
tuple: expression, ..
} => expression.find_node(byte_index).or(Some(self)),
TypedExpr::RecordUpdate { spread, args, .. } => args
.iter()
.find_map(|arg| arg.find_node(byte_index))
.or_else(|| spread.find_node(byte_index))
.or(Some(self)),
TypedExpr::If {
branches,
final_else,
..
} => branches
.iter()
.find_map(|branch| {
branch
.condition
.find_node(byte_index)
.or_else(|| branch.body.find_node(byte_index))
})
.or_else(|| final_else.find_node(byte_index))
.or(Some(self)),
TypedExpr::UnOp { value, .. } => value.find_node(byte_index).or(Some(self)),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum UntypedExpr {
Int {
location: Span,
value: String,
},
String {
location: Span,
value: String,
},
Sequence {
location: Span,
expressions: Vec<Self>,
},
Var {
location: Span,
name: String,
},
Fn {
location: Span,
is_capture: bool,
arguments: Vec<Arg<()>>,
body: Box<Self>,
return_annotation: Option<Annotation>,
},
List {
location: Span,
elements: Vec<Self>,
tail: Option<Box<Self>>,
},
Call {
arguments: Vec<CallArg<Self>>,
fun: Box<Self>,
location: Span,
},
BinOp {
location: Span,
name: BinOp,
left: Box<Self>,
right: Box<Self>,
},
ByteArray {
location: Span,
bytes: Vec<u8>,
preferred_format: ByteArrayFormatPreference,
},
PipeLine {
expressions: Vec1<Self>,
one_liner: bool,
},
Assignment {
location: Span,
value: Box<Self>,
pattern: Pattern<(), ()>,
kind: AssignmentKind,
annotation: Option<Annotation>,
},
Trace {
kind: TraceKind,
location: Span,
then: Box<Self>,
text: Box<Self>,
},
TraceIfFalse {
location: Span,
value: Box<Self>,
},
When {
location: Span,
subject: Box<Self>,
clauses: Vec<UntypedClause>,
},
If {
location: Span,
branches: Vec1<IfBranch<Self>>,
final_else: Box<Self>,
},
FieldAccess {
location: Span,
label: String,
container: Box<Self>,
},
Tuple {
location: Span,
elems: Vec<Self>,
},
TupleIndex {
location: Span,
index: usize,
tuple: Box<Self>,
},
ErrorTerm {
location: Span,
},
RecordUpdate {
location: Span,
constructor: Box<Self>,
spread: RecordUpdateSpread,
arguments: Vec<UntypedRecordUpdateArg>,
},
UnOp {
op: UnOp,
location: Span,
value: Box<Self>,
},
}
pub const DEFAULT_TODO_STR: &str = "aiken::todo";
pub const DEFAULT_ERROR_STR: &str = "aiken::error";
impl UntypedExpr {
pub fn todo(location: Span, reason: Option<Self>) -> Self {
UntypedExpr::Trace {
location,
kind: TraceKind::Todo,
then: Box::new(UntypedExpr::ErrorTerm { location }),
text: Box::new(reason.unwrap_or_else(|| UntypedExpr::String {
location,
value: DEFAULT_TODO_STR.to_string(),
})),
}
}
pub fn error(location: Span, reason: Option<Self>) -> Self {
UntypedExpr::Trace {
location,
kind: TraceKind::Error,
then: Box::new(UntypedExpr::ErrorTerm { location }),
text: Box::new(reason.unwrap_or_else(|| UntypedExpr::String {
location,
value: DEFAULT_ERROR_STR.to_string(),
})),
}
}
pub fn append_in_sequence(self, next: Self) -> Self {
let location = Span {
start: self.location().start,
end: next.location().end,
};
match (self.clone(), next.clone()) {
(
Self::Sequence {
expressions: mut current_expressions,
..
},
Self::Sequence {
expressions: mut next_expressions,
..
},
) => {
current_expressions.append(&mut next_expressions);
Self::Sequence {
location,
expressions: current_expressions,
}
}
(
_,
Self::Sequence {
expressions: mut next_expressions,
..
},
) => {
let mut current_expressions = vec![self];
current_expressions.append(&mut next_expressions);
Self::Sequence {
location,
expressions: current_expressions,
}
}
(_, _) => Self::Sequence {
location,
expressions: vec![self, next],
},
}
}
pub fn location(&self) -> Span {
match self {
Self::PipeLine { expressions, .. } => expressions.last().location(),
Self::Trace { then, .. } => then.location(),
Self::TraceIfFalse { location, .. }
| Self::Fn { location, .. }
| Self::Var { location, .. }
| Self::Int { location, .. }
| Self::ErrorTerm { location, .. }
| Self::When { location, .. }
| Self::Call { location, .. }
| Self::List { location, .. }
| Self::ByteArray { location, .. }
| Self::BinOp { location, .. }
| Self::Tuple { location, .. }
| Self::String { location, .. }
| Self::Assignment { location, .. }
| Self::TupleIndex { location, .. }
| Self::FieldAccess { location, .. }
| Self::RecordUpdate { location, .. }
| Self::UnOp { location, .. }
| Self::If { location, .. } => *location,
Self::Sequence {
location,
expressions,
..
} => expressions.last().map(Self::location).unwrap_or(*location),
}
}
pub fn start_byte_index(&self) -> usize {
match self {
Self::Sequence {
expressions,
location,
..
} => expressions
.first()
.map(|e| e.start_byte_index())
.unwrap_or(location.start),
Self::PipeLine { expressions, .. } => expressions.first().start_byte_index(),
Self::Trace { location, .. } | Self::Assignment { location, .. } => location.start,
_ => self.location().start,
}
}
pub fn binop_precedence(&self) -> u8 {
match self {
Self::BinOp { name, .. } => name.precedence(),
Self::PipeLine { .. } => 5,
_ => std::u8::MAX,
}
}
pub fn is_simple_constant(&self) -> bool {
matches!(
self,
Self::String { .. } | Self::Int { .. } | Self::ByteArray { .. }
)
}
}