mirror of
https://github.com/ProvableHQ/leo.git
synced 2024-12-24 10:41:57 +03:00
clean up and fix type set checking
This commit is contained in:
parent
d0273719ab
commit
eee58883a6
@ -34,7 +34,7 @@ impl<R: ReconstructingReducer> ReconstructingDirector<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn reduce_type(&mut self, type_: &Type, span: &Span) -> Result<Type> {
|
pub fn reduce_type(&mut self, type_: &Type, span: &Span) -> Result<Type> {
|
||||||
self.reducer.reduce_type(type_, type_.clone(), span)
|
self.reducer.reduce_type(type_, *type_, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expressions
|
// Expressions
|
||||||
|
@ -20,7 +20,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// Explicit type used for defining a variable or expression type
|
/// Explicit type used for defining a variable or expression type
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub enum Type {
|
pub enum Type {
|
||||||
// Data types
|
// Data types
|
||||||
/// The `address` type.
|
/// The `address` type.
|
||||||
@ -51,12 +51,12 @@ impl Type {
|
|||||||
///
|
///
|
||||||
pub fn eq_flat(&self, other: &Self) -> bool {
|
pub fn eq_flat(&self, other: &Self) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Type::Address, Type::Address) => true,
|
(Type::Address, Type::Address)
|
||||||
(Type::Boolean, Type::Boolean) => true,
|
| (Type::Boolean, Type::Boolean)
|
||||||
(Type::Field, Type::Field) => true,
|
| (Type::Field, Type::Field)
|
||||||
(Type::Group, Type::Group) => true,
|
| (Type::Group, Type::Group)
|
||||||
(Type::Char, Type::Char) => true,
|
| (Type::Char, Type::Char)
|
||||||
(Type::Scalar, Type::Scalar) => true,
|
| (Type::Scalar, Type::Scalar) => true,
|
||||||
(Type::IntegerType(left), Type::IntegerType(right)) => left.eq(right),
|
(Type::IntegerType(left), Type::IntegerType(right)) => left.eq(right),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ impl<'a> TypeChecker<'a> {
|
|||||||
match expr {
|
match expr {
|
||||||
Expression::Identifier(ident) => {
|
Expression::Identifier(ident) => {
|
||||||
if let Some(var) = self.symbol_table.lookup_variable(&ident.name) {
|
if let Some(var) = self.symbol_table.lookup_variable(&ident.name) {
|
||||||
Some(self.assert_type(var.type_.clone(), expected, span))
|
Some(self.assert_type(*var.type_, expected, span))
|
||||||
} else {
|
} else {
|
||||||
self.handler
|
self.handler
|
||||||
.emit_err(TypeCheckerError::unknown_sym("variable", ident.name, span).into());
|
.emit_err(TypeCheckerError::unknown_sym("variable", ident.name, span).into());
|
||||||
@ -148,28 +148,28 @@ impl<'a> TypeChecker<'a> {
|
|||||||
},
|
},
|
||||||
Expression::Binary(binary) => match binary.op {
|
Expression::Binary(binary) => match binary.op {
|
||||||
BinaryOperation::And | BinaryOperation::Or => {
|
BinaryOperation::And | BinaryOperation::Or => {
|
||||||
self.assert_type(Type::Boolean, expected.clone(), binary.span());
|
self.assert_type(Type::Boolean, expected, binary.span());
|
||||||
let t1 = self.compare_expr_type(&binary.left, expected.clone(), binary.left.span());
|
let t1 = self.compare_expr_type(&binary.left, expected, binary.left.span());
|
||||||
let t2 = self.compare_expr_type(&binary.right, expected.clone(), binary.right.span());
|
let t2 = self.compare_expr_type(&binary.right, expected, binary.right.span());
|
||||||
|
|
||||||
return_incorrect_type(t1, t2, expected)
|
return_incorrect_type(t1, t2, expected)
|
||||||
}
|
}
|
||||||
BinaryOperation::Add => {
|
BinaryOperation::Add => {
|
||||||
self.assert_field_group_scalar_int_type(expected.clone(), binary.span());
|
self.assert_field_group_scalar_int_type(expected, binary.span());
|
||||||
let t1 = self.compare_expr_type(&binary.left, expected.clone(), binary.left.span());
|
let t1 = self.compare_expr_type(&binary.left, expected, binary.left.span());
|
||||||
let t2 = self.compare_expr_type(&binary.right, expected.clone(), binary.right.span());
|
let t2 = self.compare_expr_type(&binary.right, expected, binary.right.span());
|
||||||
|
|
||||||
return_incorrect_type(t1, t2, expected)
|
return_incorrect_type(t1, t2, expected)
|
||||||
}
|
}
|
||||||
BinaryOperation::Sub => {
|
BinaryOperation::Sub => {
|
||||||
self.assert_field_group_int_type(expected.clone(), binary.span());
|
self.assert_field_group_int_type(expected, binary.span());
|
||||||
let t1 = self.compare_expr_type(&binary.left, expected.clone(), binary.left.span());
|
let t1 = self.compare_expr_type(&binary.left, expected, binary.left.span());
|
||||||
let t2 = self.compare_expr_type(&binary.right, expected.clone(), binary.right.span());
|
let t2 = self.compare_expr_type(&binary.right, expected, binary.right.span());
|
||||||
|
|
||||||
return_incorrect_type(t1, t2, expected)
|
return_incorrect_type(t1, t2, expected)
|
||||||
}
|
}
|
||||||
BinaryOperation::Mul => {
|
BinaryOperation::Mul => {
|
||||||
self.assert_field_group_int_type(expected.clone(), binary.span());
|
self.assert_field_group_int_type(expected, binary.span());
|
||||||
|
|
||||||
let t1 = self.compare_expr_type(&binary.left, None, binary.left.span());
|
let t1 = self.compare_expr_type(&binary.left, None, binary.left.span());
|
||||||
let t2 = self.compare_expr_type(&binary.right, None, binary.right.span());
|
let t2 = self.compare_expr_type(&binary.right, None, binary.right.span());
|
||||||
@ -177,17 +177,17 @@ impl<'a> TypeChecker<'a> {
|
|||||||
// Allow `group` * `scalar` multiplication.
|
// Allow `group` * `scalar` multiplication.
|
||||||
match (t1.as_ref(), t2.as_ref()) {
|
match (t1.as_ref(), t2.as_ref()) {
|
||||||
(Some(Type::Group), Some(other)) | (Some(other), Some(Type::Group)) => {
|
(Some(Type::Group), Some(other)) | (Some(other), Some(Type::Group)) => {
|
||||||
self.assert_type(other.clone(), Some(Type::Scalar), binary.span());
|
self.assert_type(*other, Some(Type::Scalar), binary.span());
|
||||||
Some(Type::Group)
|
Some(Type::Group)
|
||||||
}
|
}
|
||||||
_ => return_incorrect_type(t1, t2, expected),
|
_ => return_incorrect_type(t1, t2, expected),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BinaryOperation::Div => {
|
BinaryOperation::Div => {
|
||||||
self.assert_field_int_type(expected.clone(), binary.span());
|
self.assert_field_int_type(expected, binary.span());
|
||||||
|
|
||||||
let t1 = self.compare_expr_type(&binary.left, expected.clone(), binary.left.span());
|
let t1 = self.compare_expr_type(&binary.left, expected, binary.left.span());
|
||||||
let t2 = self.compare_expr_type(&binary.right, expected.clone(), binary.right.span());
|
let t2 = self.compare_expr_type(&binary.right, expected, binary.right.span());
|
||||||
return_incorrect_type(t1, t2, expected)
|
return_incorrect_type(t1, t2, expected)
|
||||||
}
|
}
|
||||||
BinaryOperation::Pow => {
|
BinaryOperation::Pow => {
|
||||||
@ -198,7 +198,7 @@ impl<'a> TypeChecker<'a> {
|
|||||||
// Type A must be an int.
|
// Type A must be an int.
|
||||||
// Type B must be a unsigned int.
|
// Type B must be a unsigned int.
|
||||||
(Some(Type::IntegerType(_)), Some(Type::IntegerType(itype))) if !itype.is_signed() => {
|
(Some(Type::IntegerType(_)), Some(Type::IntegerType(itype))) if !itype.is_signed() => {
|
||||||
self.assert_type(t1.clone().unwrap(), expected, binary.span());
|
self.assert_type(t1.unwrap(), expected, binary.span());
|
||||||
}
|
}
|
||||||
// Type A was an int.
|
// Type A was an int.
|
||||||
// But Type B was not a unsigned int.
|
// But Type B was not a unsigned int.
|
||||||
@ -231,7 +231,7 @@ impl<'a> TypeChecker<'a> {
|
|||||||
t1
|
t1
|
||||||
}
|
}
|
||||||
BinaryOperation::Eq | BinaryOperation::Ne => {
|
BinaryOperation::Eq | BinaryOperation::Ne => {
|
||||||
self.assert_type(Type::Boolean, expected.clone(), binary.span());
|
self.assert_type(Type::Boolean, expected, binary.span());
|
||||||
|
|
||||||
let t1 = self.compare_expr_type(&binary.left, None, binary.left.span());
|
let t1 = self.compare_expr_type(&binary.left, None, binary.left.span());
|
||||||
let t2 = self.compare_expr_type(&binary.right, None, binary.right.span());
|
let t2 = self.compare_expr_type(&binary.right, None, binary.right.span());
|
||||||
@ -239,20 +239,20 @@ impl<'a> TypeChecker<'a> {
|
|||||||
return_incorrect_type(t1, t2, expected)
|
return_incorrect_type(t1, t2, expected)
|
||||||
}
|
}
|
||||||
BinaryOperation::Lt | BinaryOperation::Gt | BinaryOperation::Le | BinaryOperation::Ge => {
|
BinaryOperation::Lt | BinaryOperation::Gt | BinaryOperation::Le | BinaryOperation::Ge => {
|
||||||
self.assert_type(Type::Boolean, expected.clone(), binary.span());
|
self.assert_type(Type::Boolean, expected, binary.span());
|
||||||
|
|
||||||
let t1 = self.compare_expr_type(&binary.left, None, binary.left.span());
|
let t1 = self.compare_expr_type(&binary.left, None, binary.left.span());
|
||||||
self.assert_field_scalar_int_type(t1.clone(), binary.left.span());
|
self.assert_field_scalar_int_type(t1, binary.left.span());
|
||||||
|
|
||||||
let t2 = self.compare_expr_type(&binary.right, None, binary.right.span());
|
let t2 = self.compare_expr_type(&binary.right, None, binary.right.span());
|
||||||
self.assert_field_scalar_int_type(t2.clone(), binary.right.span());
|
self.assert_field_scalar_int_type(t2, binary.right.span());
|
||||||
|
|
||||||
return_incorrect_type(t1, t2, expected)
|
return_incorrect_type(t1, t2, expected)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Expression::Unary(unary) => match unary.op {
|
Expression::Unary(unary) => match unary.op {
|
||||||
UnaryOperation::Not => {
|
UnaryOperation::Not => {
|
||||||
self.assert_type(Type::Boolean, expected.clone(), unary.span());
|
self.assert_type(Type::Boolean, expected, unary.span());
|
||||||
self.compare_expr_type(&unary.inner, expected, unary.inner.span())
|
self.compare_expr_type(&unary.inner, expected, unary.inner.span())
|
||||||
}
|
}
|
||||||
UnaryOperation::Negate => {
|
UnaryOperation::Negate => {
|
||||||
@ -278,14 +278,14 @@ impl<'a> TypeChecker<'a> {
|
|||||||
},
|
},
|
||||||
Expression::Ternary(ternary) => {
|
Expression::Ternary(ternary) => {
|
||||||
self.compare_expr_type(&ternary.condition, Some(Type::Boolean), ternary.condition.span());
|
self.compare_expr_type(&ternary.condition, Some(Type::Boolean), ternary.condition.span());
|
||||||
let t1 = self.compare_expr_type(&ternary.if_true, expected.clone(), ternary.if_true.span());
|
let t1 = self.compare_expr_type(&ternary.if_true, expected, ternary.if_true.span());
|
||||||
let t2 = self.compare_expr_type(&ternary.if_false, expected.clone(), ternary.if_false.span());
|
let t2 = self.compare_expr_type(&ternary.if_false, expected, ternary.if_false.span());
|
||||||
return_incorrect_type(t1, t2, expected)
|
return_incorrect_type(t1, t2, expected)
|
||||||
}
|
}
|
||||||
Expression::Call(call) => match &*call.function {
|
Expression::Call(call) => match &*call.function {
|
||||||
Expression::Identifier(ident) => {
|
Expression::Identifier(ident) => {
|
||||||
if let Some(func) = self.symbol_table.lookup_fn(&ident.name) {
|
if let Some(func) = self.symbol_table.lookup_fn(&ident.name) {
|
||||||
let ret = self.assert_type(func.output.clone(), expected, ident.span());
|
let ret = self.assert_type(func.output, expected, ident.span());
|
||||||
|
|
||||||
if func.input.len() != call.arguments.len() {
|
if func.input.len() != call.arguments.len() {
|
||||||
self.handler.emit_err(
|
self.handler.emit_err(
|
||||||
@ -302,11 +302,7 @@ impl<'a> TypeChecker<'a> {
|
|||||||
.iter()
|
.iter()
|
||||||
.zip(call.arguments.iter())
|
.zip(call.arguments.iter())
|
||||||
.for_each(|(expected, argument)| {
|
.for_each(|(expected, argument)| {
|
||||||
self.compare_expr_type(
|
self.compare_expr_type(argument, Some(expected.get_variable().type_), argument.span());
|
||||||
argument,
|
|
||||||
Some(expected.get_variable().type_.clone()),
|
|
||||||
argument.span(),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(ret)
|
Some(ret)
|
||||||
|
@ -26,7 +26,7 @@ impl<'a> StatementVisitor<'a> for TypeChecker<'a> {
|
|||||||
let parent = self.parent.unwrap();
|
let parent = self.parent.unwrap();
|
||||||
|
|
||||||
// Would never be None.
|
// Would never be None.
|
||||||
let func_output_type = self.symbol_table.lookup_fn(&parent).map(|f| f.output.clone());
|
let func_output_type = self.symbol_table.lookup_fn(&parent).map(|f| f.output);
|
||||||
self.compare_expr_type(&input.expression, func_output_type, input.expression.span());
|
self.compare_expr_type(&input.expression, func_output_type, input.expression.span());
|
||||||
|
|
||||||
VisitResult::VisitChildren
|
VisitResult::VisitChildren
|
||||||
@ -51,7 +51,7 @@ impl<'a> StatementVisitor<'a> for TypeChecker<'a> {
|
|||||||
self.handler.emit_err(err);
|
self.handler.emit_err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.compare_expr_type(&input.value, Some(input.type_.clone()), input.value.span());
|
self.compare_expr_type(&input.value, Some(input.type_), input.value.span());
|
||||||
});
|
});
|
||||||
|
|
||||||
VisitResult::VisitChildren
|
VisitResult::VisitChildren
|
||||||
@ -70,7 +70,7 @@ impl<'a> StatementVisitor<'a> for TypeChecker<'a> {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(var.type_.clone())
|
Some(*var.type_)
|
||||||
} else {
|
} else {
|
||||||
self.handler.emit_err(
|
self.handler.emit_err(
|
||||||
TypeCheckerError::unknown_sym("variable", &input.assignee.identifier.name, input.assignee.span).into(),
|
TypeCheckerError::unknown_sym("variable", &input.assignee.identifier.name, input.assignee.span).into(),
|
||||||
@ -104,8 +104,8 @@ impl<'a> StatementVisitor<'a> for TypeChecker<'a> {
|
|||||||
self.handler.emit_err(err);
|
self.handler.emit_err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.compare_expr_type(&input.start, Some(input.type_.clone()), input.start.span());
|
self.compare_expr_type(&input.start, Some(input.type_), input.start.span());
|
||||||
self.compare_expr_type(&input.stop, Some(input.type_.clone()), input.stop.span());
|
self.compare_expr_type(&input.stop, Some(input.type_), input.stop.span());
|
||||||
|
|
||||||
VisitResult::VisitChildren
|
VisitResult::VisitChildren
|
||||||
}
|
}
|
||||||
|
@ -27,9 +27,6 @@ pub struct TypeChecker<'a> {
|
|||||||
pub(crate) negate: bool,
|
pub(crate) negate: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const FIELD_TYPE: Type = Type::Field;
|
|
||||||
const GROUP_TYPE: Type = Type::Group;
|
|
||||||
const SCALAR_TYPE: Type = Type::Scalar;
|
|
||||||
const INT_TYPES: [Type; 10] = [
|
const INT_TYPES: [Type; 10] = [
|
||||||
Type::IntegerType(IntegerType::I8),
|
Type::IntegerType(IntegerType::I8),
|
||||||
Type::IntegerType(IntegerType::I16),
|
Type::IntegerType(IntegerType::I16),
|
||||||
@ -43,6 +40,32 @@ const INT_TYPES: [Type; 10] = [
|
|||||||
Type::IntegerType(IntegerType::U128),
|
Type::IntegerType(IntegerType::U128),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const fn create_type_superset<const S: usize, const A: usize, const O: usize>(
|
||||||
|
subset: [Type; S],
|
||||||
|
additional: [Type; A],
|
||||||
|
) -> [Type; O] {
|
||||||
|
let mut superset: [Type; O] = [Type::IntegerType(IntegerType::U8); O];
|
||||||
|
let mut i = 0;
|
||||||
|
while i < S {
|
||||||
|
superset[i] = subset[i];
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
let mut j = 0;
|
||||||
|
while j < A {
|
||||||
|
superset[i + j] = additional[j];
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
superset
|
||||||
|
}
|
||||||
|
|
||||||
|
const FIELD_INT_TYPES: [Type; 11] = create_type_superset(INT_TYPES, [Type::Field]);
|
||||||
|
|
||||||
|
const FIELD_SCALAR_INT_TYPES: [Type; 12] = create_type_superset(FIELD_INT_TYPES, [Type::Scalar]);
|
||||||
|
|
||||||
|
const FIELD_GROUP_INT_TYPES: [Type; 12] = create_type_superset(FIELD_INT_TYPES, [Type::Group]);
|
||||||
|
|
||||||
|
const ALL_NUMERICAL_TYPES: [Type; 13] = create_type_superset(FIELD_GROUP_INT_TYPES, [Type::Scalar]);
|
||||||
|
|
||||||
impl<'a> TypeChecker<'a> {
|
impl<'a> TypeChecker<'a> {
|
||||||
pub fn new(symbol_table: &'a mut SymbolTable<'a>, handler: &'a Handler) -> Self {
|
pub fn new(symbol_table: &'a mut SymbolTable<'a>, handler: &'a Handler) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -57,7 +80,7 @@ impl<'a> TypeChecker<'a> {
|
|||||||
if let Some(expected) = expected {
|
if let Some(expected) = expected {
|
||||||
if type_ != expected {
|
if type_ != expected {
|
||||||
self.handler
|
self.handler
|
||||||
.emit_err(TypeCheckerError::type_should_be(type_.clone(), expected, span).into());
|
.emit_err(TypeCheckerError::type_should_be(type_, expected, span).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +88,7 @@ impl<'a> TypeChecker<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn assert_one_of_types(&self, type_: Option<Type>, expected: &[Type], span: Span) -> Option<Type> {
|
pub(crate) fn assert_one_of_types(&self, type_: Option<Type>, expected: &[Type], span: Span) -> Option<Type> {
|
||||||
if let Some(type_) = type_.clone() {
|
if let Some(type_) = type_ {
|
||||||
for t in expected.iter() {
|
for t in expected.iter() {
|
||||||
if &type_ == t {
|
if &type_ == t {
|
||||||
return Some(type_);
|
return Some(type_);
|
||||||
@ -85,23 +108,31 @@ impl<'a> TypeChecker<'a> {
|
|||||||
type_
|
type_
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn _assert_arith_type(&self, type_: Option<Type>, span: Span) -> Option<Type> {
|
||||||
|
self.assert_one_of_types(type_, &FIELD_GROUP_INT_TYPES, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn _assert_field_or_int_type(&self, type_: Option<Type>, span: Span) -> Option<Type> {
|
||||||
|
self.assert_one_of_types(type_, &FIELD_INT_TYPES, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn _assert_int_type(&self, type_: Option<Type>, span: Span) -> Option<Type> {
|
||||||
|
self.assert_one_of_types(type_, &INT_TYPES, span)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn assert_field_group_scalar_int_type(&self, type_: Option<Type>, span: Span) -> Option<Type> {
|
pub(crate) fn assert_field_group_scalar_int_type(&self, type_: Option<Type>, span: Span) -> Option<Type> {
|
||||||
self.assert_one_of_types(type_, , span)
|
self.assert_one_of_types(type_, &ALL_NUMERICAL_TYPES, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn assert_field_group_int_type(&self, type_: Option<Type>, span: Span) -> Option<Type> {
|
pub(crate) fn assert_field_group_int_type(&self, type_: Option<Type>, span: Span) -> Option<Type> {
|
||||||
self.assert_one_of_types(type_, , span)
|
self.assert_one_of_types(type_, &FIELD_GROUP_INT_TYPES, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn assert_field_scalar_int_type(&self, type_: Option<Type>, span: Span) -> Option<Type> {
|
pub(crate) fn assert_field_scalar_int_type(&self, type_: Option<Type>, span: Span) -> Option<Type> {
|
||||||
self.assert_one_of_types(type_, , span)
|
self.assert_one_of_types(type_, &FIELD_SCALAR_INT_TYPES, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn assert_field_int_type(&self, type_: Option<Type>, span: Span) -> Option<Type> {
|
pub(crate) fn assert_field_int_type(&self, type_: Option<Type>, span: Span) -> Option<Type> {
|
||||||
self.assert_one_of_types(type_, span)
|
self.assert_one_of_types(type_, &FIELD_INT_TYPES, span)
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn assert_int_type(&self, type_: Option<Type>, span: Span) -> Option<Type> {
|
|
||||||
self.assert_one_of_types(type_, , span)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8
tests/expectations/compiler/compiler/scalar/cmp.leo.out
Normal file
8
tests/expectations/compiler/compiler/scalar/cmp.leo.out
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
namespace: Compile
|
||||||
|
expectation: Pass
|
||||||
|
outputs:
|
||||||
|
- output:
|
||||||
|
- initial_input_ast: 85349bd16d49ff31c5ed3aeacf84ee6278d4d951409f8b055849eb41481e612e
|
||||||
|
initial_ast: f9d6d5d94f9aead249bf86205884b49eff222937842973864758c01757d3eafa
|
||||||
|
symbol_table: 240729bf3dd32b0ff548d132bc74bc91e42a8677f98d5fa1e388259e6a407d95
|
Loading…
Reference in New Issue
Block a user