Skip to content
21 changes: 11 additions & 10 deletions silverscript-lang/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ impl<'de> Deserialize<'de> for TypeBase {
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum ArrayDim {
Dynamic,
Inferred,
Fixed(usize),
Constant(String),
}
Expand All @@ -224,6 +225,7 @@ impl TypeRef {
for dim in &self.array_dims {
match dim {
ArrayDim::Dynamic => out.push_str("[]"),
ArrayDim::Inferred => out.push_str("[_]"),
ArrayDim::Fixed(size) => out.push_str(&format!("[{size}]")),
ArrayDim::Constant(name) => out.push_str(&format!("[{name}]")),
}
Expand Down Expand Up @@ -1159,7 +1161,13 @@ fn parse_type_name_pair(pair: Pair<'_, Rule>) -> Result<TypeRef, CompilerError>
Some(size_pair) => match size_pair.as_rule() {
Rule::array_size => {
let raw = size_pair.as_str().trim();
if let Ok(size) = raw.parse::<usize>() { ArrayDim::Fixed(size) } else { ArrayDim::Constant(raw.to_string()) }
if raw == "_" {
ArrayDim::Inferred
} else if let Ok(size) = raw.parse::<usize>() {
ArrayDim::Fixed(size)
} else {
ArrayDim::Constant(raw.to_string())
}
}
Rule::Identifier => ArrayDim::Constant(size_pair.as_str().to_string()),
_ => return Err(CompilerError::Unsupported("invalid array dimension".to_string())),
Expand Down Expand Up @@ -2077,15 +2085,8 @@ fn parse_cast<'i>(pair: Pair<'i, Rule>) -> Result<Expr<'i>, CompilerError> {
return Ok(Expr::new(ExprKind::Call { name: type_name, args, name_span: type_span }, span));
}

// Handle single byte cast (duplicate check removed above)
// Support type[N] syntax
if let Some(bracket_pos) = type_name.find('[') {
if type_name.ends_with(']') {
let size_str = &type_name[bracket_pos + 1..type_name.len() - 1];
if size_str.is_empty() || size_str.parse::<usize>().is_ok() {
return Ok(Expr::new(ExprKind::Call { name: type_name, args, name_span: type_span }, span));
}
}
if parse_type_ref(&type_name).is_ok() {
return Ok(Expr::new(ExprKind::Call { name: type_name, args, name_span: type_span }, span));
}

Err(CompilerError::Unsupported(format!("cast type not supported: {type_name}")))
Expand Down
301 changes: 273 additions & 28 deletions silverscript-lang/src/compiler.rs

Large diffs are not rendered by default.

58 changes: 47 additions & 11 deletions silverscript-lang/src/compiler/debug_value_types.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::collections::{HashMap, HashSet};

use crate::ast::{BinaryOp, Expr, ExprKind, IntrospectionKind, NullaryOp, UnaryOp, UnarySuffixKind};
use crate::ast::{BinaryOp, Expr, ExprKind, IntrospectionKind, NullaryOp, TypeBase, UnaryOp, UnarySuffixKind};
use crate::errors::CompilerError;

use super::{array_element_type, is_bytes_type};
use super::{array_element_type, is_bytes_type, parse_type_ref};

fn nullary_value_type(op: NullaryOp) -> &'static str {
match op {
Expand Down Expand Up @@ -32,11 +32,31 @@ fn introspection_value_type(kind: IntrospectionKind) -> &'static str {

fn builtin_call_value_type(name: &str) -> &'static str {
match name {
"int" => "int",
"bool" => "bool",
"byte" => "byte",
"string" => "string",
"pubkey" => "pubkey",
"sig" => "sig",
"datasig" => "datasig",
"OpBin2Num"
| "OpTxInputDaaScore"
| "OpTxGas"
| "OpTxPayloadLen"
| "OpTxInputIndex"
| "OpTxInputScriptSigLen"
| "OpTxInputSpkLen"
| "OpOutpointIndex"
| "OpTxOutputSpkLen"
| "OpAuthOutputCount"
| "OpAuthOutputIdx"
| "OpCovInputCount"
| "OpCovInputIdx"
| "OpCovOutputCount"
| "OpCovOutputIdx" => "int",
"OpTxInputIsCoinbase" => "bool",
"blake2b" | "sha256" | "OpSha256" => "byte[32]",
"bytes"
| "blake2b"
| "sha256"
| "OpSha256"
| "OpTxSubnetId"
| "OpTxPayloadSubstr"
| "OpOutpointTxId"
Expand All @@ -50,14 +70,25 @@ fn builtin_call_value_type(name: &str) -> &'static str {
| "ScriptPubKeyP2PK"
| "ScriptPubKeyP2SH"
| "ScriptPubKeyP2SHFromRedeemScript" => "byte[]",
"OpTxInputDaaScore" | "OpAuthOutputCount" | "OpCovInputCount" | "OpCovInputIdx" | "OpCovOutputCount" | "OpCovOutputIdx" => {
"int"
}
"OpInputCovenantId" => "byte[32]",
_ => "byte[]",
}
}

fn is_builtin_cast_type_name(name: &str) -> bool {
if matches!(name, "int" | "bool" | "byte" | "string" | "pubkey" | "sig" | "datasig") {
return true;
}
if !name.contains('[') {
return false;
}
let Ok(type_ref) = parse_type_ref(name) else {
return false;
};

!matches!(type_ref.base, TypeBase::Custom(_))
}

pub(super) fn infer_debug_expr_value_type<'i>(
expr: &Expr<'i>,
env: &HashMap<String, Expr<'i>>,
Expand Down Expand Up @@ -94,6 +125,8 @@ pub(super) fn infer_debug_expr_value_type<'i>(
let right_type = infer_debug_expr_value_type(right, env, types, visiting)?;
if left_type == "string" || right_type == "string" {
Ok("string".to_string())
} else if left_type == "byte" || right_type == "byte" {
Ok("int".to_string())
} else if is_bytes_type(&left_type) {
Ok(left_type)
} else if is_bytes_type(&right_type) {
Expand All @@ -102,9 +135,12 @@ pub(super) fn infer_debug_expr_value_type<'i>(
Ok("int".to_string())
}
}
BinaryOp::BitOr | BinaryOp::BitXor | BinaryOp::BitAnd | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => {
Ok("int".to_string())
BinaryOp::BitOr | BinaryOp::BitXor | BinaryOp::BitAnd => {
let left_type = infer_debug_expr_value_type(left, env, types, visiting)?;
let right_type = infer_debug_expr_value_type(right, env, types, visiting)?;
if left_type == right_type && is_bytes_type(&left_type) { Ok(left_type) } else { Ok("int".to_string()) }
}
BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => Ok("int".to_string()),
},
ExprKind::IfElse { then_expr, else_expr, .. } => {
let then_type = infer_debug_expr_value_type(then_expr, env, types, visiting)?;
Expand All @@ -130,7 +166,7 @@ pub(super) fn infer_debug_expr_value_type<'i>(
ExprKind::Nullary(kind) => Ok(nullary_value_type(*kind).to_string()),
ExprKind::Introspection { kind, .. } => Ok(introspection_value_type(*kind).to_string()),
ExprKind::Call { name, .. } => {
if name.starts_with("byte[") {
if is_builtin_cast_type_name(name) {
Ok(name.clone())
} else {
Ok(builtin_call_value_type(name).to_string())
Expand Down
2 changes: 1 addition & 1 deletion silverscript-lang/src/silverscript.pest
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ type_name = { base_type ~ array_suffix* }
base_type = { builtin_type | Identifier }
builtin_type = { "int" | "bool" | "string" | "pubkey" | "sig" | "datasig" | "byte" }
array_suffix = { "[" ~ array_size? ~ "]" }
array_size = { Identifier | (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*) }
array_size = { "_" | Identifier | (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*) }

VersionLiteral = @{ ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT+ }

Expand Down
2 changes: 1 addition & 1 deletion silverscript-lang/tests/apps/chess/chess_castle.sil
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ contract ChessCastle(
}

byte[32] settle_template = blake2b(settle_prefix + settle_suffix);
require(blake2b(settle_template + player_template) == route_templates.slice(256, 288));
require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288)));

SettleState next_state = {
player_template: player_template,
Expand Down
4 changes: 2 additions & 2 deletions silverscript-lang/tests/apps/chess/chess_castle_challenge.sil
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ contract ChessCastleChallengePrep(

int hash_start = selector * 32;
int hash_end = hash_start + 32;
byte[32] target_template = route_templates.slice(hash_start, hash_end);
byte[32] target_template = byte[32](route_templates.slice(hash_start, hash_end));

// After prep the challenger is committed to this proof move. The next
// worker sees recent_castle != 0 and must settle immediately on success.
Expand Down Expand Up @@ -252,7 +252,7 @@ contract ChessCastleChallengePrep(
}

byte[32] settle_template = blake2b(settle_prefix + settle_suffix);
require(blake2b(settle_template + player_template) == route_templates.slice(256, 288));
require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288)));

SettleState next_state = {
player_template: player_template,
Expand Down
2 changes: 1 addition & 1 deletion silverscript-lang/tests/apps/chess/chess_diag.sil
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ contract ChessDiag(
}

byte[32] settle_template = blake2b(settle_prefix + settle_suffix);
require(blake2b(settle_template + player_template) == route_templates.slice(256, 288));
require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288)));

SettleState next_state = {
player_template: player_template,
Expand Down
2 changes: 1 addition & 1 deletion silverscript-lang/tests/apps/chess/chess_horiz.sil
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ contract ChessHoriz(
}

byte[32] settle_template = blake2b(settle_prefix + settle_suffix);
require(blake2b(settle_template + player_template) == route_templates.slice(256, 288));
require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288)));

SettleState next_state = {
player_template: player_template,
Expand Down
2 changes: 1 addition & 1 deletion silverscript-lang/tests/apps/chess/chess_king.sil
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ contract ChessKing(
}

byte[32] settle_template = blake2b(settle_prefix + settle_suffix);
require(blake2b(settle_template + player_template) == route_templates.slice(256, 288));
require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288)));

SettleState next_state = {
player_template: player_template,
Expand Down
2 changes: 1 addition & 1 deletion silverscript-lang/tests/apps/chess/chess_knight.sil
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ contract ChessKnight(
}

byte[32] settle_template = blake2b(settle_prefix + settle_suffix);
require(blake2b(settle_template + player_template) == route_templates.slice(256, 288));
require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288)));

SettleState next_state = {
player_template: player_template,
Expand Down
6 changes: 3 additions & 3 deletions silverscript-lang/tests/apps/chess/chess_mux.sil
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ contract ChessMux(
byte[] all_route_templates = move_route_templates + mux_template;
int hash_start = selector * 32;
int hash_end = hash_start + 32;
byte[32] target_template = all_route_templates.slice(hash_start, hash_end);
byte[32] target_template = byte[32](all_route_templates.slice(hash_start, hash_end));
validateOutputStateWithTemplate(
output_idx,
next_state,
Expand Down Expand Up @@ -261,7 +261,7 @@ contract ChessMux(
}

byte[32] settle_template = blake2b(settle_prefix + settle_suffix);
require(blake2b(settle_template + player_template) == route_templates.slice(256, 288));
require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288)));

SettleState next_state = {
player_template: player_template,
Expand All @@ -278,7 +278,7 @@ contract ChessMux(
entrypoint function settle(byte[32] player_template, byte[] settle_prefix, byte[] settle_suffix) {
require(status != 0 /*LIVE*/);
byte[32] settle_template = blake2b(settle_prefix + settle_suffix);
require(blake2b(settle_template + player_template) == route_templates.slice(256, 288));
require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288)));

SettleState next_state = {
player_template: player_template,
Expand Down
2 changes: 1 addition & 1 deletion silverscript-lang/tests/apps/chess/chess_pawn.sil
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ contract ChessPawn(
}

byte[32] settle_template = blake2b(settle_prefix + settle_suffix);
require(blake2b(settle_template + player_template) == route_templates.slice(256, 288));
require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288)));

SettleState next_state = {
player_template: player_template,
Expand Down
2 changes: 1 addition & 1 deletion silverscript-lang/tests/apps/chess/chess_vert.sil
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ contract ChessVert(
}

byte[32] settle_template = blake2b(settle_prefix + settle_suffix);
require(blake2b(settle_template + player_template) == route_templates.slice(256, 288));
require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288)));

SettleState next_state = {
player_template: player_template,
Expand Down
2 changes: 1 addition & 1 deletion silverscript-lang/tests/apps/chess/player.sil
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ contract Player(

int leader_input_idx = OpCovInputIdx(cov_id, 0);
require(blake2b(route_templates) == routes_commitment);
require(blake2b(settle_template + player_template) == route_templates.slice(256, 288));
require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288)));
SettleState leader = readInputStateWithTemplate(leader_input_idx, settle_prefix_len, settle_suffix_len, settle_template);

// Settlement is driven by a terminal settle worker state, not by a
Expand Down
46 changes: 23 additions & 23 deletions silverscript-lang/tests/chess_apps_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -678,58 +678,58 @@ fn size_snapshots() -> Vec<SizeSnapshot> {
SizeSnapshot {
name: "chess_pawn.sil",
ctor: pawn_constructor_args,
expected_script_len: 1846,
expected_instruction_count: 1219,
expected_script_len: 1833,
expected_instruction_count: 1208,
expected_charged_op_count: 794,
},
SizeSnapshot {
name: "chess_knight.sil",
ctor: pawn_constructor_args,
expected_script_len: 1392,
expected_instruction_count: 801,
expected_charged_op_count: 529,
expected_script_len: 1383,
expected_instruction_count: 794,
expected_charged_op_count: 527,
},
SizeSnapshot {
name: "chess_vert.sil",
ctor: pawn_constructor_args,
expected_script_len: 2060,
expected_instruction_count: 1416,
expected_charged_op_count: 925,
expected_script_len: 2035,
expected_instruction_count: 1393,
expected_charged_op_count: 915,
},
SizeSnapshot {
name: "chess_horiz.sil",
ctor: pawn_constructor_args,
expected_script_len: 2060,
expected_instruction_count: 1416,
expected_charged_op_count: 925,
expected_script_len: 2035,
expected_instruction_count: 1393,
expected_charged_op_count: 915,
},
SizeSnapshot {
name: "chess_diag.sil",
ctor: pawn_constructor_args,
expected_script_len: 1823,
expected_instruction_count: 1217,
expected_charged_op_count: 795,
expected_script_len: 1814,
expected_instruction_count: 1210,
expected_charged_op_count: 793,
},
SizeSnapshot {
name: "chess_king.sil",
ctor: pawn_constructor_args,
expected_script_len: 1537,
expected_instruction_count: 944,
expected_charged_op_count: 621,
expected_script_len: 1512,
expected_instruction_count: 921,
expected_charged_op_count: 611,
},
SizeSnapshot {
name: "chess_castle.sil",
ctor: pawn_constructor_args,
expected_script_len: 1548,
expected_instruction_count: 951,
expected_charged_op_count: 617,
expected_script_len: 1523,
expected_instruction_count: 928,
expected_charged_op_count: 608,
},
SizeSnapshot {
name: "chess_castle_challenge.sil",
ctor: pawn_constructor_args,
expected_script_len: 1762,
expected_instruction_count: 1149,
expected_charged_op_count: 744,
expected_script_len: 1735,
expected_instruction_count: 1124,
expected_charged_op_count: 733,
},
]
}
Expand Down
Loading