Skip to content

Commit 12be327

Browse files
committed
faet: principals support
1 parent 1999cb1 commit 12be327

File tree

9 files changed

+142
-23
lines changed

9 files changed

+142
-23
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"rust-analyzer.check.command": "clippy",
2+
// "rust-analyzer.check.command": "clippy",
33
// rust-analyzer hack:
44
// manually set --workspace in extraArgs so that it's always set only one time
55
"rust-analyzer.check.workspace": false,

Cargo.lock

Lines changed: 1 addition & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/clarinet-cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ clarinet-format = { path = "../clarinet-format" }
3333
clarinet-deployments = { path = "../clarinet-deployments" }
3434
hiro-system-kit = { path = "../hiro-system-kit" }
3535
stacks-network = { path = "../stacks-network" }
36+
ts-to-clar = { path = "../ts-to-clar" }
3637

3738
[target.'cfg(unix)'.dependencies]
3839
nix = "0.29.0"

components/clarinet-cli/src/frontend/cli.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ enum Command {
9191
/// Subcommands for Devnet usage
9292
#[clap(subcommand, name = "devnet")]
9393
Devnet(Devnet),
94+
/// Subcommands ts-to-clar transpiler usage
95+
#[clap(name = "build", aliases = &["transpile"], bin_name = "build")]
96+
Build(Build),
9497
/// Get Clarity autocompletion and inline errors from your code editor (VSCode, vim, emacs, etc)
9598
#[clap(name = "lsp", bin_name = "lsp")]
9699
LSP,
@@ -102,6 +105,12 @@ enum Command {
102105
DAP,
103106
}
104107

108+
#[derive(Parser, PartialEq, Clone, Debug)]
109+
struct Build {
110+
#[clap(long = "path", short = 'p')]
111+
pub path: Option<String>,
112+
}
113+
105114
#[derive(Parser, PartialEq, Clone, Debug)]
106115
struct Formatter {
107116
#[clap(long = "manifest-path", short = 'm')]
@@ -1367,6 +1376,24 @@ pub fn main() {
13671376
}
13681377
Devnet::DevnetStart(cmd) => devnet_start(cmd, clarinetrc),
13691378
},
1379+
Command::Build(Build { path }) => {
1380+
let file_name = path.unwrap_or_else(|| {
1381+
eprintln!("No file path provided. Please specify a .clar.ts file.");
1382+
process::exit(1);
1383+
});
1384+
let file_path = std::path::Path::new(&file_name);
1385+
let extension = file_path.extension().unwrap_or_default();
1386+
assert_eq!(extension, "ts");
1387+
assert!(file_path.is_file());
1388+
assert!(file_name.ends_with(".clar.ts"));
1389+
1390+
let output_path = file_name.strip_suffix(".ts").unwrap();
1391+
1392+
let src = std::fs::read_to_string(file_path).unwrap();
1393+
let clarity_code = ts_to_clar::transpile(&file_name, &src).unwrap();
1394+
std::fs::write(output_path, clarity_code).unwrap();
1395+
println!("Transpiled {file_name} to {output_path}");
1396+
}
13701397
};
13711398
}
13721399

components/ts-to-clar/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ license = "GPL-3.0"
99
anyhow = "1.0.98"
1010

1111
oxc_allocator = "0.75"
12-
oxc_ast = { version = "0.75", features = ["serialize"] }
12+
oxc_ast = { version = "0.75" }
1313
oxc_ast_visit = "0.75"
1414
oxc_parser = "0.75"
1515
oxc_span = "0.75"
1616
oxc_syntax = "0.75"
17-
oxc_traverse = "0.75"
17+
oxc_traverse = { version = "0.75" }
1818
oxc_semantic = "0.75"
1919

2020
clarity = { workspace = true }

components/ts-to-clar/src/converter.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::vec;
44

55
use clarity::vm::{
66
representations::{PreSymbolicExpression, PreSymbolicExpressionType, Span},
7-
types::{SequenceSubtype, StringSubtype, TypeSignature as ClarityTypeSignature},
7+
types::{PrincipalData, SequenceSubtype, StringSubtype, TypeSignature as ClarityTypeSignature},
88
ClarityName, Value as ClarityValue,
99
};
1010
use oxc_allocator::Allocator;
@@ -23,6 +23,9 @@ fn type_signature_to_pse(
2323
ClarityTypeSignature::UIntType => PreSymbolicExpression::atom(ClarityName::from("uint")),
2424
ClarityTypeSignature::IntType => PreSymbolicExpression::atom(ClarityName::from("int")),
2525
ClarityTypeSignature::BoolType => PreSymbolicExpression::atom(ClarityName::from("bool")),
26+
ClarityTypeSignature::PrincipalType => {
27+
PreSymbolicExpression::atom(ClarityName::from("principal"))
28+
}
2629
ClarityTypeSignature::SequenceType(seq_subtype) => match seq_subtype {
2730
SequenceSubtype::StringType(string_subtype) => match string_subtype {
2831
StringSubtype::ASCII(len) => PreSymbolicExpression::list(vec![
@@ -80,6 +83,22 @@ fn convert_expression_with_type(
8083
}
8184
_ => return Err(anyhow::anyhow!("Invalid expression for Bool")),
8285
},
86+
ClarityTypeSignature::PrincipalType => match &expr {
87+
OxcExpression::StringLiteral(str) => PreSymbolicExpression::atom_value(
88+
ClarityValue::Principal(PrincipalData::parse(str.value.as_str())?),
89+
),
90+
OxcExpression::Identifier(ident) => {
91+
if ident.name == "txSender" {
92+
PreSymbolicExpression::atom(ClarityName::from("tx-sender"))
93+
} else {
94+
return Err(anyhow::anyhow!(
95+
"Invalid identifier for Principal: {}",
96+
ident.name
97+
));
98+
}
99+
}
100+
_ => return Err(anyhow::anyhow!("Invalid expression for Principal")),
101+
},
83102
ClarityTypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII(
84103
_len,
85104
))) => match &expr {
@@ -124,7 +143,7 @@ fn convert_expression_with_type(
124143

125144
_ => return Err(anyhow::anyhow!("Unsupported expression for Tuple")),
126145
},
127-
_ => return Err(anyhow::anyhow!("Unsupported type for constant")),
146+
_ => return Err(anyhow::anyhow!("Unsupported type for variable")),
128147
})
129148
}
130149

@@ -167,7 +186,7 @@ fn convert_function(
167186
.parameters
168187
.iter()
169188
.map(|(name, r#type)| {
170-
let name = PreSymbolicExpression::atom(ClarityName::from(name.as_str()));
189+
let name = PreSymbolicExpression::atom(ClarityName::from(to_kebab_case(name).as_str()));
171190
let r#type = type_signature_to_pse(r#type)?;
172191
Ok(vec![name, r#type])
173192
})
@@ -331,6 +350,19 @@ mod test {
331350
// The following tests use the assert_pses_eq function to dynamically
332351
// build the expected PSEs from the Clarity source code.
333352

353+
#[test]
354+
fn test_principal_data_var() {
355+
let ts_src =
356+
"const owner = new DataVar<Principal>(\"ST3PF13W7Z0RRM42A8VZRVFQ75SV1K26RXEP8YGKJ\");";
357+
assert_pses_eq(
358+
ts_src,
359+
r#"(define-data-var owner principal 'ST3PF13W7Z0RRM42A8VZRVFQ75SV1K26RXEP8YGKJ)"#,
360+
);
361+
362+
let ts_src = "const owner = new DataVar<Principal>(txSender);";
363+
assert_pses_eq(ts_src, r#"(define-data-var owner principal tx-sender)"#);
364+
}
365+
334366
#[test]
335367
fn test_convert_tuple_type() {
336368
let ts_src = "const state = new DataVar<{ ok: Int }>({ ok: 1 });";
@@ -416,4 +448,19 @@ mod test {
416448
};
417449
assert_pses_eq(ts_src, r#"(define-public (myfunc) (ok true))"#);
418450
}
451+
452+
#[test]
453+
fn test_function_arg_casing() {
454+
let ts_src = indoc! {
455+
r#"const addr = new DataVar<Principal>(txSender);
456+
function updateAddr(newAddr: Principal) { return ok(addr.set(newAddr)); }"#
457+
};
458+
assert_pses_eq(
459+
ts_src,
460+
indoc! {
461+
r#"(define-data-var addr principal tx-sender)
462+
(define-private (update-addr (new-addr principal)) (ok (var-set addr new-addr)))"#
463+
},
464+
);
465+
}
419466
}

components/ts-to-clar/src/expression_converter.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ fn atom(name: &str) -> PreSymbolicExpression {
3333

3434
fn get_clarity_binary_operator(operator: &ast::BinaryOperator) -> &str {
3535
use ast::BinaryOperator::*;
36+
3637
match operator {
3738
Addition => "+",
3839
Subtraction => "-",
@@ -61,14 +62,20 @@ fn get_clarity_binary_operator(operator: &ast::BinaryOperator) -> &str {
6162

6263
impl<'a> StatementConverter<'a> {
6364
fn new(ir: &'a IR, function: &'a IRFunction<'a>) -> Self {
65+
let current_bindings = function
66+
.parameters
67+
.iter()
68+
.map(|(name, r#type)| (name.to_string(), Some(r#type.clone())))
69+
.collect();
70+
6471
Self {
6572
ir,
6673
function,
6774
expressions: Vec::new(),
6875
lists_stack: vec![],
6976
current_context_type: None,
7077
current_context_type_stack: vec![],
71-
current_bindings: vec![],
78+
current_bindings,
7279
}
7380
}
7481

@@ -384,13 +391,16 @@ impl<'a> Traverse<'a, ConverterState<'a>> for StatementConverter<'a> {
384391
}
385392

386393
let ident_name = ident.name.as_str();
394+
395+
// function call
387396
let matching_function = self.ir.functions.iter().any(|f| f.name == ident_name);
388397
if matching_function {
389398
self.lists_stack
390399
.push(atom(to_kebab_case(ident_name).as_str()));
391400
return;
392401
}
393402

403+
// data-var reference
394404
let matching_data_var = self
395405
.current_bindings
396406
.iter()
@@ -401,6 +411,9 @@ impl<'a> Traverse<'a, ConverterState<'a>> for StatementConverter<'a> {
401411
return;
402412
}
403413

414+
//
415+
416+
// imports
404417
if let Some((_, name)) = self
405418
.ir
406419
.std_specific_imports
@@ -851,4 +864,14 @@ mod test {
851864
let expected_clar_src = "(let ((my-count1 1) (my-count2 2)) true)";
852865
assert_last_function_body_eq(ts_src, expected_clar_src);
853866
}
867+
868+
#[test]
869+
fn test_function_arg_casing() {
870+
let ts_src = indoc!(
871+
r#"const addr = new DataVar<Principal>(txSender);
872+
function updateAddr(newAddr: Principal) { return ok(addr.set(newAddr)); }"#
873+
);
874+
let expected_clar_src = "(ok (var-set addr new-addr))";
875+
assert_last_function_body_eq(ts_src, expected_clar_src);
876+
}
854877
}

components/ts-to-clar/src/parser.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,11 @@ mod test {
372372
get_ir(allocator, "tmp.clar.ts", src)
373373
}
374374

375+
fn expr_identifier<'a>(allocator: &'a Allocator, name: &'a str) -> Expression<'a> {
376+
AstBuilder::new(allocator)
377+
.expression_identifier(Span::empty(0), Atom::from_in(name.to_string(), allocator))
378+
}
379+
375380
fn expr_number<'a>(allocator: &'a Allocator, value: f64) -> Expression<'a> {
376381
AstBuilder::new(allocator).expression_numeric_literal(
377382
Span::empty(0),
@@ -425,6 +430,9 @@ mod test {
425430
fn assert_expr_eq(actual: &Expression, expected: &Expression) {
426431
use Expression::*;
427432
match (&actual, &expected) {
433+
(Identifier(actual_id), Identifier(expected_id)) => {
434+
assert_eq!(actual_id.name, expected_id.name);
435+
}
428436
(NumericLiteral(actual_num), NumericLiteral(expected_num)) => {
429437
assert_eq!(actual_num.value, expected_num.value);
430438
}
@@ -579,6 +587,33 @@ mod test {
579587
assert_data_var_eq(ir, &expected);
580588
}
581589

590+
#[test]
591+
fn test_data_var_principal_ir() {
592+
let src =
593+
"const owner = new DataVar<Principal>(\"ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0\");";
594+
let allocator = Allocator::default();
595+
let ir = &get_tmp_ir(&allocator, src).data_vars[0];
596+
let expected = IRDataVar {
597+
name: "owner".to_string(),
598+
r#type: PrincipalType,
599+
expr: expr_string(&allocator, "ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0"),
600+
};
601+
assert_data_var_eq(ir, &expected);
602+
}
603+
604+
#[test]
605+
fn test_data_var_tx_sender() {
606+
let src = "const owner = new DataVar<Principal>(txSender);";
607+
let allocator = Allocator::default();
608+
let ir = &get_tmp_ir(&allocator, src).data_vars[0];
609+
let expected = IRDataVar {
610+
name: "owner".to_string(),
611+
r#type: PrincipalType,
612+
expr: expr_identifier(&allocator, "txSender"),
613+
};
614+
assert_data_var_eq(ir, &expected);
615+
}
616+
582617
#[test]
583618
fn test_data_map_ir() {
584619
let src = "const statuses = new DataMap<Uint, Bool>();";

components/ts-to-clar/src/types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ fn extract_type(
4040
"Uint" => Ok(TypeSignature::UIntType),
4141
"Int" => Ok(TypeSignature::IntType),
4242
"Bool" => Ok(TypeSignature::BoolType),
43+
"Principal" => Ok(TypeSignature::PrincipalType),
4344
"StringAscii" => extract_numeric_type_param(type_params).map(get_ascii_type),
4445
"StringUtf8" => extract_numeric_type_param(type_params).map(get_utf8_type),
4546
_ => Err(anyhow!("Unknown type: {}", type_ident)),

0 commit comments

Comments
 (0)