Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- [Comparison Operators](#comparison-operators)
- [Logical Operators](#logical-operators)
- [Bitwise Operators](#bitwise-operators)
- [Ternary Operator](#ternary-operator)
6. [Control Flow](#control-flow)
- [If Statements](#if-statements)
- [Require Statements](#require-statements)
Expand Down Expand Up @@ -403,6 +404,26 @@ int bitOr = x | y; // 0xFF (bitwise OR)
int bitXor = x ^ y; // 0xFF (bitwise XOR)
```

### Ternary Operator

Use the ternary operator to choose between two expressions:

```javascript
bool condition = true;
int thenValue = 100;
int elseValue = 50;
int value = condition ? thenValue : elseValue;
```

The condition must evaluate to `bool`, and both result branches must have the same type. The ternary expression's result must also match the declared type where it is assigned or returned:

```javascript
entrypoint function example(int amount, bool useBonus) {
int payout = useBonus ? amount + 100 : amount;
require(payout >= amount);
}
```

---

## Control Flow
Expand Down
8 changes: 5 additions & 3 deletions silverscript-lang/src/compiler/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use super::infer_array::lower_inferred_array_sizes;
use super::inline_functions::lower_inline_functions;
use super::locals::lower_local_aliases;
use super::stack_bindings::StackBindings;
use super::static_check::static_check_contract;
use super::*;
use kaspa_txscript::opcodes::codes::*;
use kaspa_txscript::script_builder::ScriptBuilder;
Expand Down Expand Up @@ -98,14 +99,15 @@ pub(super) fn compile_contract_impl<'i>(
}

let mut debug_recorder = DebugRecorder::new(options, contract)?;
let covenant_lowered_contract = lower_covenant_declarations(contract, &constants)?;
let inferred_lowered_contract = lower_inferred_array_sizes(contract, &constants)?;
static_check_contract(&inferred_lowered_contract, constructor_args, options)?;
let covenant_lowered_contract = lower_covenant_declarations(&inferred_lowered_contract, &constants)?;
let inline_lowered_contract = lower_inline_functions(&covenant_lowered_contract, &mut debug_recorder)?;
let structs = build_struct_registry(&inline_lowered_contract)?;
let struct_lowered_contract = lower_structs_contract(&inline_lowered_contract, &structs, &constants)?;
let append_lowered_contract = lower_array_appends(&struct_lowered_contract)?;
let for_lowered_contract = lower_for_loops(&append_lowered_contract, &constants)?;
let lowered_contract = lower_inferred_array_sizes(&for_lowered_contract, &constants)?;
let lowered_contract = if options.record_debug_infos { lowered_contract } else { lower_local_aliases(&lowered_contract)? };
let lowered_contract = if options.record_debug_infos { for_lowered_contract } else { lower_local_aliases(&for_lowered_contract)? };
let mut lowered_constants = flatten_constructor_args_env(&covenant_lowered_contract.params, constructor_args, &structs)?;
lowered_constants.extend(lowered_contract.constants.iter().map(|constant| (constant.name.clone(), constant.expr.clone())));

Expand Down
138 changes: 105 additions & 33 deletions silverscript-lang/src/compiler/infer_array.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use std::collections::HashMap;

use super::compile::{array_literal_matches_type_with_env_ref, type_name_from_ref};
use super::compile::type_name_from_ref;
use super::*;
use crate::ast::{ArrayDim, ConstantAst, ContractAst, ContractFieldAst, FunctionAst, ParamAst, Statement, TypeRef};

pub(super) fn lower_inferred_array_sizes<'i>(
contract: &ContractAst<'i>,
contract_constants: &HashMap<String, Expr<'i>>,
) -> Result<ContractAst<'i>, CompilerError> {
let functions_by_name: HashMap<String, &FunctionAst<'i>> =
contract.functions.iter().map(|function| (function.name.clone(), function)).collect();
let mut top_level_types = HashMap::new();
for param in &contract.params {
top_level_types.insert(param.name.clone(), type_name_from_ref(&param.type_ref));
Expand All @@ -16,17 +18,17 @@ pub(super) fn lower_inferred_array_sizes<'i>(
let constants = contract
.constants
.iter()
.map(|constant| lower_constant(constant, &mut top_level_types, contract_constants))
.map(|constant| lower_constant(constant, &mut top_level_types, contract_constants, &functions_by_name))
.collect::<Result<Vec<_>, _>>()?;
let fields = contract
.fields
.iter()
.map(|field| lower_field(field, &mut top_level_types, contract_constants))
.map(|field| lower_field(field, &mut top_level_types, contract_constants, &functions_by_name))
.collect::<Result<Vec<_>, _>>()?;
let functions = contract
.functions
.iter()
.map(|function| lower_function(function, &top_level_types, contract_constants))
.map(|function| lower_function(function, &top_level_types, contract_constants, &functions_by_name))
.collect::<Result<Vec<_>, _>>()?;

Ok(ContractAst {
Expand All @@ -42,45 +44,38 @@ pub(super) fn lower_inferred_array_sizes<'i>(
})
}

pub(super) fn infer_fixed_array_type_from_initializer_ref<'i>(
fn infer_fixed_array_type_from_initializer_ref<'i>(
declared_type: &TypeRef,
initializer: Option<&Expr<'i>>,
types: &HashMap<String, String>,
constants: &HashMap<String, Expr<'i>>,
functions: &HashMap<String, &FunctionAst<'i>>,
) -> Option<TypeRef> {
if !matches!(declared_type.array_size(), Some(ArrayDim::Inferred)) {
return None;
}

let element_type = declared_type.element_type()?;
let init = initializer?;
let init_type = infer_expr_type_ref(init, types, constants, functions, Some(&element_type))?;

match &init.kind {
ExprKind::Array(values) => {
let mut inferred = element_type.clone();
inferred.array_dims.push(ArrayDim::Fixed(values.len()));
if array_literal_matches_type_with_env_ref(values, &inferred, types, constants) { Some(inferred) } else { None }
}
ExprKind::Identifier(name) => {
let other_type = parse_type_ref(types.get(name)?).ok()?;
if !other_type.is_array() || other_type.element_type() != Some(element_type.clone()) {
return None;
}
let size = array_size_with_constants_ref(&other_type, constants)?;
let mut inferred = element_type;
inferred.array_dims.push(ArrayDim::Fixed(size));
Some(inferred)
}
_ => None,
if !init_type.is_array() || init_type.element_type() != Some(element_type.clone()) {
return None;
}

let size = array_size_with_constants_ref(&init_type, constants)?;
let mut inferred = element_type;
inferred.array_dims.push(ArrayDim::Fixed(size));
Some(inferred)
}

fn lower_constant<'i>(
constant: &ConstantAst<'i>,
types: &mut HashMap<String, String>,
constants: &HashMap<String, Expr<'i>>,
functions: &HashMap<String, &FunctionAst<'i>>,
) -> Result<ConstantAst<'i>, CompilerError> {
let type_ref = infer_type_ref(&constant.type_ref, Some(&constant.expr), types, constants)
let type_ref = infer_type_ref(&constant.type_ref, Some(&constant.expr), types, constants, functions)
.ok_or_else(|| CompilerError::Unsupported(format!("cannot infer fixed array size from constant '{}'", constant.name)))?;
types.insert(constant.name.clone(), type_name_from_ref(&type_ref));
Ok(ConstantAst { type_ref, ..constant.clone() })
Expand All @@ -90,8 +85,9 @@ fn lower_field<'i>(
field: &ContractFieldAst<'i>,
types: &mut HashMap<String, String>,
constants: &HashMap<String, Expr<'i>>,
functions: &HashMap<String, &FunctionAst<'i>>,
) -> Result<ContractFieldAst<'i>, CompilerError> {
let type_ref = infer_type_ref(&field.type_ref, Some(&field.expr), types, constants)
let type_ref = infer_type_ref(&field.type_ref, Some(&field.expr), types, constants, functions)
.ok_or_else(|| CompilerError::Unsupported(format!("cannot infer fixed array size from contract field '{}'", field.name)))?;
types.insert(field.name.clone(), type_name_from_ref(&type_ref));
Ok(ContractFieldAst { type_ref, ..field.clone() })
Expand All @@ -101,23 +97,25 @@ fn lower_function<'i>(
function: &FunctionAst<'i>,
top_level_types: &HashMap<String, String>,
constants: &HashMap<String, Expr<'i>>,
functions: &HashMap<String, &FunctionAst<'i>>,
) -> Result<FunctionAst<'i>, CompilerError> {
let mut types = top_level_types.clone();
for param in &function.params {
types.insert(param.name.clone(), type_name_from_ref(&param.type_ref));
}
let body = lower_block(&function.body, &mut types, constants)?;
let body = lower_block(&function.body, &mut types, constants, functions)?;
Ok(FunctionAst { body, ..function.clone() })
}

fn lower_block<'i>(
statements: &[Statement<'i>],
types: &mut HashMap<String, String>,
constants: &HashMap<String, Expr<'i>>,
functions: &HashMap<String, &FunctionAst<'i>>,
) -> Result<Vec<Statement<'i>>, CompilerError> {
let mut lowered = Vec::with_capacity(statements.len());
for statement in statements {
lowered.push(lower_statement(statement, types, constants)?);
lowered.push(lower_statement(statement, types, constants, functions)?);
}
Ok(lowered)
}
Expand All @@ -126,10 +124,11 @@ fn lower_statement<'i>(
statement: &Statement<'i>,
types: &mut HashMap<String, String>,
constants: &HashMap<String, Expr<'i>>,
functions: &HashMap<String, &FunctionAst<'i>>,
) -> Result<Statement<'i>, CompilerError> {
match statement {
Statement::VariableDefinition { type_ref, name, expr, .. } => {
let lowered_type = infer_type_ref(type_ref, expr.as_ref(), types, constants)
let lowered_type = infer_type_ref(type_ref, expr.as_ref(), types, constants, functions)
.ok_or_else(|| CompilerError::Unsupported(format!("cannot infer fixed array size from variable '{}'", name)))?;
types.insert(name.clone(), type_name_from_ref(&lowered_type));
Ok(match statement {
Expand All @@ -152,7 +151,7 @@ fn lower_statement<'i>(
let lowered_bindings = bindings
.iter()
.map(|binding| {
let lowered_type = infer_type_ref(&binding.type_ref, None, types, constants).ok_or_else(|| {
let lowered_type = infer_type_ref(&binding.type_ref, None, types, constants, functions).ok_or_else(|| {
CompilerError::Unsupported(format!("cannot infer fixed array size from binding '{}'", binding.name))
})?;
types.insert(binding.name.clone(), type_name_from_ref(&lowered_type));
Expand All @@ -169,15 +168,15 @@ fn lower_statement<'i>(
}
Statement::Block { body, span } => {
let mut block_types = types.clone();
let lowered_body = lower_block(body, &mut block_types, constants)?;
let lowered_body = lower_block(body, &mut block_types, constants, functions)?;
Ok(Statement::Block { body: lowered_body, span: *span })
}
Statement::If { condition, then_branch, else_branch, span, then_span, else_span } => {
let mut then_types = types.clone();
let lowered_then = lower_block(then_branch, &mut then_types, constants)?;
let lowered_then = lower_block(then_branch, &mut then_types, constants, functions)?;
let (lowered_else, merged_types) = if let Some(else_branch) = else_branch {
let mut else_types = types.clone();
let lowered_else = lower_block(else_branch, &mut else_types, constants)?;
let lowered_else = lower_block(else_branch, &mut else_types, constants, functions)?;
let mut merged = then_types;
merged.extend(else_types);
(Some(lowered_else), merged)
Expand All @@ -197,7 +196,7 @@ fn lower_statement<'i>(
Statement::For { ident, start, end, max_iterations, body, span, ident_span, body_span } => {
let mut body_types = types.clone();
body_types.insert(ident.clone(), "int".to_string());
let lowered_body = lower_block(body, &mut body_types, constants)?;
let lowered_body = lower_block(body, &mut body_types, constants, functions)?;
Ok(Statement::For {
ident: ident.clone(),
start: start.clone(),
Expand All @@ -218,14 +217,87 @@ fn infer_type_ref<'i>(
initializer: Option<&Expr<'i>>,
types: &HashMap<String, String>,
constants: &HashMap<String, Expr<'i>>,
functions: &HashMap<String, &FunctionAst<'i>>,
) -> Option<TypeRef> {
if matches!(declared_type.array_size(), Some(ArrayDim::Inferred)) {
infer_fixed_array_type_from_initializer_ref(declared_type, initializer, types, constants)
infer_fixed_array_type_from_initializer_ref(declared_type, initializer, types, constants, functions)
} else {
Some(declared_type.clone())
}
}

fn infer_expr_type_ref<'i>(
expr: &Expr<'i>,
types: &HashMap<String, String>,
constants: &HashMap<String, Expr<'i>>,
functions: &HashMap<String, &FunctionAst<'i>>,
array_literal_element_type: Option<&TypeRef>,
) -> Option<TypeRef> {
match &expr.kind {
ExprKind::Identifier(name) => parse_type_ref(types.get(name)?).ok(),
ExprKind::Array(values) => {
let mut inferred = array_literal_element_type
.cloned()
.or_else(|| infer_array_literal_element_type(values, types, constants, functions))?;
inferred.array_dims.push(ArrayDim::Fixed(values.len()));
Some(inferred)
}
ExprKind::Call { name, .. } => {
if let Some(function) = functions.get(name) {
if function.entrypoint || function.return_types.len() != 1 {
return None;
}
return Some(function.return_types[0].clone());
}
parse_type_ref(name).ok()
}
ExprKind::Binary { op: BinaryOp::Add, left, right } => {
let left_type = infer_expr_type_ref(left, types, constants, functions, None)?;
let right_type = infer_expr_type_ref(right, types, constants, functions, None)?;
let left_element = left_type.element_type()?;
if right_type.element_type() != Some(left_element.clone()) {
return None;
}
let left_size = array_size_with_constants_ref(&left_type, constants)?;
let right_size = array_size_with_constants_ref(&right_type, constants)?;
let mut inferred = left_element;
inferred.array_dims.push(ArrayDim::Fixed(left_size.checked_add(right_size)?));
Some(inferred)
}
ExprKind::IfElse { then_expr, else_expr, .. } => {
let then_type = infer_expr_type_ref(then_expr, types, constants, functions, None)?;
let else_type = infer_expr_type_ref(else_expr, types, constants, functions, None)?;
(then_type == else_type).then_some(then_type)
}
ExprKind::Append { source, args, .. } => {
let source_type = infer_expr_type_ref(source, types, constants, functions, None)?;
let element_type = source_type.element_type()?;
let source_size = array_size_with_constants_ref(&source_type, constants)?;
let mut inferred = element_type;
inferred.array_dims.push(ArrayDim::Fixed(source_size.checked_add(args.len())?));
Some(inferred)
}
ExprKind::UnarySuffix { source, kind: UnarySuffixKind::Reverse, .. } => {
infer_expr_type_ref(source, types, constants, functions, None)
}
_ => None,
}
}

fn infer_array_literal_element_type<'i>(
values: &[Expr<'i>],
types: &HashMap<String, String>,
constants: &HashMap<String, Expr<'i>>,
functions: &HashMap<String, &FunctionAst<'i>>,
) -> Option<TypeRef> {
let first_type = infer_expr_type_ref(values.first()?, types, constants, functions, None)?;
if values.iter().skip(1).all(|value| infer_expr_type_ref(value, types, constants, functions, None).as_ref() == Some(&first_type)) {
Some(first_type)
} else {
None
}
}

fn array_size_with_constants_ref<'i>(type_ref: &TypeRef, constants: &HashMap<String, Expr<'i>>) -> Option<usize> {
match type_ref.array_size()? {
ArrayDim::Fixed(size) => Some(*size),
Expand Down
4 changes: 1 addition & 3 deletions silverscript-lang/src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub use compile::{compile_debug_expr, function_branch_index};
pub(crate) use debug_recording::DebugRecorder;
use r#for::lower_for_loops;
pub(crate) use static_check::expr_matches_declared_type_ref;
use static_check::{static_check_contract, value_matches_type_ref};
use static_check::value_matches_type_ref;
pub use structs::flattened_struct_name;
pub(super) use structs::{
StructFieldSpec, StructRegistry, build_struct_registry, ensure_known_or_builtin_type, flatten_constructor_args_env,
Expand Down Expand Up @@ -106,7 +106,6 @@ pub fn compile_contract<'i>(
options: CompileOptions,
) -> Result<CompiledContract<'i>, CompilerError> {
let contract = parse_contract_ast(source)?;
static_check_contract(&contract, constructor_args, options)?;
compile_contract_impl(&contract, constructor_args, options, Some(source))
}

Expand All @@ -115,7 +114,6 @@ pub fn compile_contract_ast<'i>(
constructor_args: &[Expr<'i>],
options: CompileOptions,
) -> Result<CompiledContract<'i>, CompilerError> {
static_check_contract(contract, constructor_args, options)?;
compile_contract_impl(contract, constructor_args, options, None)
}

Expand Down
Loading