diff --git a/crates/tx3-lang/src/analyzing.rs b/crates/tx3-lang/src/analyzing.rs index 7c884d3..ec7abed 100644 --- a/crates/tx3-lang/src/analyzing.rs +++ b/crates/tx3-lang/src/analyzing.rs @@ -621,6 +621,19 @@ impl Analyzable for MulOp { } } +impl Analyzable for DivOp { + fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + let left = self.lhs.analyze(parent.clone()); + let right = self.rhs.analyze(parent.clone()); + + left + right + } + + fn is_resolved(&self) -> bool { + self.lhs.is_resolved() && self.rhs.is_resolved() + } +} + impl Analyzable for NegateOp { fn analyze(&mut self, parent: Option>) -> AnalyzeReport { self.operand.analyze(parent) @@ -767,6 +780,7 @@ impl Analyzable for DataExpr { DataExpr::AddOp(x) => x.analyze(parent), DataExpr::SubOp(x) => x.analyze(parent), DataExpr::MulOp(x) => x.analyze(parent), + DataExpr::DivOp(x) => x.analyze(parent), DataExpr::NegateOp(x) => x.analyze(parent), DataExpr::PropertyOp(x) => x.analyze(parent), DataExpr::AnyAssetConstructor(x) => x.analyze(parent), @@ -785,6 +799,7 @@ impl Analyzable for DataExpr { DataExpr::AddOp(x) => x.is_resolved(), DataExpr::SubOp(x) => x.is_resolved(), DataExpr::MulOp(x) => x.is_resolved(), + DataExpr::DivOp(x) => x.is_resolved(), DataExpr::NegateOp(x) => x.is_resolved(), DataExpr::PropertyOp(x) => x.is_resolved(), DataExpr::AnyAssetConstructor(x) => x.is_resolved(), diff --git a/crates/tx3-lang/src/ast.rs b/crates/tx3-lang/src/ast.rs index 752b9da..a9c300d 100644 --- a/crates/tx3-lang/src/ast.rs +++ b/crates/tx3-lang/src/ast.rs @@ -732,6 +732,19 @@ impl MulOp { } } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct DivOp { + pub lhs: Box, + pub rhs: Box, + pub span: Span, +} + +impl DivOp { + pub fn target_type(&self) -> Option { + self.lhs.target_type() + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct ConcatOp { pub lhs: Box, @@ -777,6 +790,7 @@ pub enum DataExpr { AddOp(AddOp), SubOp(SubOp), MulOp(MulOp), + DivOp(DivOp), ConcatOp(ConcatOp), NegateOp(NegateOp), PropertyOp(PropertyOp), @@ -810,6 +824,7 @@ impl DataExpr { DataExpr::AddOp(x) => x.target_type(), DataExpr::SubOp(x) => x.target_type(), DataExpr::MulOp(x) => x.target_type(), + DataExpr::DivOp(x) => x.target_type(), DataExpr::ConcatOp(x) => x.target_type(), DataExpr::NegateOp(x) => x.target_type(), DataExpr::PropertyOp(x) => x.target_type(), diff --git a/crates/tx3-lang/src/lowering.rs b/crates/tx3-lang/src/lowering.rs index 13492d6..48d3cbe 100644 --- a/crates/tx3-lang/src/lowering.rs +++ b/crates/tx3-lang/src/lowering.rs @@ -456,6 +456,19 @@ impl IntoLower for ast::MulOp { } } +impl IntoLower for ast::DivOp { + type Output = ir::Expression; + + fn into_lower(&self, ctx: &Context) -> Result { + let left = self.lhs.into_lower(ctx)?; + let right = self.rhs.into_lower(ctx)?; + + Ok(ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Div( + left, right, + )))) + } +} + impl IntoLower for ast::ConcatOp { type Output = ir::Expression; @@ -635,6 +648,7 @@ impl IntoLower for ast::DataExpr { ast::DataExpr::AddOp(x) => x.into_lower(ctx)?, ast::DataExpr::SubOp(x) => x.into_lower(ctx)?, ast::DataExpr::MulOp(x) => x.into_lower(ctx)?, + ast::DataExpr::DivOp(x) => x.into_lower(ctx)?, ast::DataExpr::ConcatOp(x) => x.into_lower(ctx)?, ast::DataExpr::NegateOp(x) => x.into_lower(ctx)?, ast::DataExpr::PropertyOp(x) => x.into_lower(ctx)?, diff --git a/crates/tx3-lang/src/parsing.rs b/crates/tx3-lang/src/parsing.rs index 41e9c9b..58b2836 100644 --- a/crates/tx3-lang/src/parsing.rs +++ b/crates/tx3-lang/src/parsing.rs @@ -1337,12 +1337,22 @@ impl DataExpr { span, })) } + + fn div_op_parse(left: DataExpr, pair: Pair, right: DataExpr) -> Result { + let span = pair.as_span().into(); + + Ok(DataExpr::DivOp(DivOp { + lhs: Box::new(left), + rhs: Box::new(right), + span, + })) + } } static DATA_EXPR_PRATT_PARSER: LazyLock> = LazyLock::new(|| { PrattParser::new() .op(Op::infix(Rule::data_add, Assoc::Left) | Op::infix(Rule::data_sub, Assoc::Left)) - .op(Op::infix(Rule::data_mul, Assoc::Left)) + .op(Op::infix(Rule::data_mul, Assoc::Left) | Op::infix(Rule::data_div, Assoc::Left)) .op(Op::prefix(Rule::data_negate)) .op(Op::postfix(Rule::data_property) | Op::postfix(Rule::data_index)) }); @@ -1384,6 +1394,7 @@ impl AstNode for DataExpr { Rule::data_add => DataExpr::add_op_parse(left?, op, right?), Rule::data_sub => DataExpr::sub_op_parse(left?, op, right?), Rule::data_mul => DataExpr::mul_op_parse(left?, op, right?), + Rule::data_div => DataExpr::div_op_parse(left?, op, right?), x => unreachable!("Unexpected rule as data infix: {:?}", x), }) .parse(inner) @@ -1405,6 +1416,7 @@ impl AstNode for DataExpr { DataExpr::AddOp(x) => &x.span, DataExpr::SubOp(x) => &x.span, DataExpr::MulOp(x) => &x.span, + DataExpr::DivOp(x) => &x.span, DataExpr::ConcatOp(x) => &x.span, DataExpr::NegateOp(x) => &x.span, DataExpr::PropertyOp(x) => &x.span, @@ -2203,6 +2215,50 @@ mod tests { }) ); + input_to_ast_check!( + DataExpr, + "div_op", + "10 / var1", + DataExpr::DivOp(DivOp { + lhs: Box::new(DataExpr::Number(10)), + rhs: Box::new(DataExpr::Identifier(Identifier::new("var1"))), + span: Span::DUMMY, + }) + ); + + // `/` binds tighter than `+`, so `2 + 8 / 4` parses as `2 + (8 / 4)`. + input_to_ast_check!( + DataExpr, + "div_binds_tighter_than_add", + "2 + 8 / 4", + DataExpr::AddOp(AddOp { + lhs: Box::new(DataExpr::Number(2)), + rhs: Box::new(DataExpr::DivOp(DivOp { + lhs: Box::new(DataExpr::Number(8)), + rhs: Box::new(DataExpr::Number(4)), + span: Span::DUMMY, + })), + span: Span::DUMMY, + }) + ); + + // `*` and `/` share precedence and are left-associative, so `8 / 4 * 2` + // parses as `(8 / 4) * 2`. + input_to_ast_check!( + DataExpr, + "div_binds_like_mul", + "8 / 4 * 2", + DataExpr::MulOp(MulOp { + lhs: Box::new(DataExpr::DivOp(DivOp { + lhs: Box::new(DataExpr::Number(8)), + rhs: Box::new(DataExpr::Number(4)), + span: Span::DUMMY, + })), + rhs: Box::new(DataExpr::Number(2)), + span: Span::DUMMY, + }) + ); + input_to_ast_check!( DataExpr, "concat_op", diff --git a/crates/tx3-lang/src/tx3.pest b/crates/tx3-lang/src/tx3.pest index 1024226..da0ee9e 100644 --- a/crates/tx3-lang/src/tx3.pest +++ b/crates/tx3-lang/src/tx3.pest @@ -138,10 +138,11 @@ fn_call = { data_expr = { data_prefix* ~ data_primary ~ data_postfix* ~ (data_infix ~ data_prefix* ~ data_primary ~ data_postfix* )* } - data_infix = _{ data_add | data_sub | data_mul } + data_infix = _{ data_add | data_sub | data_mul | data_div } data_add = { "+" } data_sub = { "-" } data_mul = { "*" } + data_div = { "/" } data_prefix = _{ data_negate } data_negate = { "!" } diff --git a/specs/v1beta0/03-lexical-structure.md b/specs/v1beta0/03-lexical-structure.md index 1050e64..596decc 100644 --- a/specs/v1beta0/03-lexical-structure.md +++ b/specs/v1beta0/03-lexical-structure.md @@ -210,13 +210,13 @@ designated role, the name is consumed as that role. The following tokens are operators or punctuation: ``` -+ - ! ++ - * / ! . [ ] ( ) { } , ; : = < > :: ... # -? * +? ``` Operator precedence and associativity are given in §4.5. @@ -227,6 +227,12 @@ grammatical context: `*` is a block flag only when it immediately follows the `input` keyword in a transaction block; in a data expression it is the multiplication operator. +The token `/` is the infix division operator (§5.5.1). Because `//` begins a +line comment and `/*` begins a block comment (§3.3), division must be written +with a single `/`; a sequence like `a//b` lexes as the identifier `a` followed +by a line comment, not as two divisions. Separating the operands with +whitespace (`a / b`) avoids any ambiguity. + The token `?` is used as a flag on `output` blocks (`output?`); it is otherwise reserved. diff --git a/specs/v1beta0/04-syntactic-grammar.md b/specs/v1beta0/04-syntactic-grammar.md index 9688a27..1396c21 100644 --- a/specs/v1beta0/04-syntactic-grammar.md +++ b/specs/v1beta0/04-syntactic-grammar.md @@ -164,7 +164,7 @@ A `custom_type` is an identifier that MUST resolve to a `record_def`, data_expr ::= data_prefix* data_primary data_postfix* ( data_infix data_prefix* data_primary data_postfix* )* -data_infix ::= "+" | "-" | "*" +data_infix ::= "+" | "-" | "*" | "/" data_prefix ::= "!" data_postfix ::= "." identifier | "[" data_expr "]" @@ -255,14 +255,14 @@ tightest binding (highest precedence) to loosest: | ----- | -------------------------- | ------------- | | 1 | `.` `[…]` (postfix) | left | | 2 | `!` (prefix) | non-assoc | -| 3 | `*` (infix) | left | +| 3 | `*` `/` (infix) | left | | 4 | `+` `-` (infix) | left | Parentheses `( data_expr )` may be used to override precedence. There are no further operators in `v1beta0`. In particular, there are no -comparison operators, no boolean combinators, no division or shift -operators, and no ternary form. +comparison operators, no boolean combinators, no modulo or shift operators, +and no ternary form. ## 4.6 Transaction-body blocks diff --git a/specs/v1beta0/05-type-system.md b/specs/v1beta0/05-type-system.md index 23a7079..3fc9a8f 100644 --- a/specs/v1beta0/05-type-system.md +++ b/specs/v1beta0/05-type-system.md @@ -163,11 +163,25 @@ The infix operator `*` is binary and left-associative, and binds tighter than Multiplication of one `AnyAsset` by another `AnyAsset` is **not** defined. +The infix operator `/` is binary and left-associative, and shares the +precedence of `*` (§4.5). It denotes **integer division that truncates toward +zero** (e.g. `7 / 2` is `3`). It is defined for the following operand-type +pairs: + +| Left type | Right type | Result type | Meaning | +| ----------- | ----------- | ----------- | -------------------------------- | +| `Int` | `Int` | `Int` | Integer division (truncating). | +| `AnyAsset` | `Int` | `AnyAsset` | Divide every asset quantity. | + +Division does **not** commute: `Int / AnyAsset` and `AnyAsset / AnyAsset` are +**not** defined. A division whose right operand reduces to `0` is a +compile-time error. + Where the left and right operands have compatible compound types that contain `Int` or `AnyAsset` components (such as types representing UTxO -values), implementations MAY extend `+`, `-`, and `*` to those types provided -the extension is consistent across operand pairs. Such extensions are out of -the scope of this version of the specification. +values), implementations MAY extend `+`, `-`, `*`, and `/` to those types +provided the extension is consistent across operand pairs. Such extensions are +out of the scope of this version of the specification. The prefix operator `!` is defined as **arithmetic negation** when applied to an operand of numeric type: