Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: format specifiers #1373

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
add Printf implementation
  • Loading branch information
TomasArrachea committed Dec 26, 2024
commit 232c6662f917e7a39f1c1fd0274ebd78b4141e09
4 changes: 4 additions & 0 deletions frontend/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ type API interface {
// whose value will be resolved at runtime when computed by the solver
Println(a ...Variable)

// Println behaves like fmt.Printf but accepts cd.Variable as parameter
// whose value will be resolved at runtime when computed by the solver
Printf(format string, args ...Variable)

// Compiler returns the compiler object for advanced circuit development
Compiler() Compiler

Expand Down
62 changes: 62 additions & 0 deletions frontend/cs/r1cs/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,68 @@ func (builder *builder) Println(a ...frontend.Variable) {
builder.cs.AddLog(log)
}

func (builder *builder) Printf(format string, args ...frontend.Variable) {
var log constraint.LogEntry

// prefix log line with file.go:line
if _, file, line, ok := runtime.Caller(1); ok {
log.Caller = fmt.Sprintf("%s:%d", filepath.Base(file), line)
}

var sbb strings.Builder
formatIndex := 0

// Parse the format string and match placeholders with args
for _, arg := range args {
// Search for the next format specifier
nextPercent := strings.Index(format[formatIndex:], "%")
if nextPercent == -1 {
// No more placeholders; add remaining format string and break
sbb.WriteString(format[formatIndex:])
break
}

// Add the part of the format string before the next %
sbb.WriteString(format[formatIndex : formatIndex+nextPercent])
formatIndex += nextPercent + 1 // Move past %

// Handle format specifier
if formatIndex < len(format) {
specifier := format[formatIndex]
formatIndex++ // Move past the specifier

switch specifier {
case 's', 'd', 'f', 'x': // Supported format specifiers
if v, ok := arg.(expr.LinearExpression); ok {
assertIsSet(v)
sbb.WriteString("%" + string(specifier))
log.ToResolve = append(log.ToResolve, builder.getLinearExpression(v))
} else {
builder.printArg(&log, &sbb, arg)
}
default:
// Unsupported specifier; add it directly
sbb.WriteByte('%')
sbb.WriteByte(specifier)
}
} else {
// Malformed format string (ends with %)
sbb.WriteByte('%')
}
}

// Add remaining format string after the last placeholder
if formatIndex < len(format) {
sbb.WriteString(format[formatIndex:])
}

// Set the format string for the log entry
log.Format = sbb.String()

// Add the log entry to the circuit's constraint system
builder.cs.AddLog(log)
}

func (builder *builder) printArg(log *constraint.LogEntry, sbb *strings.Builder, a frontend.Variable) {

leafCount, err := schema.Walk(a, tVariable, nil)
Expand Down
31 changes: 31 additions & 0 deletions frontend/cs/scs/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,37 @@ func (builder *builder) Println(a ...frontend.Variable) {
builder.cs.AddLog(log)
}

func (builder *builder) Printf(format string, args ...frontend.Variable) {
var log constraint.LogEntry

// prefix log line with file.go:line
if _, file, line, ok := runtime.Caller(1); ok {
log.Caller = fmt.Sprintf("%s:%d", filepath.Base(file), line)
}

var sbb strings.Builder

for i, arg := range args {
if i > 0 {
sbb.WriteByte(' ')
}
if v, ok := arg.(expr.Term); ok {

sbb.WriteString("%s")
// we set limits to the linear expression, so that the log printer
// can evaluate it before printing it
log.ToResolve = append(log.ToResolve, constraint.LinearExpression{builder.cs.MakeTerm(v.Coeff, v.VID)})
} else {
builder.printArg(&log, &sbb, arg)
}
}

// set format string to be used with fmt.Sprintf, once the variables are solved in the R1CS.Solve() method
log.Format = sbb.String()

builder.cs.AddLog(log)
}

func (builder *builder) printArg(log *constraint.LogEntry, sbb *strings.Builder, a frontend.Variable) {

leafCount, err := schema.Walk(a, tVariable, nil)
Expand Down
19 changes: 19 additions & 0 deletions test/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,25 @@ func (e *engine) print(sbb *strings.Builder, x interface{}) {
}
}

func (e *engine) Printf(format string, args ...frontend.Variable) {
var sbb strings.Builder
sbb.WriteString("(test.engine) ")

// prefix log line with file.go:line
if _, file, line, ok := runtime.Caller(1); ok {
sbb.WriteString(filepath.Base(file))
sbb.WriteByte(':')
sbb.WriteString(strconv.Itoa(line))
sbb.WriteByte(' ')
}

for i := 0; i < len(args); i++ {
e.print(&sbb, args[i])
sbb.WriteByte(' ')
}
fmt.Println(sbb.String())
}

func (e *engine) NewHint(f solver.Hint, nbOutputs int, inputs ...frontend.Variable) ([]frontend.Variable, error) {

if nbOutputs <= 0 {
Expand Down