From 95c00439436ac3d9b16bbc8f69409b947062c611 Mon Sep 17 00:00:00 2001 From: ascandone Date: Tue, 23 Jun 2026 16:56:44 +0200 Subject: [PATCH 1/2] chore: removed todo comment --- builder/builder.go | 1 - 1 file changed, 1 deletion(-) diff --git a/builder/builder.go b/builder/builder.go index d4cc68d2..fa545dfc 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -135,7 +135,6 @@ func renderVars( return st.sb.String() } -// TODO double check this one (do we need to handle vars?) func BuildProgram(statements ...Statement) (map[string]string, VarsEnv, string) { env := newEnv() for _, stmt := range statements { From 08b4640f9f9412e7eba63376fb00d5a7658619ab Mon Sep 17 00:00:00 2001 From: ascandone Date: Tue, 23 Jun 2026 17:07:31 +0200 Subject: [PATCH 2/2] feat: add flags --- builder/builder.go | 51 +++++++++++++++++++++++++++++++++++++++++ builder/builder_test.go | 31 +++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/builder/builder.go b/builder/builder.go index fa545dfc..e9538714 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -149,3 +149,54 @@ func BuildProgram(statements ...Statement) (map[string]string, VarsEnv, string) return st.knownBindings, env.varsEnv, vars + env.builder.String() } + +// Check feature flag has only lower chars and "-" chars. +// Less declarative than a regex, but this way we don't need a more complex api user-wise +// just for the sake of perfs (this should be good enough) +func checkIsFlagValid(s string) bool { + if len(s) == 0 { + return false + } + + for i := 0; i < len(s); i++ { + ch := s[i] + if !(ch >= 'a' && ch <= 'z') && ch != '-' { + return false + } + } + return true +} + +// Same as `BuildProgram`, but accepts features flag. +// +// IMPORTANT NOTE: this function will panic if a feature flag doesn't match `^[a-z-]+$`. Flags are meant to be passed directly, and a sintactically +// incorrect flag is treated as an argument error panic right away, not returned as an "error". +// Also note we don't keep the list of valid flags here +func BuildProgramWithFeatureFlags( + featureFlags []string, + statements ...Statement, +) (map[string]string, VarsEnv, string) { + knownBindings, varsEnv, script := BuildProgram(statements...) + + var flagsArgs strings.Builder + for index, flag := range featureFlags { + if !checkIsFlagValid(flag) { + // Yes, we are panicking instead of returning an error here. + // That's desidered: flags are meant to be passed manually. Not computed, created conditionally, etc. + // If a feature flag is wrong we want to crash the thing immediately instead of having the user handle that, log that, or whatever. + panic(fmt.Sprintf("Invalid argument: the `%s` feature flag is invalid. Only flags matching `^[a-z-]+$` are accepted.", flag)) + } + + if index != 0 { + flagsArgs.WriteString(", ") + } + + flagsArgs.WriteByte('"') + flagsArgs.WriteString(flag) + flagsArgs.WriteByte('"') + + } + + updatedScript := fmt.Sprintf("#![feature(%s)]\n%s", flagsArgs.String(), script) + return knownBindings, varsEnv, updatedScript +} diff --git a/builder/builder_test.go b/builder/builder_test.go index 3394d214..ef0b829a 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -36,6 +36,37 @@ send [$asset_0 42] ( )`)) } +func TestSimpleSendWithFlag(t *testing.T) { + stmt := builder.StmtSend( + builder.ExprMonetary( + builder.ExprAsset("USD/2"), + builder.ExprNumberBigInt(big.NewInt(42)), + ), + builder.SrcAccount( + builder.ExprAccount("src"), + ), + builder.DestAccount( + builder.ExprAccount("dest"), + ), + ) + + _, _, script := builder.BuildProgramWithFeatureFlags([]string{ + "my-flag", + "another-flag", + }, stmt) + snaps.MatchInlineSnapshot(t, script, snaps.Inline(`#![feature("my-flag", "another-flag")] +vars { + account $account_0 + account $account_1 + asset $asset_0 +} + +send [$asset_0 42] ( + source = $account_0 + destination = $account_1 +)`)) +} + func TestSendAll(t *testing.T) { stmt := builder.StmtSendAll( builder.ExprAsset("COIN"),