Skip to content

Instantly share code, notes, and snippets.

@yetanotherchris
Last active June 15, 2025 18:48
Show Gist options
  • Save yetanotherchris/c80d0fadb5a2ee5b4beb0a4384020dbf to your computer and use it in GitHub Desktop.
Save yetanotherchris/c80d0fadb5a2ee5b4beb0a4384020dbf to your computer and use it in GitHub Desktop.
Claude's response for improvements to a C# implementation of Go's text templates - https://github.com/yetanotherchris/text-template

What are some ways to increase the performance of Antlr

Here are several effective ways to improve ANTLR performance:

Parser Optimization

Use the SLL prediction mode first - Set your parser to use PredictionMode.SLL initially, which is faster than the default LL mode. Fall back to LL mode only when SLL fails:

parser.getInterpreter().setPredictionMode(PredictionMode.SLL);

Eliminate left recursion - Direct and indirect left recursion can significantly slow parsing. Restructure your grammar rules to use right recursion or iterative patterns instead.

Minimize backtracking - Reduce ambiguous grammar constructs that force the parser to backtrack. Use semantic predicates {...}? to guide parsing decisions when needed.

Grammar Design

Optimize rule ordering - Place more common alternatives first in your grammar rules, as ANTLR tries them in order.

Use lexer modes - For complex tokenization scenarios, lexer modes can be more efficient than complex lexer rules with many alternatives.

Simplify complex rules - Break down overly complex rules into smaller, more focused ones. This can improve both performance and maintainability.

Memory and Caching

Reuse parser instances - Creating new parser instances is expensive. Reuse them when possible, just reset the input stream.

Tune memory settings - Increase JVM heap size if you're processing large files. ANTLR can be memory-intensive for complex grammars.

Use parse tree caching - If parsing the same input multiple times, consider caching parse trees.

Lexer Optimization

Order lexer rules carefully - Put more specific tokens before general ones, and commonly used tokens first.

Avoid expensive lexer predicates - Semantic predicates in the lexer can be costly since they're evaluated frequently.

Advanced Techniques

Use visitor pattern over listener - Visitors are generally faster than listeners for tree traversal since they don't use the call stack as heavily.

Profile your grammar - Use ANTLR's profiling tools to identify bottlenecks in your specific grammar.

Consider two-pass parsing - For very large files, sometimes a lightweight first pass to identify structure followed by detailed parsing can be more efficient.

The most impactful optimizations are usually grammar restructuring to reduce ambiguity and using SLL prediction mode, so start with those approaches.

Go Text/Template Parser Implementation with ANTLR

Implement a basic Go text/template parser using ANTLR that supports core template functionality. The parser should handle variable interpolation, conditional statements, and loops while maintaining compatibility with Go's template syntax.

Core Requirements

1. Variable Interpolation

  • Support {{ .Field }} syntax for accessing struct fields
  • Support nested field access: {{ .User.Name }}
  • Support array/slice indexing: {{ .Items[0] }}
  • Support map access: {{ .Data.key }}
  • Handle whitespace control with {{- }} and {{ -}}

2. Conditional Statements

  • Support {{ if condition }}...{{ end }} blocks
  • Support {{ if condition }}...{{ else }}...{{ end }}
  • Support {{ if condition }}...{{ else if condition2 }}...{{ end }}
  • Conditions should support:
    • Boolean fields: {{ if .IsActive }}
    • Comparisons: {{ if eq .Status "active" }}
    • Existence checks: {{ if .User }}

3. Loop Statements

  • Support {{ range .Items }}...{{ end }} for iterating over slices/arrays
  • Support {{ range $index, $value := .Items }}...{{ end }} with variables
  • Support {{ range .Map }}...{{ end }} for iterating over maps
  • Support {{ range $key, $value := .Map }}...{{ end }} with key-value variables
  • Handle empty collections with {{ range .Items }}...{{ else }}...{{ end }}

4. Built-in Functions (Basic Set)

  • eq: equality comparison {{ if eq .Status "active" }}
  • ne: not equal {{ if ne .Count 0 }}
  • lt, le, gt, ge: numeric comparisons
  • and, or: logical operations {{ if and .IsActive .IsValid }}
  • not: logical negation {{ if not .IsHidden }}

5. Comments

  • Support {{/* comment */}} syntax
  • Comments should be completely ignored in output

Grammar Structure (ANTLR4)

Your ANTLR grammar should define:

  • Template root with mixed text and actions
  • Action blocks ({{ ... }})
  • Field access with dot notation
  • Conditional blocks with proper nesting
  • Range blocks with optional variable assignments
  • Function calls with arguments
  • String and numeric literals
  • Whitespace control tokens

Edge Cases to Handle

Variable Access Edge Cases

// Nested field access
{{ .User.Profile.Avatar.URL }}

// Array indexing with variables
{{ .Items[.CurrentIndex] }}

// Map access with string keys containing special characters
{{ .Data["key-with-dashes"] }}

// Accessing fields that might not exist (should not panic)
{{ .MaybeNil.Field }}

Conditional Edge Cases

// Nested conditions
{{ if .User }}
  {{ if .User.IsActive }}
    Active user: {{ .User.Name }}
  {{ else }}
    Inactive user
  {{ end }}
{{ end }}

// Complex boolean expressions
{{ if and (eq .Status "active") (gt .Count 0) }}
  Status is active and count is positive
{{ end }}

// Zero value checks
{{ if .Count }}Count: {{ .Count }}{{ else }}No items{{ end }}

Loop Edge Cases

// Empty slice handling
{{ range .Items }}
  Item: {{ . }}
{{ else }}
  No items found
{{ end }}

// Nested loops
{{ range .Categories }}
  Category: {{ .Name }}
  {{ range .Items }}
    - {{ .Title }}
  {{ end }}
{{ end }}

// Range with index over empty slice
{{ range $i, $item := .EmptySlice }}
  Never executed
{{ else }}
  Empty slice message
{{ end }}

Whitespace Control Edge Cases

// Trimming leading whitespace
  {{- .Field }}

// Trimming trailing whitespace
{{ .Field -}}  

// Trimming both
  {{- .Field -}}  

// In loops and conditions
{{ range .Items -}}
{{- .Name }}
{{- end }}

Test Cases for Implementation

Basic Variable Tests

// Test data
data := struct {
    Name   string
    Count  int
    Active bool
    Items  []string
    User   *User
}{
    Name:   "Test",
    Count:  42,
    Active: true,
    Items:  []string{"a", "b", "c"},
    User:   &User{Name: "John", Age: 30},
}

// Test cases
"Hello {{ .Name }}""Hello Test"
"Count: {{ .Count }}""Count: 42"
"User: {{ .User.Name }}""User: John"
"First: {{ .Items[0] }}""First: a"

Conditional Tests

"{{ if .Active }}Active{{ else }}Inactive{{ end }}""Active"
"{{ if .Count }}Has items{{ end }}""Has items"
"{{ if eq .Name \"Test\" }}Match{{ end }}""Match"
"{{ if and .Active (gt .Count 10) }}Both true{{ end }}""Both true"

Loop Tests

"{{ range .Items }}{{ . }} {{ end }}""a b c "
"{{ range $i, $v := .Items }}{{ $i }}:{{ $v }} {{ end }}""0:a 1:b 2:c "
"{{ range .EmptySlice }}Never{{ else }}Empty{{ end }}""Empty"

Error Handling Tests

// Should not panic, should render empty or default
"{{ .NonExistent }}"""
"{{ .User.NonExistent }}"""
"{{ .NilPointer.Field }}"""

Implementation Guidelines

  1. Error Handling: The parser should gracefully handle missing fields and nil pointers without panicking
  2. Type Safety: Implement proper type checking for comparisons and operations
  3. Performance: Cache field lookups and compiled templates where possible
  4. Memory Safety: Avoid infinite recursion in nested templates
  5. Whitespace: Preserve original whitespace unless explicitly trimmed with {{- -}}

Output Format

Provide:

  1. ANTLR4 grammar file (.g4)
  2. Go code for the template engine
  3. Unit tests covering all edge cases mentioned above
  4. Example usage showing how to parse and execute templates
  5. Error handling examples

The implementation should be production-ready for basic use cases while remaining simple enough to understand and extend.

Go Text Template Directives - Implementation Specification for C#

Overview

This specification defines the behavior of define, template, and block directives from Go's text/template package for implementation in C#.

Core Concepts

Template Registry

  • Maintain a dictionary/map of template names to compiled template objects
  • Templates can be defined at parse time or runtime
  • Template lookup is case-sensitive
  • Nested template definitions are allowed

Context Management

  • Each template execution has a data context (equivalent to Go's "dot" .)
  • Context can be passed explicitly to template invocations
  • Default context is the current execution context

1. define Directive

Syntax

{{define "templateName"}}content{{end}}

Behavior

  • Registers a named template in the template registry
  • Does NOT execute the template immediately
  • Template content is parsed and compiled for later use
  • Can be defined anywhere in the template (top-level or nested)
  • Multiple definitions with the same name: last definition wins

Implementation Requirements

// Pseudo-interface for implementation
interface ITemplateDirective
{
    void ParseDefine(string templateName, string templateContent);
    void RegisterTemplate(string name, CompiledTemplate template);
}

Example Processing

Input: {{define "header"}}<h1>{{.Title}}</h1>{{end}}

  • Extract name: "header"
  • Extract content: <h1>{{.Title}}</h1>
  • Compile content as separate template
  • Register in template registry with key "header"

2. template Directive

Syntax

{{template "templateName"}}
{{template "templateName" pipeline}}

Behavior

  • Executes a previously defined template by name
  • If no pipeline provided, uses current context (.)
  • If pipeline provided, evaluates pipeline and uses result as context
  • Throws error if template name not found
  • Output is inserted at the current position

Implementation Requirements

// Pseudo-interface for implementation
interface ITemplateExecution
{
    string ExecuteTemplate(string templateName, object context = null);
    bool TemplateExists(string templateName);
}

Context Handling

  • {{template "name"}} → Execute with current context
  • {{template "name" .User}} → Execute with .User as context
  • {{template "name" "literal"}} → Execute with string literal as context
  • {{template "name" pipeline}} → Execute with pipeline result as context

Error Conditions

  • Template not found → Runtime error
  • Template execution error → Propagate error up

3. block Directive

Syntax

{{block "templateName" pipeline}}default content{{end}}

Behavior

  • Combination of define + template
  • Defines a template with the given name and content
  • Immediately executes the template with the provided pipeline
  • If template with same name already exists, uses existing template instead of default content
  • Enables template inheritance/override pattern

Implementation Requirements

// Pseudo-interface for implementation
interface IBlockDirective
{
    string ProcessBlock(string templateName, object context, string defaultContent);
}

Processing Logic

// Pseudo-code for block processing
string ProcessBlock(string name, object context, string defaultContent)
{
    if (TemplateRegistry.ContainsKey(name))
    {
        // Use existing template (override case)
        return ExecuteTemplate(name, context);
    }
    else
    {
        // Define and execute default template
        var compiledDefault = CompileTemplate(defaultContent);
        TemplateRegistry[name] = compiledDefault;
        return ExecuteTemplate(name, context);
    }
}

Implementation Details

Template Compilation

  • Templates should be compiled to an intermediate representation
  • Support nested template calls
  • Handle variable scoping correctly
  • Preserve line numbers for error reporting

Error Handling

public class TemplateError : Exception
{
    public string TemplateName { get; set; }
    public int LineNumber { get; set; }
    public string DirectiveType { get; set; } // "template", "define", "block"
}

Thread Safety

  • Template registry should be thread-safe for concurrent access
  • Template execution should be thread-safe
  • Consider using ConcurrentDictionary for template storage

Memory Management

  • Compiled templates should be cached efficiently
  • Consider template cleanup/eviction strategies for long-running applications

Usage Patterns

Basic Template Definition and Usage

// Go template equivalent
{{define "greeting"}}Hello, {{.Name}}!{{end}}
{{template "greeting" .User}}

Template Inheritance with Blocks

// Base template
{{block "title" .}}Default Title{{end}}
{{block "content" .}}Default content{{end}}

// Child template (parsed later)
{{define "title"}}Custom Title: {{.PageTitle}}{{end}}
{{define "content"}}Custom content: {{.Body}}{{end}}

Expected C# API Usage

var engine = new GoTemplateEngine();

// Parse template with definitions
engine.ParseTemplate("base", baseTemplateContent);
engine.ParseTemplate("child", childTemplateContent);

// Execute with data
var result = engine.ExecuteTemplate("base", new { 
    PageTitle = "My Page", 
    Body = "Content here" 
});

Test Cases

Define Directive

  • Define template and verify it's registered
  • Define template with same name twice (last wins)
  • Define nested templates
  • Define template with complex content including other directives

Template Directive

  • Execute existing template with current context
  • Execute existing template with explicit context
  • Execute non-existent template (should error)
  • Execute template that calls other templates

Block Directive

  • Block with no existing template (uses default)
  • Block with existing template (uses existing, ignores default)
  • Block with complex pipeline context
  • Nested blocks

Integration Tests

  • Template inheritance scenarios
  • Circular template references (should error)
  • Deep nesting of template calls
  • Performance with large numbers of templates

What is a comprehensive list of built in functions

Go text templates support pipelines and chaining of actions, which are powerful features that allow you to process data through a series of transformations in a readable, left-to-right manner.

Pipelines

A pipeline in Go templates uses the pipe operator | to pass the output of one action as input to the next. The basic syntax is:

{{ value | function1 | function2 | function3 }}

The value flows from left to right through each function. Here's a simple example:

{{ .Name | printf "Hello, %s!" | print }}

This takes the .Name field, passes it to printf to format it, then passes the result to print.

Chaining Actions

You can chain multiple template actions together using pipelines. Common patterns include:

String manipulation:

{{ .Title | lower | title }}  // Convert to lowercase, then title case
{{ .Content | html | printf "<p>%s</p>" }}  // HTML escape, then wrap in tags

Conditional processing:

{{ .Value | default "N/A" | printf "Result: %s" }}

Multiple transformations:

{{ .Price | printf "%.2f" | printf "$%s" }}  // Format as decimal, then add dollar sign

Built-in Functions in Pipelines

Go templates provide several built-in functions that work well in pipelines:

  • printf - Format strings (like fmt.Sprintf)
  • print, println - Output functions
  • html - HTML escape
  • js - JavaScript escape
  • urlquery - URL query escape
  • len - Length of arrays, slices, maps, strings
  • index - Get element at index
  • slice - Create slice from array/slice

Custom Functions

You can register custom functions for use in pipelines:

funcMap := template.FuncMap{
    "upper": strings.ToUpper,
    "reverse": func(s string) string {
        runes := []rune(s)
        for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
            runes[i], runes[j] = runes[j], runes[i]
        }
        return string(runes)
    },
}

tmpl := template.New("example").Funcs(funcMap)

Then use them in pipelines:

{{ .Name | upper | reverse | printf "Transformed: %s" }}

Complex Pipeline Examples

Processing lists:

{{ range .Items }}
  {{ . | printf "Item: %s" | println }}
{{ end }}

Conditional formatting:

{{ if .IsActive }}
  {{ .Status | upper | printf "STATUS: %s" }}
{{ else }}
  {{ "inactive" | printf "STATUS: %s" }}
{{ end }}

Nested data access:

{{ .User.Email | printf "mailto:%s" | printf "<a href='%s'>Contact</a>" | html }}

Pipelines make Go templates more readable and allow you to build complex data transformations by composing simple functions. The key is that data flows left-to-right through the pipeline, with each function receiving the output of the previous one as its final argument.

What are all the format options for sprintf

Could you provide this in the form of a prompt (with all the same information you just produced) that OpenAI Codex could implement as a class, and with test cases it can use for unit tests

C# sprintf Implementation Prompt for OpenAI Codex

Create a C# class called SprintfFormatter that implements C-style sprintf formatting functionality. The class should provide a static method Format(string format, params object[] args) that mimics the behavior of C's sprintf function.

Required Format Specifiers to Implement

Basic Integer Formats

  • %d, %i - signed decimal integer
    • C# equivalent: {0} or {0:D} or .ToString() or .ToString("D")
  • %u - unsigned decimal integer
    • C# equivalent: {0} or {0:D} or .ToString() or .ToString("D")
  • %o - unsigned octal integer
    • C# equivalent: No direct equivalent, use Convert.ToString(value, 8)
  • %x - unsigned hexadecimal (lowercase a-f)
    • C# equivalent: {0:x} or .ToString("x")
  • %X - unsigned hexadecimal (uppercase A-F)
    • C# equivalent: {0:X} or .ToString("X")

Floating-Point Formats

  • %f, %F - decimal floating point
    • C# equivalent: {0:F} or .ToString("F")
  • %e - scientific notation (lowercase e)
    • C# equivalent: {0:e} or .ToString("e")
  • %E - scientific notation (uppercase E)
    • C# equivalent: {0:E} or .ToString("E")
  • %g - shortest of %f or %e
    • C# equivalent: {0:G} or .ToString("G")
  • %G - shortest of %F or %E
    • C# equivalent: {0:G} or .ToString("G")

Character and String Formats

  • %c - single character
    • C# equivalent: {0} or .ToString()
  • %s - string of characters
    • C# equivalent: {0} or .ToString()
  • %% - literal percent sign
    • C# equivalent: {{ or }} in string.Format, or literal % in interpolation

Pointer Format

  • %p - pointer address (format as hexadecimal with 0x prefix)
    • C# equivalent: No direct equivalent, use custom formatting with IntPtr

Width and Precision Modifiers

  • %10d - minimum width of 10 characters (right-aligned)
    • C# equivalent: {0,10} or .ToString().PadLeft(10)
  • %-10d - left-aligned in 10-character field
    • C# equivalent: {0,-10} or .ToString().PadRight(10)
  • %010d - zero-padded to 10 characters
    • C# equivalent: {0:D10} or .ToString("D10")
  • %.2f - 2 decimal places for floating point
    • C# equivalent: {0:F2} or .ToString("F2")
  • %10.2f - 10 characters wide, 2 decimal places
    • C# equivalent: {0,10:F2} or .ToString("F2").PadLeft(10)
  • %-10.2f - left-aligned, 10 characters wide, 2 decimal places
    • C# equivalent: {0,-10:F2} or .ToString("F2").PadRight(10)

Flags to Support

  • - - left-justify
    • C# equivalent: Use negative width in string.Format {0,-10} or .PadRight()
  • + - always show sign for numbers
    • C# equivalent: Custom formatting with conditional logic, or use ToString("+#;-#;0")
  • (space) - space for positive numbers
    • C# equivalent: Custom formatting with conditional logic
  • # - alternate form (0x for hex, 0 for octal)
    • C# equivalent: Custom formatting, no direct equivalent
  • 0 - zero-pad numbers
    • C# equivalent: Use format specifiers like D10 for integers or width padding

Length Modifiers (Optional but Recommended)

  • %ld - long integer
    • C# equivalent: {0} or .ToString() (long is handled automatically in C#)
  • %hd - short integer
    • C# equivalent: {0} or .ToString() (short is handled automatically in C#)

Implementation Requirements

  1. Create a SprintfFormatter class with a static Format method
  2. Parse the format string to identify format specifiers
  3. Handle all the format types listed above
  4. Support width, precision, and flag modifiers
  5. Throw appropriate exceptions for invalid format strings or argument mismatches
  6. Handle edge cases like null arguments, overflow conditions, etc.

Example Usage

string result1 = SprintfFormatter.Format("%d", 123);              // "123"
string result2 = SprintfFormatter.Format("%.2f", 3.14159);        // "3.14"
string result3 = SprintfFormatter.Format("%10s", "Hello");        // "     Hello"
string result4 = SprintfFormatter.Format("%-10d", 42);            // "42        "
string result5 = SprintfFormatter.Format("%#x", 255);             // "0xff"
string result6 = SprintfFormatter.Format("%+d", 42);              // "+42"

Unit Test Cases to Implement

Create comprehensive unit tests covering all format specifiers and edge cases:

Basic Integer Tests

[Test] public void TestBasicInteger() {
    Assert.AreEqual("123", SprintfFormatter.Format("%d", 123));
    Assert.AreEqual("123", SprintfFormatter.Format("%i", 123));
    Assert.AreEqual("123", SprintfFormatter.Format("%u", 123u));
    Assert.AreEqual("-123", SprintfFormatter.Format("%d", -123));
}

Hexadecimal Tests

[Test] public void TestHexadecimal() {
    Assert.AreEqual("ff", SprintfFormatter.Format("%x", 255));
    Assert.AreEqual("FF", SprintfFormatter.Format("%X", 255));
    Assert.AreEqual("0xff", SprintfFormatter.Format("%#x", 255));
    Assert.AreEqual("0XFF", SprintfFormatter.Format("%#X", 255));
}

Octal Tests

[Test] public void TestOctal() {
    Assert.AreEqual("377", SprintfFormatter.Format("%o", 255));
    Assert.AreEqual("0377", SprintfFormatter.Format("%#o", 255));
}

Floating Point Tests

[Test] public void TestFloatingPoint() {
    Assert.AreEqual("3.140000", SprintfFormatter.Format("%f", 3.14));
    Assert.AreEqual("3.14", SprintfFormatter.Format("%.2f", 3.14));
    Assert.AreEqual("3.1e+00", SprintfFormatter.Format("%.1e", 3.1));
    Assert.AreEqual("3.1E+00", SprintfFormatter.Format("%.1E", 3.1));
    Assert.AreEqual("3.14", SprintfFormatter.Format("%g", 3.14));
    Assert.AreEqual("3.14", SprintfFormatter.Format("%G", 3.14));
}

Width and Alignment Tests

[Test] public void TestWidthAndAlignment() {
    Assert.AreEqual("   123", SprintfFormatter.Format("%6d", 123));
    Assert.AreEqual("123   ", SprintfFormatter.Format("%-6d", 123));
    Assert.AreEqual("000123", SprintfFormatter.Format("%06d", 123));
    Assert.AreEqual("  3.14", SprintfFormatter.Format("%6.2f", 3.14));
    Assert.AreEqual("3.14  ", SprintfFormatter.Format("%-6.2f", 3.14));
}

Character and String Tests

[Test] public void TestCharacterAndString() {
    Assert.AreEqual("A", SprintfFormatter.Format("%c", 'A'));
    Assert.AreEqual("A", SprintfFormatter.Format("%c", 65));
    Assert.AreEqual("Hello", SprintfFormatter.Format("%s", "Hello"));
    Assert.AreEqual("     Hello", SprintfFormatter.Format("%10s", "Hello"));
    Assert.AreEqual("Hello     ", SprintfFormatter.Format("%-10s", "Hello"));
}

Sign and Space Tests

[Test] public void TestSignsAndSpaces() {
    Assert.AreEqual("+123", SprintfFormatter.Format("%+d", 123));
    Assert.AreEqual("-123", SprintfFormatter.Format("%+d", -123));
    Assert.AreEqual(" 123", SprintfFormatter.Format("% d", 123));
    Assert.AreEqual("-123", SprintfFormatter.Format("% d", -123));
}

Special Cases Tests

[Test] public void TestSpecialCases() {
    Assert.AreEqual("%", SprintfFormatter.Format("%%"));
    Assert.AreEqual("0", SprintfFormatter.Format("%d", 0));
    Assert.AreEqual("0.000000", SprintfFormatter.Format("%f", 0.0));
}

Multiple Arguments Tests

[Test] public void TestMultipleArguments() {
    Assert.AreEqual("Hello 123 3.14", SprintfFormatter.Format("%s %d %.2f", "Hello", 123, 3.14159));
    Assert.AreEqual("Value: 42, Hex: 2a", SprintfFormatter.Format("Value: %d, Hex: %x", 42, 42));
}

Error Cases Tests

[Test] public void TestErrorCases() {
    Assert.Throws<ArgumentException>(() => SprintfFormatter.Format("%d"));  // Missing argument
    Assert.Throws<ArgumentException>(() => SprintfFormatter.Format("%d", "not a number"));  // Wrong type
    Assert.Throws<FormatException>(() => SprintfFormatter.Format("%q", 123));  // Invalid format
}

Pointer Tests (if implementing %p)

[Test] public void TestPointer() {
    IntPtr ptr = new IntPtr(0x12345678);
    string result = SprintfFormatter.Format("%p", ptr);
    Assert.IsTrue(result.StartsWith("0x"));
    Assert.IsTrue(result.Contains("12345678"));
}

Implementation Notes

  1. Use regular expressions or state machine parsing to identify format specifiers
  2. Handle type conversion carefully - integers should work with various numeric types
  3. For floating point formats, pay attention to precision and scientific notation thresholds
  4. Consider thread safety if needed
  5. Provide clear error messages for invalid formats or argument mismatches
  6. Handle null arguments gracefully
  7. Consider performance optimization for frequently used format patterns

C# Format Reference for Implementation

When implementing each sprintf format, use these C# equivalents as the underlying mechanism:

sprintf Format C# string.Format C# ToString() C# Interpolation
%d, %i {0} or {0:D} .ToString() or .ToString("D") $"{value}"
%u {0} or {0:D} .ToString() or .ToString("D") $"{value}"
%o Custom logic Convert.ToString(value, 8) $"{Convert.ToString(value, 8)}"
%x {0:x} .ToString("x") $"{value:x}"
%X {0:X} .ToString("X") $"{value:X}"
%f, %F {0:F} .ToString("F") $"{value:F}"
%e {0:e} .ToString("e") $"{value:e}"
%E {0:E} .ToString("E") $"{value:E}"
%g {0:G} .ToString("G") $"{value:G}"
%G {0:G} .ToString("G") $"{value:G}"
%c {0} .ToString() $"{value}"
%s {0} .ToString() $"{value}"
%10d {0,10} .ToString().PadLeft(10) $"{value,10}"
%-10d {0,-10} .ToString().PadRight(10) $"{value,-10}"
%010d {0:D10} .ToString("D10") $"{value:D10}"
%.2f {0:F2} .ToString("F2") $"{value:F2}"

Bonus Features (Optional)

  • Support for positional arguments like %2$d (argument 2)
  • Support for * width and precision specifiers from arguments
  • Additional length modifiers (ll, h, hh)
  • Better error reporting with position information

The implementation should be robust, well-tested, and handle edge cases appropriately while maintaining compatibility with standard C sprintf behavior.

@yetanotherchris
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment