use crate::ast::*;
use crate::parser::{parse_expression, parse_solidity, SolidityParser};
use crate::solidity_writer::write_source_unit;
use pest::Parser;
fn test_parse_expression(input: &str) -> bool {
match parse_expression(input) {
Ok(_) => true,
Err(e) => {
println!("Parse error for '{}': {}", input, e);
false
}
}
}
fn test_parse_solidity(input: &str) -> bool {
match parse_solidity(input) {
Ok(_) => true,
Err(e) => {
println!("Parse error for Solidity source: {}", e);
false
}
}
}
#[test]
fn test_basic_arithmetic_expressions() {
assert!(test_parse_expression("1 + 2"));
assert!(test_parse_expression("a - b"));
assert!(test_parse_expression("x * y"));
assert!(test_parse_expression("total / count"));
assert!(test_parse_expression("value % 10"));
assert!(test_parse_expression("base ** exponent"));
}
#[test]
fn test_comparison_expressions() {
assert!(test_parse_expression("a == b"));
assert!(test_parse_expression("x != y"));
assert!(test_parse_expression("value > 0"));
assert!(test_parse_expression("balance >= amount"));
assert!(test_parse_expression("count < limit"));
assert!(test_parse_expression("age <= maxAge"));
}
#[test]
fn test_logical_expressions() {
assert!(test_parse_expression("isValid && isActive"));
assert!(test_parse_expression("hasPermission || isOwner"));
assert!(test_parse_expression("!isLocked"));
assert!(test_parse_expression("!(a && b)"));
assert!(test_parse_expression("a && b || c"));
assert!(test_parse_expression("(a || b) && (c || d)"));
}
#[test]
fn test_bitwise_expressions() {
assert!(test_parse_expression("flags & mask"));
assert!(test_parse_expression("value | flag"));
assert!(test_parse_expression("a ^ b"));
assert!(test_parse_expression("~bits"));
assert!(test_parse_expression("value << 2"));
assert!(test_parse_expression("data >> 8"));
}
#[test]
fn test_assignment_expressions() {
assert!(test_parse_expression("x = y"));
assert!(test_parse_expression("balance += amount"));
assert!(test_parse_expression("count -= 1"));
assert!(test_parse_expression("total *= factor"));
assert!(test_parse_expression("value /= divisor"));
assert!(test_parse_expression("remainder %= modulus"));
assert!(test_parse_expression("flags |= newFlag"));
assert!(test_parse_expression("mask &= filter"));
assert!(test_parse_expression("data ^= key"));
assert!(test_parse_expression("value <<= shift"));
assert!(test_parse_expression("data >>= offset"));
}
#[test]
fn test_unary_expressions() {
assert!(test_parse_expression("++counter"));
assert!(test_parse_expression("--index"));
assert!(test_parse_expression("counter++"));
assert!(test_parse_expression("index--"));
assert!(test_parse_expression("+value"));
assert!(test_parse_expression("-amount"));
assert!(test_parse_expression("!flag"));
assert!(test_parse_expression("~mask"));
assert!(test_parse_expression("delete data"));
}
#[test]
fn test_conditional_expressions() {
assert!(test_parse_expression("condition ? trueValue : falseValue"));
assert!(test_parse_expression("x > 0 ? x : -x"));
assert!(test_parse_expression(
"isValid ? processData() : handleError()"
));
assert!(test_parse_expression(
"a > b ? a > c ? a : c : b > c ? b : c"
));
}
#[test]
fn test_complex_predicates() {
assert!(test_parse_expression("msg.sender == owner"));
assert!(test_parse_expression("block.timestamp >= startTime"));
assert!(test_parse_expression("balances[from] >= amount"));
assert!(test_parse_expression(
"allowances[from][msg.sender] >= amount"
));
assert!(test_parse_expression("totalSupply + amount <= maxSupply"));
assert!(test_parse_expression(
"isActive && !isPaused && block.timestamp >= startTime"
));
assert!(test_parse_expression(
"(msg.sender == owner || hasRole(ADMIN_ROLE, msg.sender)) && amount > 0"
));
assert!(test_parse_expression(
"balances[account] >= amount && amount > 0 && account != address(0)"
));
assert!(test_parse_expression(
"(a > 0 && b > 0) || (c > 0 && d > 0)"
));
assert!(test_parse_expression(
"!(paused || locked) && (isWhitelisted || publicSale)"
));
}
#[test]
fn test_function_call_expressions() {
assert!(test_parse_expression("balanceOf(account)"));
assert!(test_parse_expression("transfer(to, amount)"));
assert!(test_parse_expression("approve(spender, amount)"));
assert!(test_parse_expression("keccak256(abi.encodePacked(data))"));
assert!(test_parse_expression(
"require(condition, \"Error message\")"
));
assert!(test_parse_expression("token.balanceOf(msg.sender)"));
assert!(test_parse_expression("contracts[id].isActive()"));
assert!(test_parse_expression("min(max(value, minValue), maxValue)"));
assert!(test_parse_expression("sqrt(a * a + b * b)"));
}
#[test]
fn test_member_access_expressions() {
assert!(test_parse_expression("msg.sender"));
assert!(test_parse_expression("msg.value"));
assert!(test_parse_expression("block.timestamp"));
assert!(test_parse_expression("block.number"));
assert!(test_parse_expression("tx.origin"));
assert!(test_parse_expression("address(this).balance"));
assert!(test_parse_expression("user.profile.name"));
assert!(test_parse_expression("settings.config.maxSupply"));
}
#[test]
fn test_array_access_expressions() {
assert!(test_parse_expression("balances[account]"));
assert!(test_parse_expression("allowances[owner][spender]"));
assert!(test_parse_expression("data[index]"));
assert!(test_parse_expression("matrix[row][col]"));
assert!(test_parse_expression("users[userCount - 1]"));
assert!(test_parse_expression("tokens[tokenId]"));
assert!(test_parse_expression("mappings[keccak256(key)][subKey]"));
}
#[test]
fn test_type_conversion_expressions() {
assert!(test_parse_expression("uint256(value)"));
assert!(test_parse_expression("address(contractAddress)"));
assert!(test_parse_expression("bytes32(data)"));
assert!(test_parse_expression("bool(flag)"));
assert!(test_parse_expression("uint256(bytes32(data))"));
assert!(test_parse_expression("address(uint160(addressValue))"));
}
#[test]
fn test_literal_expressions() {
assert!(test_parse_expression("42"));
assert!(test_parse_expression("0"));
assert!(test_parse_expression("1000000"));
assert!(test_parse_expression("0x1234"));
assert!(test_parse_expression("0xFF"));
assert!(test_parse_expression("1 ether"));
assert!(test_parse_expression("1000 wei"));
assert!(test_parse_expression("1 gwei"));
assert!(test_parse_expression("30 days"));
assert!(test_parse_expression("1 hours"));
assert!(test_parse_expression("60 seconds"));
assert!(test_parse_expression("\"Hello, World!\""));
assert!(test_parse_expression("\"Error: insufficient balance\""));
assert!(test_parse_expression("'Single quoted string'"));
assert!(test_parse_expression("true"));
assert!(test_parse_expression("false"));
assert!(test_parse_expression("hex\"deadbeef\""));
assert!(test_parse_expression("hex'1234'"));
assert!(test_parse_expression("unicode\"Hello 🌍\""));
}
#[test]
fn test_operator_precedence() {
let expr = parse_expression("1 + 2 * 3").unwrap();
if let Expression::Binary(binary) = expr {
assert!(matches!(binary.operator, BinaryOperator::Add));
if let Expression::Binary(right_binary) = *binary.right {
assert!(matches!(right_binary.operator, BinaryOperator::Mul));
} else {
panic!("Expected multiplication on the right side");
}
} else {
panic!("Expected binary expression");
}
let expr = parse_expression("2 ** 3 * 4").unwrap();
if let Expression::Binary(binary) = expr {
assert!(matches!(binary.operator, BinaryOperator::Mul));
if let Expression::Binary(left_binary) = *binary.left {
assert!(matches!(left_binary.operator, BinaryOperator::Exp));
} else {
panic!("Expected exponentiation on the left side");
}
} else {
panic!("Expected binary expression");
}
}
#[test]
fn test_parenthesized_expressions() {
assert!(test_parse_expression("(a + b)"));
assert!(test_parse_expression("(a + b) * c"));
assert!(test_parse_expression("a * (b + c)"));
assert!(test_parse_expression("((a + b) * c) / d"));
let expr = parse_expression("(1 + 2) * 3").unwrap();
if let Expression::Binary(binary) = expr {
assert!(matches!(binary.operator, BinaryOperator::Mul));
} else {
panic!("Expected binary expression");
}
}
#[test]
fn test_tuple_expressions() {
assert!(test_parse_expression("(a, b)"));
assert!(test_parse_expression("(a, b, c)"));
assert!(test_parse_expression("(, b, )")); assert!(test_parse_expression("()")); }
#[test]
fn test_array_literal_expressions() {
assert!(test_parse_expression("[1, 2, 3]"));
assert!(test_parse_expression("[a, b, c]"));
assert!(test_parse_expression("[]")); assert!(test_parse_expression("[uint256(1), uint256(2)]"));
}
#[test]
fn test_complex_expressions() {
assert!(test_parse_expression(
"balances[from] >= amount && allowances[from][msg.sender] >= amount && to != address(0)"
));
assert!(test_parse_expression(
"block.timestamp >= startTime && block.timestamp <= endTime && !paused"
));
assert!(test_parse_expression(
"totalSupply + amount <= maxSupply && amount > 0 && msg.value >= price * amount"
));
assert!(test_parse_expression(
"(msg.sender == owner || hasRole(MINTER_ROLE, msg.sender)) && amount <= maxMintPerTx"
));
assert!(test_parse_expression(
"sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)"
));
assert!(test_parse_expression(
"principal * rate * time / (365 * 24 * 3600 * 10000)"
));
}
#[test]
fn test_solidity_require_statements() {
assert!(test_parse_expression("msg.sender != address(0)"));
assert!(test_parse_expression("amount > 0"));
assert!(test_parse_expression("balance >= amount"));
assert!(test_parse_expression("block.timestamp >= unlockTime"));
assert!(test_parse_expression("!paused"));
assert!(test_parse_expression("isAuthorized[msg.sender]"));
assert!(test_parse_expression("nonces[owner] == nonce"));
assert!(test_parse_expression("deadline >= block.timestamp"));
}
#[test]
fn test_solidity_modifier_conditions() {
assert!(test_parse_expression("msg.sender == owner"));
assert!(test_parse_expression("hasRole(role, account)"));
assert!(test_parse_expression("!locked"));
assert!(test_parse_expression("initialized"));
assert!(test_parse_expression(
"block.timestamp > lastUpdate + cooldown"
));
}
#[test]
fn test_invalid_expressions() {
assert!(!test_parse_expression(""));
assert!(!test_parse_expression("1 +"));
assert!(!test_parse_expression("(1 + 2"));
assert!(!test_parse_expression("1 + 2)"));
assert!(!test_parse_expression("1 2"));
assert!(!test_parse_expression("a b"));
}
#[test]
fn test_simple_solidity_contract() {
let source = r#"
pragma solidity ^0.8.0;
contract SimpleStorage {
uint256 public value;
function setValue(uint256 _value) public {
value = _value;
}
function getValue() public view returns (uint256) {
return value;
}
}
"#;
assert!(test_parse_solidity(source));
}
#[test]
fn test_contract_with_complex_expressions() {
let source = r#"
pragma solidity ^0.8.0;
contract TokenContract {
mapping(address => uint256) public balances;
uint256 public totalSupply;
address public owner;
bool public paused;
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
modifier whenNotPaused() {
require(!paused, "Contract is paused");
_;
}
function transfer(address to, uint256 amount) public whenNotPaused returns (bool) {
require(to != address(0), "Invalid address");
require(balances[msg.sender] >= amount, "Insufficient balance");
require(amount > 0, "Amount must be positive");
balances[msg.sender] -= amount;
balances[to] += amount;
return true;
}
function mint(address to, uint256 amount) public onlyOwner {
require(to != address(0), "Invalid address");
require(totalSupply + amount <= 1000000 * 10**18, "Max supply exceeded");
balances[to] += amount;
totalSupply += amount;
}
}
"#;
assert!(test_parse_solidity(source));
}
#[test]
fn test_expression_parsing_details() {
let expr = parse_expression("a + b * c").unwrap();
match expr {
Expression::Binary(binary) => {
assert!(matches!(binary.operator, BinaryOperator::Add));
if let Expression::Identifier(name) = *binary.left {
assert_eq!(name, "a");
} else {
panic!("Expected identifier 'a' on left side");
}
if let Expression::Binary(right_binary) = *binary.right {
assert!(matches!(right_binary.operator, BinaryOperator::Mul));
if let Expression::Identifier(left_name) = *right_binary.left {
assert_eq!(left_name, "b");
} else {
panic!("Expected identifier 'b'");
}
if let Expression::Identifier(right_name) = *right_binary.right {
assert_eq!(right_name, "c");
} else {
panic!("Expected identifier 'c'");
}
} else {
panic!("Expected multiplication on right side");
}
}
_ => panic!("Expected binary expression"),
}
}
#[test]
fn test_comparison_expression_parsing() {
let expr = parse_expression("balance >= amount").unwrap();
match expr {
Expression::Binary(binary) => {
assert!(matches!(
binary.operator,
BinaryOperator::GreaterThanOrEqual
));
if let Expression::Identifier(left_name) = *binary.left {
assert_eq!(left_name, "balance");
} else {
panic!("Expected identifier 'balance'");
}
if let Expression::Identifier(right_name) = *binary.right {
assert_eq!(right_name, "amount");
} else {
panic!("Expected identifier 'amount'");
}
}
_ => panic!("Expected binary expression"),
}
}
#[test]
fn test_logical_expression_parsing() {
let expr = parse_expression("isValid && !isPaused").unwrap();
match expr {
Expression::Binary(binary) => {
assert!(matches!(binary.operator, BinaryOperator::And));
if let Expression::Identifier(left_name) = *binary.left {
assert_eq!(left_name, "isValid");
} else {
panic!("Expected identifier 'isValid'");
}
if let Expression::Unary(unary) = *binary.right {
assert!(matches!(unary.operator, UnaryOperator::Not));
assert!(unary.is_prefix);
if let Expression::Identifier(operand_name) = *unary.operand {
assert_eq!(operand_name, "isPaused");
} else {
panic!("Expected identifier 'isPaused'");
}
} else {
panic!("Expected unary expression on right side");
}
}
_ => panic!("Expected binary expression"),
}
}
#[test]
fn test_assignment_expression_parsing() {
let expr = parse_expression("balance += amount").unwrap();
match expr {
Expression::Assignment(assignment) => {
assert!(matches!(assignment.operator, AssignmentOperator::AddAssign));
if let Expression::Identifier(left_name) = *assignment.left {
assert_eq!(left_name, "balance");
} else {
panic!("Expected identifier 'balance'");
}
if let Expression::Identifier(right_name) = *assignment.right {
assert_eq!(right_name, "amount");
} else {
panic!("Expected identifier 'amount'");
}
}
_ => panic!("Expected assignment expression"),
}
}
#[test]
fn test_literal_parsing() {
let expr = parse_expression("true").unwrap();
match expr {
Expression::Literal(Literal::Boolean(value)) => {
assert!(value);
}
_ => panic!("Expected boolean literal"),
}
let expr = parse_expression("42").unwrap();
match expr {
Expression::Literal(Literal::Number(number)) => {
assert_eq!(number.value, "42");
}
_ => panic!("Expected number literal"),
}
let expr = parse_expression("\"hello\"").unwrap();
match expr {
Expression::Literal(Literal::String(string)) => {
assert_eq!(string.value, "hello");
}
_ => panic!("Expected string literal"),
}
}
#[test]
fn test_pest_parser_direct() {
use crate::parser::Rule;
let result = SolidityParser::parse(Rule::identifier, "myVariable");
assert!(result.is_ok());
let result = SolidityParser::parse(Rule::number_literal, "42");
assert!(result.is_ok());
let result = SolidityParser::parse(Rule::boolean_literal, "true");
assert!(result.is_ok());
let result = SolidityParser::parse(Rule::string_literal, "\"hello\"");
assert!(result.is_ok());
let result = SolidityParser::parse(Rule::additive_expression, "a + b");
assert!(result.is_ok());
let result = SolidityParser::parse(Rule::multiplicative_expression, "a * b");
assert!(result.is_ok());
let result = SolidityParser::parse(Rule::equality_expression, "a == b");
assert!(result.is_ok());
let result = SolidityParser::parse(Rule::relational_expression, "a > b");
assert!(result.is_ok());
let result = SolidityParser::parse(Rule::logical_and_expression, "a && b");
assert!(result.is_ok());
let result = SolidityParser::parse(Rule::logical_or_expression, "a || b");
assert!(result.is_ok());
}
#[test]
fn test_write_simple_contract() {
use crate::solidity_writer::write_source_unit;
let contract = ContractDefinition {
is_abstract: false,
name: "SimpleStorage".to_string(),
inheritance: vec![],
body: vec![
ContractBodyElement::StateVariable(StateVariableDeclaration {
type_name: TypeName::Elementary(ElementaryTypeName::UnsignedInteger(Some(256))),
visibility: Some(Visibility::Public),
is_constant: false,
is_immutable: false,
is_transient: false,
override_specifier: None,
name: "value".to_string(),
initial_value: None,
}),
ContractBodyElement::Function(FunctionDefinition {
name: Some("setValue".to_string()),
parameters: vec![Parameter {
type_name: TypeName::Elementary(ElementaryTypeName::UnsignedInteger(Some(256))),
data_location: None,
name: Some("_value".to_string()),
}],
visibility: Some(Visibility::Public),
state_mutability: None,
modifiers: vec![],
is_virtual: false,
override_specifier: None,
returns: None,
body: Some(Block {
statements: vec![Statement::Expression(ExpressionStatement {
expression: Expression::Assignment(AssignmentExpression {
left: Box::new(Expression::Identifier("value".to_string())),
operator: AssignmentOperator::Assign,
right: Box::new(Expression::Identifier("_value".to_string())),
}),
})],
}),
}),
],
};
let source_unit = SourceUnit {
items: vec![
SourceUnitItem::Pragma(PragmaDirective {
tokens: vec!["solidity".to_string(), "^0.8.0".to_string()],
}),
SourceUnitItem::Contract(contract),
],
};
let output = write_source_unit(&source_unit);
assert!(output.contains("pragma solidity ^0.8.0;"));
assert!(output.contains("contract SimpleStorage"));
assert!(output.contains("uint256 public value;"));
assert!(output.contains("function setValue(uint256 _value) public"));
assert!(output.contains("value = _value;"));
println!("Generated Solidity code:\n{}", output);
}
#[test]
fn test_write_pragma_directive() {
use crate::solidity_writer::write_source_unit;
let source_unit = SourceUnit {
items: vec![
SourceUnitItem::Pragma(PragmaDirective {
tokens: vec!["solidity".to_string(), ">=0.8.0".to_string(), "<0.9.0".to_string()],
}),
],
};
let output = write_source_unit(&source_unit);
assert_eq!(output.trim(), "pragma solidity >=0.8.0 <0.9.0;");
}
#[test]
fn test_write_import_directive() {
use crate::solidity_writer::write_source_unit;
let source_unit = SourceUnit {
items: vec![
SourceUnitItem::Import(ImportDirective {
path: "./Token.sol".to_string(),
alias: Some("Token".to_string()),
symbols: None,
}),
SourceUnitItem::Import(ImportDirective {
path: "@openzeppelin/contracts/token/ERC20/IERC20.sol".to_string(),
alias: None,
symbols: Some(vec![
ImportSymbol {
name: "IERC20".to_string(),
alias: None,
},
ImportSymbol {
name: "IERC20Metadata".to_string(),
alias: Some("Metadata".to_string()),
},
]),
}),
],
};
let output = write_source_unit(&source_unit);
assert!(output.contains("import * as Token from \"./Token.sol\";"));
assert!(output.contains("import {IERC20, IERC20Metadata as Metadata} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";"));
}
#[test]
fn test_write_expressions() {
use crate::solidity_writer::write_source_unit;
let expressions = vec![
Expression::Binary(BinaryExpression {
left: Box::new(Expression::Identifier("a".to_string())),
operator: BinaryOperator::Add,
right: Box::new(Expression::Identifier("b".to_string())),
}),
Expression::Unary(UnaryExpression {
operator: UnaryOperator::Not,
operand: Box::new(Expression::Identifier("flag".to_string())),
is_prefix: true,
}),
Expression::FunctionCall(FunctionCallExpression {
function: Box::new(Expression::Identifier("transfer".to_string())),
arguments: vec![
Expression::Identifier("to".to_string()),
Expression::Identifier("amount".to_string()),
],
}),
Expression::MemberAccess(MemberAccessExpression {
object: Box::new(Expression::Identifier("msg".to_string())),
member: "sender".to_string(),
}),
Expression::IndexAccess(IndexAccessExpression {
object: Box::new(Expression::Identifier("balances".to_string())),
index: Some(Box::new(Expression::Identifier("account".to_string()))),
}),
];
let function = FunctionDefinition {
name: Some("testExpressions".to_string()),
parameters: vec![],
visibility: Some(Visibility::Public),
state_mutability: Some(StateMutability::Pure),
modifiers: vec![],
is_virtual: false,
override_specifier: None,
returns: None,
body: Some(Block {
statements: expressions.into_iter().map(|expr| {
Statement::Expression(ExpressionStatement { expression: expr })
}).collect(),
}),
};
let source_unit = SourceUnit {
items: vec![SourceUnitItem::Function(function)],
};
let output = write_source_unit(&source_unit);
assert!(output.contains("a + b;"));
assert!(output.contains("!flag;"));
assert!(output.contains("transfer(to, amount);"));
assert!(output.contains("msg.sender;"));
assert!(output.contains("balances[account];"));
println!("Generated expressions:\n{}", output);
}
#[test]
fn test_write_type_names() {
use crate::solidity_writer::write_source_unit;
let struct_def = StructDefinition {
name: "TestStruct".to_string(),
members: vec![
StructMember {
type_name: TypeName::Elementary(ElementaryTypeName::Address),
name: "addr".to_string(),
},
StructMember {
type_name: TypeName::Elementary(ElementaryTypeName::UnsignedInteger(Some(256))),
name: "amount".to_string(),
},
StructMember {
type_name: TypeName::Elementary(ElementaryTypeName::Bool),
name: "isActive".to_string(),
},
StructMember {
type_name: TypeName::Array(
Box::new(TypeName::Elementary(ElementaryTypeName::UnsignedInteger(Some(256)))),
None,
),
name: "values".to_string(),
},
StructMember {
type_name: TypeName::Mapping(MappingType {
key_type: Box::new(TypeName::Elementary(ElementaryTypeName::Address)),
key_name: None,
value_type: Box::new(TypeName::Elementary(ElementaryTypeName::UnsignedInteger(Some(256)))),
value_name: None,
}),
name: "balances".to_string(),
},
],
};
let source_unit = SourceUnit {
items: vec![SourceUnitItem::Struct(struct_def)],
};
let output = write_source_unit(&source_unit);
assert!(output.contains("struct TestStruct {"));
assert!(output.contains("address addr;"));
assert!(output.contains("uint256 amount;"));
assert!(output.contains("bool isActive;"));
assert!(output.contains("uint256[] values;"));
assert!(output.contains("mapping(address => uint256) balances;"));
println!("Generated struct:\n{}", output);
}
#[test]
fn test_roundtrip_parsing_and_writing() {
let original_source = r#"pragma solidity ^0.8.0;
contract TestContract {
}"#;
let parsed = parse_solidity(original_source).expect("Failed to parse Solidity source");
let written = write_source_unit(&parsed);
println!("Original:\n{}\n", original_source);
println!("Roundtrip:\n{}", written);
assert!(written.contains("pragma solidity ^ 0.8.0;"), "Missing pragma directive in output: {}", written);
assert!(written.contains("contract TestContract"));
assert!(written.contains("{"));
assert!(written.contains("}"));
}
#[test]
fn test_debug_parser_output() {
let source = r#"pragma solidity ^0.8.0;"#;
match parse_solidity(source) {
Ok(parsed) => {
println!("Parsed successfully!");
println!("Number of items: {}", parsed.items.len());
for (i, item) in parsed.items.iter().enumerate() {
println!("Item {}: {:?}", i, item);
}
}
Err(e) => {
println!("Parse error: {}", e);
}
}
}
#[test]
fn test_debug_simple_contract() {
let source = r#"contract Test {
uint256 value;
}"#;
match parse_solidity(source) {
Ok(parsed) => {
println!("Parsed successfully!");
println!("Number of items: {}", parsed.items.len());
for (i, item) in parsed.items.iter().enumerate() {
println!("Item {}: {:?}", i, item);
}
let written = write_source_unit(&parsed);
println!("Written output:\n{}", written);
}
Err(e) => {
println!("Parse error: {}", e);
}
}
}