Added conststr + tests for handling constant strings.

This commit is contained in:
Mattias Hansson 2025-10-26 21:06:11 +01:00
parent 134dbd1d3c
commit 8b4c7dc0d4
3 changed files with 267 additions and 0 deletions

View file

@ -0,0 +1,74 @@
package compiler
import "c65gm/internal/preproc"
const (
asmStringDeclStart = " !raw "
asmStringDeclEnd = " !8 0"
asmCBMStringDeclStart = " !pet "
asmCBMStringDeclEnd = " !8 0"
)
type ConstantString struct {
label string
value string
compressable bool
useCBMStrings bool
}
type ConstantStringHandler struct {
strings []*ConstantString
compressMap map[string]string
}
func NewConstantStringHandler() *ConstantStringHandler {
return &ConstantStringHandler{
strings: make([]*ConstantString, 0),
compressMap: make(map[string]string),
}
}
func (h *ConstantStringHandler) AddConstStr(label, value string, compress bool, ps preproc.PragmaSet) string {
useCBM := ps.GetPragma("_P_USE_CBM_STRINGS") != ""
if compress {
if existingLabel, exists := h.compressMap[value]; exists {
return existingLabel
}
}
cs := &ConstantString{
label: label,
value: value,
compressable: compress,
useCBMStrings: useCBM,
}
h.strings = append(h.strings, cs)
if compress {
h.compressMap[value] = label
}
return label
}
func (h *ConstantStringHandler) GenerateConstStrDecls() []string {
result := make([]string, 0, len(h.strings)*3)
for _, cs := range h.strings {
startStr := asmStringDeclStart
endStr := asmStringDeclEnd
if cs.useCBMStrings {
startStr = asmCBMStringDeclStart
endStr = asmCBMStringDeclEnd
}
result = append(result, cs.label)
result = append(result, startStr+cs.value)
result = append(result, endStr)
}
return result
}

View file

@ -0,0 +1,188 @@
package compiler
import (
"c65gm/internal/preproc"
"testing"
)
func TestAddConstStr_NonCompressable(t *testing.T) {
h := NewConstantStringHandler()
ps := preproc.PragmaSet{}
label1 := h.AddConstStr("lbl1", "hello", false, ps)
label2 := h.AddConstStr("lbl2", "hello", false, ps)
if label1 != "lbl1" {
t.Errorf("Expected label1 = lbl1, got %s", label1)
}
if label2 != "lbl2" {
t.Errorf("Expected label2 = lbl2, got %s", label2)
}
if len(h.strings) != 2 {
t.Errorf("Expected 2 strings, got %d", len(h.strings))
}
}
func TestAddConstStr_CompressableDedupe(t *testing.T) {
h := NewConstantStringHandler()
ps := preproc.PragmaSet{}
label1 := h.AddConstStr("lbl1", "hello", true, ps)
label2 := h.AddConstStr("lbl2", "hello", true, ps)
if label1 != "lbl1" {
t.Errorf("Expected label1 = lbl1, got %s", label1)
}
if label2 != "lbl1" {
t.Errorf("Expected label2 = lbl1 (deduped), got %s", label2)
}
if len(h.strings) != 1 {
t.Errorf("Expected 1 string after dedup, got %d", len(h.strings))
}
}
func TestAddConstStr_CompressableNoDedupe(t *testing.T) {
h := NewConstantStringHandler()
ps := preproc.PragmaSet{}
label1 := h.AddConstStr("lbl1", "hello", true, ps)
label2 := h.AddConstStr("lbl2", "world", true, ps)
if label1 != "lbl1" {
t.Errorf("Expected label1 = lbl1, got %s", label1)
}
if label2 != "lbl2" {
t.Errorf("Expected label2 = lbl2, got %s", label2)
}
if len(h.strings) != 2 {
t.Errorf("Expected 2 strings (different values), got %d", len(h.strings))
}
}
func TestGenerateConstStrDecls_Normal(t *testing.T) {
h := NewConstantStringHandler()
ps := preproc.PragmaSet{}
h.AddConstStr("str1", "\"hello\"", false, ps)
result := h.GenerateConstStrDecls()
expected := []string{
"str1",
" !raw \"hello\"",
" !8 0",
}
if len(result) != len(expected) {
t.Fatalf("Expected %d lines, got %d", len(expected), len(result))
}
for i, exp := range expected {
if result[i] != exp {
t.Errorf("Line %d: expected %q, got %q", i, exp, result[i])
}
}
}
func TestGenerateConstStrDecls_CBM(t *testing.T) {
h := NewConstantStringHandler()
ps := mockPragmaSet(map[string]string{"_P_USE_CBM_STRINGS": "1"})
h.AddConstStr("str1", "\"hello\"", false, ps)
result := h.GenerateConstStrDecls()
expected := []string{
"str1",
" !pet \"hello\"",
" !8 0",
}
if len(result) != len(expected) {
t.Fatalf("Expected %d lines, got %d", len(expected), len(result))
}
for i, exp := range expected {
if result[i] != exp {
t.Errorf("Line %d: expected %q, got %q", i, exp, result[i])
}
}
}
func TestGenerateConstStrDecls_MixedPragmas(t *testing.T) {
h := NewConstantStringHandler()
psNormal := preproc.PragmaSet{}
psCBM := mockPragmaSet(map[string]string{"_P_USE_CBM_STRINGS": "1"})
h.AddConstStr("str1", "\"hello\"", false, psNormal)
h.AddConstStr("str2", "\"world\"", false, psCBM)
result := h.GenerateConstStrDecls()
expected := []string{
"str1",
" !raw \"hello\"",
" !8 0",
"str2",
" !pet \"world\"",
" !8 0",
}
if len(result) != len(expected) {
t.Fatalf("Expected %d lines, got %d", len(expected), len(result))
}
for i, exp := range expected {
if result[i] != exp {
t.Errorf("Line %d: expected %q, got %q", i, exp, result[i])
}
}
}
func TestGenerateConstStrDecls_Order(t *testing.T) {
h := NewConstantStringHandler()
ps := preproc.PragmaSet{}
h.AddConstStr("str1", "\"first\"", false, ps)
h.AddConstStr("str2", "\"second\"", false, ps)
h.AddConstStr("str3", "\"third\"", false, ps)
result := h.GenerateConstStrDecls()
if len(result) != 9 {
t.Fatalf("Expected 9 lines, got %d", len(result))
}
if result[0] != "str1" || result[3] != "str2" || result[6] != "str3" {
t.Errorf("Labels not in correct order")
}
}
func TestAddConstStr_CompressOnlyDedupesSameValue(t *testing.T) {
h := NewConstantStringHandler()
ps := preproc.PragmaSet{}
h.AddConstStr("lbl1", "hello", true, ps)
h.AddConstStr("lbl2", "hello", true, ps)
h.AddConstStr("lbl3", "world", true, ps)
h.AddConstStr("lbl4", "hello", true, ps)
if len(h.strings) != 2 {
t.Errorf("Expected 2 unique strings, got %d", len(h.strings))
}
if len(h.compressMap) != 2 {
t.Errorf("Expected 2 entries in compress map, got %d", len(h.compressMap))
}
if h.compressMap["hello"] != "lbl1" {
t.Errorf("Expected hello -> lbl1, got %s", h.compressMap["hello"])
}
if h.compressMap["world"] != "lbl3" {
t.Errorf("Expected world -> lbl3, got %s", h.compressMap["world"])
}
}
func mockPragmaSet(m map[string]string) preproc.PragmaSet {
return preproc.NewPragmaSet(m)
}

View file

@ -13,6 +13,11 @@ func (ps PragmaSet) GetPragma(name string) string {
return "" return ""
} }
// For mocking
func NewPragmaSet(m map[string]string) PragmaSet {
return PragmaSet{m: m}
}
// Pragma manages an immutable stack of PragmaSet snapshots. // Pragma manages an immutable stack of PragmaSet snapshots.
type Pragma struct { type Pragma struct {
pragmaSetStack []PragmaSet pragmaSetStack []PragmaSet