mirror of
https://github.com/edg-l/edlang.git
synced 2024-11-22 07:58:24 +00:00
feat: compile unary op, compile asref, compile deref, reference arguments, avoid some temporaries on direct use
This commit is contained in:
parent
6d31a9ea6f
commit
819a70d9f5
|
@ -31,20 +31,6 @@ pub struct PathExpr {
|
|||
pub span: Span,
|
||||
}
|
||||
|
||||
impl PathExpr {
|
||||
pub fn get_full_path(&self) -> String {
|
||||
let mut result = self.first.name.clone();
|
||||
for path in &self.extra {
|
||||
result.push('.');
|
||||
match path {
|
||||
PathSegment::Field(name) => result.push_str(&name.name),
|
||||
PathSegment::Index { .. } => result.push_str("[]"),
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum PathSegment {
|
||||
Field(Ident),
|
||||
|
@ -109,6 +95,7 @@ pub struct LetStmt {
|
|||
pub struct AssignStmt {
|
||||
pub name: PathExpr,
|
||||
pub value: Expression,
|
||||
pub deref_times: usize,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
|
@ -182,6 +169,8 @@ pub enum Expression {
|
|||
FnCall(FnCallExpr),
|
||||
Unary(UnaryOp, Box<Self>),
|
||||
Binary(Box<Self>, BinaryOp, Box<Self>),
|
||||
Deref(Box<Self>),
|
||||
AsRef(Box<Self>, bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
|
|
|
@ -18,7 +18,7 @@ use inkwell::{
|
|||
AddressSpace,
|
||||
};
|
||||
use ir::{LocalKind, ModuleBody, ProgramBody, TypeInfo, ValueTree};
|
||||
use llvm_sys::debuginfo::{LLVMDIFlagLValueReference, LLVMDIFlagPublic};
|
||||
use llvm_sys::debuginfo::LLVMDIFlagPublic;
|
||||
use tracing::{info, trace};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
@ -234,40 +234,7 @@ fn compile_fn_signature(ctx: &ModuleCompileCtx<'_, '_>, fn_id: DefId) {
|
|||
|
||||
// https://llvm.org/doxygen/group__LLVMCCoreTypes.html
|
||||
|
||||
/* starting from 1 to 80
|
||||
allocalign allocptr alwaysinline builtin cold convergent disable_sanitizer_instrumentation fn_ret_thunk_extern hot
|
||||
immarg inreg inlinehint jumptable minsize mustprogress naked nest noalias
|
||||
nobuiltin nocallback nocapture nocf_check noduplicate nofree noimplicitfloat
|
||||
noinline nomerge noprofile norecurse noredzone noreturn nosanitize_bounds
|
||||
nosanitize_coverage nosync noundef nounwind nonlazybind nonnull null_pointer_is_valid
|
||||
optforfuzzing optsize optnone presplitcoroutine readnone readonly returned returns_twice
|
||||
signext safestack sanitize_address sanitize_hwaddress sanitize_memtag sanitize_memory
|
||||
sanitize_thread shadowcallstack skipprofile speculatable speculative_load_hardening ssp
|
||||
sspreq sspstrong strictfp swiftasync swifterror swiftself willreturn writeonly (67) zeroext byref byval elementtype inalloca
|
||||
preallocated sret align 0 allockind(\"\") allocsize(0,0) dereferenceable(0) dereferenceable_or_null(0
|
||||
*/
|
||||
|
||||
/*
|
||||
// nounwind
|
||||
fn_value.add_attribute(
|
||||
inkwell::attributes::AttributeLoc::Function,
|
||||
ctx.ctx.context.create_enum_attribute(36, 0),
|
||||
);
|
||||
|
||||
// nonlazybind
|
||||
fn_value.add_attribute(
|
||||
inkwell::attributes::AttributeLoc::Function,
|
||||
ctx.ctx.context.create_enum_attribute(37, 0),
|
||||
);
|
||||
|
||||
// willreturn
|
||||
fn_value.add_attribute(
|
||||
inkwell::attributes::AttributeLoc::Function,
|
||||
ctx.ctx.context.create_enum_attribute(66, 0),
|
||||
);
|
||||
*/
|
||||
|
||||
fn_value.set_call_conventions(0);
|
||||
fn_value.set_call_conventions(0); // cconv
|
||||
|
||||
let (_, line, _col) = ctx
|
||||
.ctx
|
||||
|
@ -420,11 +387,29 @@ fn compile_fn(ctx: &ModuleCompileCtx, fn_id: DefId) -> Result<(), BuilderError>
|
|||
match &stmt.kind {
|
||||
ir::StatementKind::Assign(place, rvalue) => {
|
||||
let local = &body.locals[place.local];
|
||||
let mut local_ty = local.ty.clone();
|
||||
let mut ptr = *locals.get(&place.local).unwrap();
|
||||
|
||||
for proj in &place.projection {
|
||||
match proj {
|
||||
ir::PlaceElem::Deref => {
|
||||
ptr = ctx
|
||||
.builder
|
||||
.build_load(compile_basic_type(ctx, &local_ty), ptr, "deref")?
|
||||
.into_pointer_value();
|
||||
local_ty = match local_ty.kind {
|
||||
ir::TypeKind::Ptr(inner) => *inner,
|
||||
ir::TypeKind::Ref(_, inner) => *inner,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
ir::PlaceElem::Field { .. } => todo!(),
|
||||
ir::PlaceElem::Index { .. } => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
let (value, _value_ty) = compile_rvalue(ctx, fn_id, &locals, rvalue)?;
|
||||
let instruction = ctx
|
||||
.builder
|
||||
.build_store(*locals.get(&place.local).unwrap(), value)?;
|
||||
let instruction = ctx.builder.build_store(ptr, value)?;
|
||||
|
||||
if let Some(_debug_name) = &local.debug_name {
|
||||
let di_local = di_locals.get(&place.local).unwrap();
|
||||
|
@ -558,6 +543,41 @@ fn compile_fn(ctx: &ModuleCompileCtx, fn_id: DefId) -> Result<(), BuilderError>
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_unary_op<'ctx>(
|
||||
ctx: &ModuleCompileCtx<'ctx, '_>,
|
||||
fn_id: DefId,
|
||||
locals: &HashMap<usize, PointerValue<'ctx>>,
|
||||
op: ir::UnOp,
|
||||
value: &ir::Operand,
|
||||
) -> Result<(BasicValueEnum<'ctx>, TypeInfo), BuilderError> {
|
||||
let (value, ty) = compile_load_operand(ctx, fn_id, locals, value)?;
|
||||
|
||||
let is_float = matches!(ty.kind, ir::TypeKind::Float(_));
|
||||
|
||||
Ok(match op {
|
||||
ir::UnOp::Not => {
|
||||
assert!(ty.kind.is_integer(), "must be a integer");
|
||||
let value = ctx
|
||||
.builder
|
||||
.build_not(value.into_int_value(), "not")?
|
||||
.as_basic_value_enum();
|
||||
(value, ty)
|
||||
}
|
||||
ir::UnOp::Neg => {
|
||||
let value = if is_float {
|
||||
ctx.builder
|
||||
.build_float_neg(value.into_float_value(), "negf")?
|
||||
.as_basic_value_enum()
|
||||
} else {
|
||||
ctx.builder
|
||||
.build_int_neg(value.into_int_value(), "negi")?
|
||||
.as_basic_value_enum()
|
||||
};
|
||||
(value, ty)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn compile_bin_op<'ctx>(
|
||||
ctx: &ModuleCompileCtx<'ctx, '_>,
|
||||
fn_id: DefId,
|
||||
|
@ -908,10 +928,40 @@ fn compile_rvalue<'ctx>(
|
|||
) -> Result<(BasicValueEnum<'ctx>, TypeInfo), BuilderError> {
|
||||
Ok(match rvalue {
|
||||
ir::RValue::Use(op) => compile_load_operand(ctx, fn_id, locals, op)?,
|
||||
ir::RValue::Ref(_, _) => todo!(),
|
||||
ir::RValue::Ref(_mutable, op) => match op {
|
||||
ir::Operand::Copy(_) => todo!(),
|
||||
ir::Operand::Move(place) => {
|
||||
let mut ptr = *locals.get(&place.local).unwrap();
|
||||
let mut local_ty = {
|
||||
let body = ctx.ctx.program.functions.get(&fn_id).unwrap();
|
||||
body.locals[place.local].ty.clone()
|
||||
};
|
||||
|
||||
for proj in &place.projection {
|
||||
match proj {
|
||||
ir::PlaceElem::Deref => {
|
||||
ptr = ctx
|
||||
.builder
|
||||
.build_load(compile_basic_type(ctx, &local_ty), ptr, "deref")?
|
||||
.into_pointer_value();
|
||||
local_ty = match local_ty.kind {
|
||||
ir::TypeKind::Ptr(inner) => *inner,
|
||||
ir::TypeKind::Ref(_, inner) => *inner,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
ir::PlaceElem::Field { .. } => todo!(),
|
||||
ir::PlaceElem::Index { .. } => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
(ptr.as_basic_value_enum(), local_ty)
|
||||
}
|
||||
ir::Operand::Constant(_) => todo!("references to constants not yet implemented"),
|
||||
},
|
||||
ir::RValue::BinOp(op, lhs, rhs) => compile_bin_op(ctx, fn_id, locals, *op, lhs, rhs)?,
|
||||
ir::RValue::LogicOp(_, _, _) => todo!(),
|
||||
ir::RValue::UnOp(_, _) => todo!(),
|
||||
ir::RValue::UnOp(op, value) => compile_unary_op(ctx, fn_id, locals, *op, value)?,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -933,8 +983,29 @@ fn compile_load_operand<'ctx>(
|
|||
)
|
||||
}
|
||||
ir::Operand::Move(place) => {
|
||||
let pointee_ty = compile_basic_type(ctx, &body.locals[place.local].ty);
|
||||
let ptr = *locals.get(&place.local).unwrap();
|
||||
let mut ptr = *locals.get(&place.local).unwrap();
|
||||
let mut local_ty = body.locals[place.local].ty.clone();
|
||||
|
||||
for proj in &place.projection {
|
||||
match proj {
|
||||
ir::PlaceElem::Deref => {
|
||||
ptr = ctx
|
||||
.builder
|
||||
.build_load(compile_basic_type(ctx, &local_ty), ptr, "deref")?
|
||||
.into_pointer_value();
|
||||
local_ty = match local_ty.kind {
|
||||
ir::TypeKind::Ptr(inner) => *inner,
|
||||
ir::TypeKind::Ref(_, inner) => *inner,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
ir::PlaceElem::Field { .. } => todo!(),
|
||||
ir::PlaceElem::Index { .. } => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
let pointee_ty = compile_basic_type(ctx, &local_ty);
|
||||
|
||||
(
|
||||
ctx.builder.build_load(pointee_ty, ptr, "")?,
|
||||
body.locals[place.local].ty.clone(),
|
||||
|
|
|
@ -208,10 +208,14 @@ pub enum TypeKind {
|
|||
}
|
||||
|
||||
impl TypeKind {
|
||||
pub fn is_unit(&self) -> bool {
|
||||
pub const fn is_unit(&self) -> bool {
|
||||
matches!(self, Self::Unit)
|
||||
}
|
||||
|
||||
pub const fn is_integer(&self) -> bool {
|
||||
matches!(self, Self::Int(_) | Self::Uint(_))
|
||||
}
|
||||
|
||||
pub fn get_falsy_value(&self) -> ValueTree {
|
||||
match self {
|
||||
Self::Bool => ValueTree::Leaf(ConstValue::Bool(false)),
|
||||
|
|
|
@ -5,8 +5,8 @@ use common::{BodyBuilder, BuildCtx};
|
|||
use edlang_ast as ast;
|
||||
use edlang_ir as ir;
|
||||
use ir::{
|
||||
BasicBlock, Body, DefId, Local, LocalKind, Operand, Place, ProgramBody, Statement,
|
||||
StatementKind, SwitchTarget, Terminator, TypeInfo, TypeKind,
|
||||
BasicBlock, Body, DefId, Local, LocalKind, Operand, Place, PlaceElem, ProgramBody, RValue,
|
||||
Statement, StatementKind, SwitchTarget, Terminator, TypeInfo, TypeKind,
|
||||
};
|
||||
use tracing::trace;
|
||||
|
||||
|
@ -371,10 +371,40 @@ fn lower_let(builder: &mut BodyBuilder, info: &ast::LetStmt) {
|
|||
}
|
||||
|
||||
fn lower_assign(builder: &mut BodyBuilder, info: &ast::AssignStmt) {
|
||||
let local = *builder.name_to_local.get(&info.name.first.name).unwrap();
|
||||
let ty = builder.body.locals[local].ty.clone();
|
||||
let (rvalue, _ty) = lower_expr(builder, &info.value, Some(&ty.kind));
|
||||
let (place, _ty) = lower_path(builder, &info.name);
|
||||
let (mut place, mut ty) = lower_path(builder, &info.name);
|
||||
|
||||
if let Some(PlaceElem::Deref) = place.projection.last() {
|
||||
match &ty {
|
||||
TypeKind::Ptr(inner) => {
|
||||
ty = inner.kind.clone();
|
||||
}
|
||||
TypeKind::Ref(is_mut, inner) => {
|
||||
if !is_mut {
|
||||
panic!("trying to mutate non mut ref");
|
||||
}
|
||||
ty = inner.kind.clone();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..info.deref_times {
|
||||
match &ty {
|
||||
TypeKind::Ptr(inner) => {
|
||||
ty = inner.kind.clone();
|
||||
}
|
||||
TypeKind::Ref(is_mut, inner) => {
|
||||
if !is_mut {
|
||||
panic!("trying to mutate non mut ref");
|
||||
}
|
||||
ty = inner.kind.clone();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
place.projection.push(PlaceElem::Deref);
|
||||
}
|
||||
|
||||
let (rvalue, _ty) = lower_expr(builder, &info.value, Some(&ty));
|
||||
|
||||
builder.statements.push(Statement {
|
||||
span: Some(info.name.first.span),
|
||||
|
@ -426,6 +456,8 @@ fn find_expr_type(builder: &mut BodyBuilder, info: &ast::Expression) -> Option<T
|
|||
find_expr_type(builder, lhs).or(find_expr_type(builder, rhs))?
|
||||
}
|
||||
}
|
||||
ast::Expression::Deref(_) => todo!(),
|
||||
ast::Expression::AsRef(_, _) => todo!(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -447,6 +479,50 @@ fn lower_expr(
|
|||
ast::Expression::Binary(lhs, op, rhs) => {
|
||||
lower_binary_expr(builder, lhs, op, rhs, type_hint)
|
||||
}
|
||||
ast::Expression::Deref(_) => todo!(),
|
||||
ast::Expression::AsRef(inner, mutable) => {
|
||||
let type_hint = match type_hint {
|
||||
Some(inner) => match inner {
|
||||
TypeKind::Ref(_, inner) => Some(&inner.kind),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
let (mut value, ty) = lower_expr(builder, inner, type_hint);
|
||||
|
||||
// check if its a use directly, to avoid a temporary.
|
||||
value = match value {
|
||||
RValue::Use(op) => RValue::Ref(*mutable, op),
|
||||
value => {
|
||||
let inner_local = builder.add_local(Local::temp(ty.clone()));
|
||||
let inner_place = Place {
|
||||
local: inner_local,
|
||||
projection: Default::default(),
|
||||
};
|
||||
|
||||
builder.statements.push(Statement {
|
||||
span: None,
|
||||
kind: StatementKind::StorageLive(inner_local),
|
||||
});
|
||||
|
||||
builder.statements.push(Statement {
|
||||
span: None,
|
||||
kind: StatementKind::Assign(inner_place.clone(), value),
|
||||
});
|
||||
RValue::Ref(*mutable, Operand::Move(inner_place))
|
||||
}
|
||||
};
|
||||
|
||||
let ty = TypeKind::Ref(
|
||||
*mutable,
|
||||
Box::new(TypeInfo {
|
||||
span: None,
|
||||
kind: ty,
|
||||
}),
|
||||
);
|
||||
|
||||
(value, ty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -459,7 +535,6 @@ fn lower_binary_expr(
|
|||
) -> (ir::RValue, TypeKind) {
|
||||
trace!("lowering binary op: {:?}", op);
|
||||
|
||||
// todo: if lhs or rhs is a simple place, dont make another temporary?
|
||||
let (lhs, lhs_ty) = if type_hint.is_none() {
|
||||
let ty = find_expr_type(builder, lhs)
|
||||
.unwrap_or_else(|| find_expr_type(builder, rhs).expect("cant find type"));
|
||||
|
@ -474,39 +549,49 @@ fn lower_binary_expr(
|
|||
lower_expr(builder, rhs, type_hint)
|
||||
};
|
||||
|
||||
let lhs_local = builder.add_local(Local::temp(lhs_ty.clone()));
|
||||
let rhs_local = builder.add_local(Local::temp(rhs_ty.clone()));
|
||||
let lhs_place = Place {
|
||||
local: lhs_local,
|
||||
projection: Default::default(),
|
||||
};
|
||||
let rhs_place = Place {
|
||||
local: rhs_local,
|
||||
projection: Default::default(),
|
||||
let lhs = match lhs {
|
||||
RValue::Use(op) => op,
|
||||
lhs => {
|
||||
let lhs_local = builder.add_local(Local::temp(lhs_ty.clone()));
|
||||
let lhs_place = Place {
|
||||
local: lhs_local,
|
||||
projection: Default::default(),
|
||||
};
|
||||
|
||||
builder.statements.push(Statement {
|
||||
span: None,
|
||||
kind: StatementKind::StorageLive(lhs_local),
|
||||
});
|
||||
|
||||
builder.statements.push(Statement {
|
||||
span: None,
|
||||
kind: StatementKind::Assign(lhs_place.clone(), lhs),
|
||||
});
|
||||
Operand::Move(lhs_place)
|
||||
}
|
||||
};
|
||||
|
||||
builder.statements.push(Statement {
|
||||
span: None,
|
||||
kind: StatementKind::StorageLive(lhs_local),
|
||||
});
|
||||
let rhs = match rhs {
|
||||
RValue::Use(op) => op,
|
||||
rhs => {
|
||||
let rhs_local = builder.add_local(Local::temp(rhs_ty.clone()));
|
||||
let rhs_place = Place {
|
||||
local: rhs_local,
|
||||
projection: Default::default(),
|
||||
};
|
||||
|
||||
builder.statements.push(Statement {
|
||||
span: None,
|
||||
kind: StatementKind::Assign(lhs_place.clone(), lhs),
|
||||
});
|
||||
builder.statements.push(Statement {
|
||||
span: None,
|
||||
kind: StatementKind::StorageLive(rhs_local),
|
||||
});
|
||||
|
||||
builder.statements.push(Statement {
|
||||
span: None,
|
||||
kind: StatementKind::StorageLive(rhs_local),
|
||||
});
|
||||
|
||||
builder.statements.push(Statement {
|
||||
span: None,
|
||||
kind: StatementKind::Assign(rhs_place.clone(), rhs),
|
||||
});
|
||||
|
||||
let lhs = Operand::Move(lhs_place);
|
||||
let rhs = Operand::Move(rhs_place);
|
||||
builder.statements.push(Statement {
|
||||
span: None,
|
||||
kind: StatementKind::Assign(rhs_place.clone(), rhs),
|
||||
});
|
||||
Operand::Move(rhs_place)
|
||||
}
|
||||
};
|
||||
|
||||
match op {
|
||||
ast::BinaryOp::Arith(op, _) => (
|
||||
|
@ -791,10 +876,19 @@ fn lower_path(builder: &mut BodyBuilder, info: &ast::PathExpr) -> (ir::Place, Ty
|
|||
.expect("local not found");
|
||||
let ty = builder.body.locals[local].ty.kind.clone();
|
||||
|
||||
let projection = Vec::new();
|
||||
|
||||
for extra in &info.extra {
|
||||
match extra {
|
||||
ast::PathSegment::Field(_) => todo!(),
|
||||
ast::PathSegment::Index { .. } => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
Place {
|
||||
local,
|
||||
projection: Default::default(), // todo, field array deref
|
||||
projection: projection.into(), // todo, field array deref
|
||||
},
|
||||
ty,
|
||||
)
|
||||
|
|
|
@ -160,7 +160,7 @@ pub(crate) PathExpr: ast::PathExpr = {
|
|||
first,
|
||||
extra: extra.unwrap_or(vec![]),
|
||||
span: ast::Span::new(lo, hi),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
pub PathSegments: Vec<ast::PathSegment> = {
|
||||
|
@ -215,9 +215,10 @@ pub(crate) LetStmt: ast::LetStmt = {
|
|||
}
|
||||
|
||||
pub(crate) AssignStmt: ast::AssignStmt = {
|
||||
<lo:@L> <name:PathExpr> "=" <value:Expression> <hi:@R> => ast::AssignStmt {
|
||||
<lo:@L> <deref:"*"*> <name:PathExpr> "=" <value:Expression> <hi:@R> => ast::AssignStmt {
|
||||
name,
|
||||
value,
|
||||
deref_times: deref.len(),
|
||||
span: ast::Span::new(lo, hi),
|
||||
},
|
||||
}
|
||||
|
@ -268,32 +269,38 @@ pub(crate) IfStmt: ast::IfStmt = {
|
|||
}
|
||||
|
||||
pub(crate) Term: ast::Expression = {
|
||||
#[precedence(level="0")]
|
||||
<ValueExpr> => ast::Expression::Value(<>),
|
||||
<FnCallExpr> => ast::Expression::FnCall(<>),
|
||||
#[precedence(level="2")] #[assoc(side="left")]
|
||||
"(" <Expression> ")",
|
||||
}
|
||||
|
||||
pub(crate) Expression: ast::Expression = {
|
||||
#[precedence(level="0")]
|
||||
<Term>,
|
||||
#[precedence(level="1")] #[assoc(side="left")]
|
||||
"&" "mut" <Expression> => ast::Expression::AsRef(Box::new(<>), true),
|
||||
"&" <Expression> => ast::Expression::AsRef(Box::new(<>), false),
|
||||
"*" <Expression> => ast::Expression::Deref(Box::new(<>)),
|
||||
<op:UnaryOp> <rhs:Expression> => ast::Expression::Unary(
|
||||
op,
|
||||
Box::new(rhs)
|
||||
),
|
||||
// <op:UnaryOp> <e:Expression> => ast::Expression::UnaryOp(op, Box::new(e)),
|
||||
#[precedence(level="1")] #[assoc(side="left")]
|
||||
|
||||
#[precedence(level="2")] #[assoc(side="left")]
|
||||
<lhs:Expression> <op:BinaryFirstLvlOp> <rhs:Expression> => ast::Expression::Binary(
|
||||
Box::new(lhs),
|
||||
op,
|
||||
Box::new(rhs)
|
||||
),
|
||||
#[precedence(level="2")] #[assoc(side="left")]
|
||||
#[precedence(level="3")] #[assoc(side="left")]
|
||||
<lhs:Expression> <op:BinarySecondLvlOp> <rhs:Expression> => ast::Expression::Binary(
|
||||
Box::new(lhs),
|
||||
op,
|
||||
Box::new(rhs)
|
||||
),
|
||||
#[precedence(level="3")] #[assoc(side="left")]
|
||||
#[precedence(level="4")] #[assoc(side="left")]
|
||||
<lhs:Expression> <op:BinaryThirdLvlOp> <rhs:Expression> => ast::Expression::Binary(
|
||||
Box::new(lhs),
|
||||
op,
|
||||
|
|
Loading…
Reference in a new issue