diff --git a/internal/utils/multiindex.go b/internal/utils/multiindex.go new file mode 100644 index 0000000..2504bba --- /dev/null +++ b/internal/utils/multiindex.go @@ -0,0 +1,57 @@ +package utils + +import ( + "strings" +) + +type MultiIndex[T any] struct { + items []T + indexes []map[string]*T +} + +func NewMultiIndex[T any](numIndexes int) *MultiIndex[T] { + m := &MultiIndex[T]{ + items: make([]T, 0), + indexes: make([]map[string]*T, numIndexes), + } + for i := range m.indexes { + m.indexes[i] = make(map[string]*T) + } + return m +} + +func (m *MultiIndex[T]) AddItem(item T, searchKeys [][]string) { + if len(searchKeys) != len(m.indexes) { + panic("searchKeys length must match number of indexes") + } + + m.items = append(m.items, item) + itemPtr := &m.items[len(m.items)-1] + + for i, key := range searchKeys { + m.indexes[i][keyToString(key)] = itemPtr + } +} + +func (m *MultiIndex[T]) FindItem(indexNum int, key []string) (T, bool) { + var zero T + if indexNum < 0 || indexNum >= len(m.indexes) { + return zero, false + } + + itemPtr, found := m.indexes[indexNum][keyToString(key)] + if !found { + return zero, false + } + return *itemPtr, true +} + +func (m *MultiIndex[T]) Items() []T { + result := make([]T, len(m.items)) + copy(result, m.items) + return result +} + +func keyToString(parts []string) string { + return strings.Join(parts, "\x00") +} diff --git a/main.go b/main.go index 7ac9c86..ccf7add 100644 --- a/main.go +++ b/main.go @@ -2,30 +2,210 @@ package main import ( "c65gm/internal/preproc" + "flag" "fmt" + "os" + "strings" ) -// put the DefineList type in another file (same package) or import it; -// here I assume definelist.go is in its own package "definelist". -// If it's in the same package main, just call New() directly. +// c65cm - A 6502 Cross-Compiler for the ACME Cross-Assembler +// Copyright (C) 1999, 2025 Mattias Hansson +// Distributed under GPL. External library github.com/armon/go-radix under MIT. + +// Package-level shared state accessible by other compilation units +var ( +/* + VarList *VarList + WhileStack *LabelStack + WendStack *LabelStack + IfStack *LabelStack + GeneralStack *LabelStack + FuncList *FunctionHandler + ConstStrHandler *ConstantStringHandler + Pragmas []string +*/ + +) + +type ShowMode int + +const ( + ShowNone ShowMode = iota + ShowLineCount + ShowLineView +) func main() { - defs := preproc.NewDefineList() // <-- important + fmt.Println("c65cm - A 6502 Cross-Compiler for the ACME Cross-Assembler.") + fmt.Println("Copyright (C) 1999, 2025 Mattias Hansson. v0.7") + fmt.Println("Distributed under GPL. External library github.com/armon/go-radix under MIT.") + fmt.Println() - defs.Add("ARRAY_MIN", "0") - defs.Add("ARRAY_MAX", "100") - defs.Add("ARRAY_MAX_PAX", "1000") - defs.Add("VERSION", "1.0") - defs.Add("π", "3.14159") + inFile := flag.String("in", "", "input source file (required)") + outFile := flag.String("out", "", "output assembly file (required)") + view := flag.String("view", "lines", "output mode: none, count, lines") + flag.Parse() - src := `for i := ARRAY_MIN to ARRAY_MAX_PAX do - writeln(VERSION); - writeln(ARRAY_MAX); - const PI = π;` + if *inFile == "" || *outFile == "" { + fmt.Fprintln(os.Stderr, "Error: -in and -out are required") + flag.Usage() + os.Exit(1) + } - fmt.Println("Original:\n" + src) - fmt.Println("\nReplaced:\n" + defs.ReplaceDefines(src)) - defs.Delete("ARRAY_MAX_PAX") - fmt.Println("\nReplaced (after .Delete):\n" + defs.ReplaceDefines(src)) + var showMode ShowMode + switch strings.ToLower(*view) { + case "none": + showMode = ShowNone + case "count": + showMode = ShowLineCount + case "lines": + showMode = ShowLineView + default: + fmt.Fprintf(os.Stderr, "Error: invalid view mode: %s\n", *view) + os.Exit(1) + } + if err := run(*inFile, *outFile, showMode); err != nil { + if _, ok := err.(preproc.HaltError); ok { + os.Exit(0) + } + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} + +func run(inFile, outFile string, showMode ShowMode) error { + initSharedObjects() + return compile(inFile, outFile, showMode) +} + +func initSharedObjects() { + /* + VarList = NewVarList() + WhileStack = NewLabelStack("whilelbl") + WendStack = NewLabelStack("wendlbl") + IfStack = NewLabelStack("iflbl") + GeneralStack = NewLabelStack("General") + FuncList = NewFunctionHandler() + ConstStrHandler = NewConstantStringHandler() + Pragmas = make([]string, 0) + */ +} + +func compile(inFile, outFile string, showMode ShowMode) error { + lines, err := preproc.PreProcess(inFile) + if err != nil { + return fmt.Errorf("preprocessor failed: %w", err) + } + + asmSource := make([]string, 0, len(lines)*2) + lineCount := 0 + + for i := 0; i < len(lines); { + line := lines[i] + lineCount++ + + displayProgress(showMode, lineCount, line.Text) + + // Handle inline ASM blocks + if isAsmStart(line) { + asmSource = append(asmSource, ";"+line.Text) + i++ + + for i < len(lines) && !isAsmEnd(lines[i]) { + lineCount++ + displayProgress(showMode, lineCount, lines[i].Text) + + if lines[i].Text != "" { + //processed := LocalVarsInAsmLine(lines[i].Text) + processed := lines[i].Text + asmSource = append(asmSource, processed) + } + i++ + } + + i++ // Skip ENDASM line + continue + } + + if isAsmEnd(line) { + i++ + continue + } + + text := stripComments(line.Text) + if text == "" { + i++ + continue + } + + /* + if !InterpretLine(text, &asmSource) { + return fmt.Errorf("line not recognized: %s", text) + } + */ + + i++ + } + + /* + VarList.WriteDownTypesToAsm(&asmSource) + ConstStrHandler.GenerateConstStrDecls(&asmSource) + VarList.WriteAbsVars(&asmSource) + VarList.WriteConstants(&asmSource) + */ + + if err := writeLines(outFile, asmSource); err != nil { + return fmt.Errorf("failed to write output: %w", err) + } + + if showMode != ShowNone { + fmt.Println("\ndone.") + } + + return nil +} + +func displayProgress(mode ShowMode, lineCount int, text string) { + switch mode { + case ShowLineCount: + fmt.Printf("\rCompiling line: %d", lineCount) + case ShowLineView: + fmt.Println(text) + } +} + +func isAsmStart(line preproc.Line) bool { + //return len(line.Tokens) == 1 && strings.ToUpper(line.Tokens[0]) == "ASM" + return false +} + +func isAsmEnd(line preproc.Line) bool { + //return len(line.Tokens) == 1 && strings.ToUpper(line.Tokens[0]) == "ENDASM" + return false +} + +func stripComments(s string) string { + if strings.HasPrefix(s, "//") { + return "" + } + if idx := strings.Index(s, "//"); idx >= 0 { + return strings.TrimSpace(s[:idx]) + } + return s +} + +func writeLines(filename string, lines []string) error { + f, err := os.Create(filename) + if err != nil { + return err + } + defer f.Close() + + for _, line := range lines { + if _, err := fmt.Fprintln(f, line); err != nil { + return err + } + } + return nil }