definelist and pragma got tests. Still working on preproc
This commit is contained in:
parent
e34c47c557
commit
f7f247b69c
4 changed files with 419 additions and 83 deletions
186
internal/preproc/definelist_test.go
Normal file
186
internal/preproc/definelist_test.go
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
package preproc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDefineList_AddAndDefined(t *testing.T) {
|
||||||
|
dl := NewDefineList()
|
||||||
|
|
||||||
|
if dl.Defined("FOO") {
|
||||||
|
t.Error("FOO should not be defined initially")
|
||||||
|
}
|
||||||
|
|
||||||
|
dl.Add("FOO", "bar")
|
||||||
|
if !dl.Defined("FOO") {
|
||||||
|
t.Error("FOO should be defined after Add")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefineList_Delete(t *testing.T) {
|
||||||
|
dl := NewDefineList()
|
||||||
|
dl.Add("FOO", "bar")
|
||||||
|
|
||||||
|
if !dl.Delete("FOO") {
|
||||||
|
t.Error("Delete should return true for existing key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if dl.Defined("FOO") {
|
||||||
|
t.Error("FOO should not be defined after Delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
if dl.Delete("NONEXISTENT") {
|
||||||
|
t.Error("Delete should return false for non-existent key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefineList_ReplaceDefines_Simple(t *testing.T) {
|
||||||
|
dl := NewDefineList()
|
||||||
|
dl.Add("FOO", "replacement")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"FOO", "replacement"},
|
||||||
|
{"before FOO after", "before replacement after"},
|
||||||
|
{"FOOFOO", "replacementreplacement"},
|
||||||
|
{"no match here", "no match here"},
|
||||||
|
{"", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := dl.ReplaceDefines(tt.input)
|
||||||
|
if got != tt.expected {
|
||||||
|
t.Errorf("ReplaceDefines(%q) = %q, want %q", tt.input, got, tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefineList_ReplaceDefines_LongestPrefix(t *testing.T) {
|
||||||
|
dl := NewDefineList()
|
||||||
|
dl.Add("FOO", "short")
|
||||||
|
dl.Add("FOOBAR", "long")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"FOO", "short"},
|
||||||
|
{"FOOBAR", "long"}, // longest match wins
|
||||||
|
{"FOOBARBAZ", "longBAZ"}, // longest match, rest unchanged
|
||||||
|
{"FOO BAR", "short BAR"}, // space breaks match
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := dl.ReplaceDefines(tt.input)
|
||||||
|
if got != tt.expected {
|
||||||
|
t.Errorf("ReplaceDefines(%q) = %q, want %q", tt.input, got, tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefineList_ReplaceDefines_NoRecursion(t *testing.T) {
|
||||||
|
dl := NewDefineList()
|
||||||
|
dl.Add("A", "B")
|
||||||
|
dl.Add("B", "C")
|
||||||
|
|
||||||
|
// Should not recursively expand B->C
|
||||||
|
if got := dl.ReplaceDefines("A"); got != "B" {
|
||||||
|
t.Errorf("ReplaceDefines should not recurse: got %q, want %q", got, "B")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefineList_ReplaceDefines_CaseSensitive(t *testing.T) {
|
||||||
|
dl := NewDefineList()
|
||||||
|
dl.Add("FOO", "bar")
|
||||||
|
|
||||||
|
if got := dl.ReplaceDefines("foo"); got != "foo" {
|
||||||
|
t.Errorf("Should be case-sensitive: got %q, want %q", got, "foo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefineList_ReplaceDefines_UTF8(t *testing.T) {
|
||||||
|
dl := NewDefineList()
|
||||||
|
dl.Add("🚀", "rocket")
|
||||||
|
dl.Add("αβ", "alpha-beta")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"🚀", "rocket"},
|
||||||
|
{"test🚀test", "testrockettest"},
|
||||||
|
{"αβγ", "alpha-betaγ"},
|
||||||
|
{"日本語FOO", "日本語FOO"}, // no match
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := dl.ReplaceDefines(tt.input)
|
||||||
|
if got != tt.expected {
|
||||||
|
t.Errorf("ReplaceDefines(%q) = %q, want %q", tt.input, got, tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefineList_ReplaceDefines_MultipleReplacements(t *testing.T) {
|
||||||
|
dl := NewDefineList()
|
||||||
|
dl.Add("X", "1")
|
||||||
|
dl.Add("Y", "2")
|
||||||
|
dl.Add("Z", "3")
|
||||||
|
|
||||||
|
got := dl.ReplaceDefines("X Y Z X")
|
||||||
|
want := "1 2 3 1"
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("ReplaceDefines = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefineList_ReplaceDefines_OverlappingPrefixes(t *testing.T) {
|
||||||
|
dl := NewDefineList()
|
||||||
|
dl.Add("A", "x")
|
||||||
|
dl.Add("AB", "y")
|
||||||
|
dl.Add("ABC", "z")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"A", "x"},
|
||||||
|
{"AB", "y"},
|
||||||
|
{"ABC", "z"},
|
||||||
|
{"ABCD", "zD"},
|
||||||
|
{"AAA", "xxx"}, // three separate A matches
|
||||||
|
{"ABABC", "yz"}, // AB, then ABC
|
||||||
|
{"A-AB-ABC", "x-y-z"}, // separated by dashes
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := dl.ReplaceDefines(tt.input)
|
||||||
|
if got != tt.expected {
|
||||||
|
t.Errorf("ReplaceDefines(%q) = %q, want %q", tt.input, got, tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefineList_ReplaceDefines_EmptyValue(t *testing.T) {
|
||||||
|
dl := NewDefineList()
|
||||||
|
dl.Add("REMOVE", "")
|
||||||
|
|
||||||
|
got := dl.ReplaceDefines("before REMOVE after")
|
||||||
|
want := "before after"
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("ReplaceDefines = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefineList_ReplaceDefines_UpdateValue(t *testing.T) {
|
||||||
|
dl := NewDefineList()
|
||||||
|
dl.Add("FOO", "first")
|
||||||
|
dl.Add("FOO", "second") // update
|
||||||
|
|
||||||
|
got := dl.ReplaceDefines("FOO")
|
||||||
|
if got != "second" {
|
||||||
|
t.Errorf("After update: got %q, want %q", got, "second")
|
||||||
|
}
|
||||||
|
}
|
||||||
44
internal/preproc/pragma.go
Normal file
44
internal/preproc/pragma.go
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
package preproc
|
||||||
|
|
||||||
|
// PragmaSet is an immutable snapshot of pragma name->value mappings.
|
||||||
|
type PragmaSet struct {
|
||||||
|
m map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPragma returns the value for name or "" if not present.
|
||||||
|
func (ps PragmaSet) GetPragma(name string) string {
|
||||||
|
if v, ok := ps.m[name]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pragma manages an immutable stack of PragmaSet snapshots.
|
||||||
|
type Pragma struct {
|
||||||
|
pragmaSetStack []PragmaSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPragma() *Pragma {
|
||||||
|
return &Pragma{
|
||||||
|
pragmaSetStack: []PragmaSet{{m: make(map[string]string)}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPragma adds or replaces a pragma by name.
|
||||||
|
func (p *Pragma) AddPragma(name, value string) {
|
||||||
|
last := p.pragmaSetStack[len(p.pragmaSetStack)-1].m
|
||||||
|
newMap := make(map[string]string, len(last)+1)
|
||||||
|
for k, v := range last {
|
||||||
|
newMap[k] = v
|
||||||
|
}
|
||||||
|
newMap[name] = value
|
||||||
|
p.pragmaSetStack = append(p.pragmaSetStack, PragmaSet{m: newMap})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pragma) GetCurrentPragmaSetIndex() int {
|
||||||
|
return len(p.pragmaSetStack) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pragma) GetPragmaSetByIndex(index int) PragmaSet {
|
||||||
|
return p.pragmaSetStack[index] // panics if bad
|
||||||
|
}
|
||||||
82
internal/preproc/pragma_test.go
Normal file
82
internal/preproc/pragma_test.go
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
package preproc
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestNewPragma(t *testing.T) {
|
||||||
|
p := NewPragma()
|
||||||
|
if len(p.pragmaSetStack) != 1 {
|
||||||
|
t.Errorf("expected initial stack length 1, got %d", len(p.pragmaSetStack))
|
||||||
|
}
|
||||||
|
if p.GetCurrentPragmaSetIndex() != 0 {
|
||||||
|
t.Errorf("expected initial index 0, got %d", p.GetCurrentPragmaSetIndex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddPragma(t *testing.T) {
|
||||||
|
p := NewPragma()
|
||||||
|
|
||||||
|
p.AddPragma("foo", "bar")
|
||||||
|
if p.GetCurrentPragmaSetIndex() != 1 {
|
||||||
|
t.Errorf("expected index 1 after add, got %d", p.GetCurrentPragmaSetIndex())
|
||||||
|
}
|
||||||
|
|
||||||
|
ps := p.GetPragmaSetByIndex(1)
|
||||||
|
if ps.GetPragma("foo") != "bar" {
|
||||||
|
t.Errorf("expected 'bar', got '%s'", ps.GetPragma("foo"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPragmaImmutability(t *testing.T) {
|
||||||
|
p := NewPragma()
|
||||||
|
p.AddPragma("x", "1")
|
||||||
|
idx1 := p.GetCurrentPragmaSetIndex()
|
||||||
|
|
||||||
|
p.AddPragma("x", "2")
|
||||||
|
idx2 := p.GetCurrentPragmaSetIndex()
|
||||||
|
|
||||||
|
ps1 := p.GetPragmaSetByIndex(idx1)
|
||||||
|
ps2 := p.GetPragmaSetByIndex(idx2)
|
||||||
|
|
||||||
|
if ps1.GetPragma("x") != "1" {
|
||||||
|
t.Errorf("snapshot corrupted: expected '1', got '%s'", ps1.GetPragma("x"))
|
||||||
|
}
|
||||||
|
if ps2.GetPragma("x") != "2" {
|
||||||
|
t.Errorf("expected '2', got '%s'", ps2.GetPragma("x"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPragmaMissing(t *testing.T) {
|
||||||
|
p := NewPragma()
|
||||||
|
ps := p.GetPragmaSetByIndex(0)
|
||||||
|
|
||||||
|
if ps.GetPragma("missing") != "" {
|
||||||
|
t.Errorf("expected empty string for missing pragma")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPragmaWrongIndex(t *testing.T) {
|
||||||
|
p := NewPragma()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r == nil {
|
||||||
|
t.Errorf("expected panic for index -1")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
p.GetPragmaSetByIndex(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiplePragmas(t *testing.T) {
|
||||||
|
p := NewPragma()
|
||||||
|
p.AddPragma("a", "1")
|
||||||
|
p.AddPragma("b", "2")
|
||||||
|
|
||||||
|
ps := p.GetPragmaSetByIndex(p.GetCurrentPragmaSetIndex())
|
||||||
|
|
||||||
|
if ps.GetPragma("a") != "1" {
|
||||||
|
t.Errorf("expected 'a'='1'")
|
||||||
|
}
|
||||||
|
if ps.GetPragma("b") != "2" {
|
||||||
|
t.Errorf("expected 'b'='2'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package preproc
|
package preproc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -15,6 +14,7 @@ type Line struct {
|
||||||
Filename string // file the line came from (after resolving includes)
|
Filename string // file the line came from (after resolving includes)
|
||||||
LineNo int // 1-based line number in Filename
|
LineNo int // 1-based line number in Filename
|
||||||
Tokens []string // whitespace-split tokens from Text (space or tab; consecutive collapsed)
|
Tokens []string // whitespace-split tokens from Text (space or tab; consecutive collapsed)
|
||||||
|
PragmaSetIndex int // index into Pragma stack for this line
|
||||||
}
|
}
|
||||||
|
|
||||||
// HaltError is returned when a `#HALT` directive is encountered.
|
// HaltError is returned when a `#HALT` directive is encountered.
|
||||||
|
|
@ -33,15 +33,17 @@ func PreProcess(rootFilename string) ([]Line, error) {
|
||||||
|
|
||||||
type preproc struct {
|
type preproc struct {
|
||||||
defs *DefineList // from definelist.go
|
defs *DefineList // from definelist.go
|
||||||
pragma map[string]string // #PRAGMA NAME VALUE (stored, not interpreted here)
|
pragma *Pragma // pragma handler
|
||||||
cond []bool // conditional stack; a line is active if all are true
|
cond []bool // conditional stack; a line is active if all are true
|
||||||
|
inAsm bool // true when inside ASM/ENDASM block
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPreproc() *preproc {
|
func newPreproc() *preproc {
|
||||||
return &preproc{
|
return &preproc{
|
||||||
defs: NewDefineList(),
|
defs: NewDefineList(),
|
||||||
pragma: make(map[string]string),
|
pragma: NewPragma(),
|
||||||
cond: []bool{},
|
cond: []bool{},
|
||||||
|
inAsm: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,49 +52,99 @@ func (p *preproc) run(root string) ([]Line, error) {
|
||||||
|
|
||||||
type frame struct {
|
type frame struct {
|
||||||
path string
|
path string
|
||||||
f *os.File
|
lines []string // file contents split into lines (no newline)
|
||||||
s *bufio.Scanner
|
idx int // next line index (0-based)
|
||||||
line int
|
line int // last emitted line number (1-based)
|
||||||
dir string
|
dir string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cache of already-read files: fullpath -> []string
|
||||||
|
cache := make(map[string][]string)
|
||||||
|
|
||||||
newFrame := func(path string) (*frame, error) {
|
newFrame := func(path string) (*frame, error) {
|
||||||
f, err := os.Open(path)
|
abs, err := filepath.Abs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sc := bufio.NewScanner(f)
|
if lines, ok := cache[abs]; ok {
|
||||||
// Allow long lines (Pascal source and macro expansion can be large)
|
return &frame{path: abs, lines: lines, idx: 0, line: 0, dir: filepath.Dir(abs)}, nil
|
||||||
sc.Buffer(make([]byte, 0, 64*1024), 10*1024*1024)
|
}
|
||||||
return &frame{path: path, f: f, s: sc, line: 0, dir: filepath.Dir(path)}, nil
|
data, err := os.ReadFile(abs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(data), "\n")
|
||||||
|
cache[abs] = lines
|
||||||
|
return &frame{path: abs, lines: lines, idx: 0, line: 0, dir: filepath.Dir(abs)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var stack []*frame
|
var frameStack []*frame
|
||||||
|
|
||||||
absRoot, _ := filepath.Abs(root)
|
absRoot, _ := filepath.Abs(root)
|
||||||
fr, err := newFrame(absRoot)
|
frRoot, err := newFrame(absRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
stack = append(stack, fr)
|
frameStack = append(frameStack, frRoot)
|
||||||
|
|
||||||
for len(stack) > 0 {
|
for len(frameStack) > 0 {
|
||||||
fr := stack[len(stack)-1]
|
currFrame := frameStack[len(frameStack)-1]
|
||||||
if !fr.s.Scan() {
|
|
||||||
if err := fr.s.Err(); err != nil {
|
// if we've exhausted lines in this frame, pop it
|
||||||
_ = fr.f.Close()
|
if currFrame.idx >= len(currFrame.lines) {
|
||||||
return nil, err
|
frameStack = frameStack[:len(frameStack)-1]
|
||||||
}
|
continue
|
||||||
_ = fr.f.Close()
|
}
|
||||||
stack = stack[:len(stack)-1]
|
|
||||||
|
// advance to next line
|
||||||
|
raw := currFrame.lines[currFrame.idx]
|
||||||
|
currFrame.idx++
|
||||||
|
currFrame.line = currFrame.idx
|
||||||
|
|
||||||
|
includeSource := p.shouldIncludeSource()
|
||||||
|
tokens := strings.Fields(raw)
|
||||||
|
|
||||||
|
// ASM mode handling
|
||||||
|
if !p.inAsm {
|
||||||
|
// Check for ASM entry
|
||||||
|
if includeSource && len(tokens) > 0 && tokens[0] == "ASM" {
|
||||||
|
p.inAsm = true
|
||||||
|
out = append(out, Line{
|
||||||
|
Text: raw,
|
||||||
|
Filename: currFrame.path,
|
||||||
|
LineNo: currFrame.line,
|
||||||
|
Tokens: []string{},
|
||||||
|
PragmaSetIndex: p.pragma.GetCurrentPragmaSetIndex(),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We're in ASM mode
|
||||||
|
// Check for ENDASM
|
||||||
|
if len(tokens) > 0 && tokens[0] == "ENDASM" {
|
||||||
|
p.inAsm = false
|
||||||
|
out = append(out, Line{
|
||||||
|
Text: raw,
|
||||||
|
Filename: currFrame.path,
|
||||||
|
LineNo: currFrame.line,
|
||||||
|
Tokens: []string{},
|
||||||
|
PragmaSetIndex: p.pragma.GetCurrentPragmaSetIndex(),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Otherwise emit line verbatim
|
||||||
|
out = append(out, Line{
|
||||||
|
Text: raw,
|
||||||
|
Filename: currFrame.path,
|
||||||
|
LineNo: currFrame.line,
|
||||||
|
Tokens: []string{},
|
||||||
|
PragmaSetIndex: p.pragma.GetCurrentPragmaSetIndex(),
|
||||||
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fr.line++
|
|
||||||
raw := fr.s.Text()
|
|
||||||
|
|
||||||
trim := strings.TrimSpace(raw)
|
trim := strings.TrimSpace(raw)
|
||||||
isDirective := strings.HasPrefix(trim, "#")
|
isDirective := strings.HasPrefix(trim, "#")
|
||||||
active := p.isActive()
|
|
||||||
|
|
||||||
if isDirective {
|
if isDirective {
|
||||||
parts := strings.Fields(trim)
|
parts := strings.Fields(trim)
|
||||||
|
|
@ -101,31 +153,31 @@ func (p *preproc) run(root string) ([]Line, error) {
|
||||||
}
|
}
|
||||||
switch strings.ToUpper(parts[0]) {
|
switch strings.ToUpper(parts[0]) {
|
||||||
case "#DEFINE":
|
case "#DEFINE":
|
||||||
if active && len(parts) >= 2 {
|
if includeSource && len(parts) >= 2 {
|
||||||
name := parts[1]
|
name := parts[1]
|
||||||
val := ""
|
val := ""
|
||||||
if len(parts) > 2 {
|
if len(parts) > 2 {
|
||||||
val = strings.Join(parts[2:], " ")
|
val = strings.Join(parts[2:], " ")
|
||||||
val = p.defs.ReplaceDefines(val) // allow nested defines in values
|
val = p.defs.ReplaceDefines(val)
|
||||||
}
|
}
|
||||||
p.defs.Add(name, val)
|
p.defs.Add(name, val)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case "#UNDEF":
|
case "#UNDEF":
|
||||||
if active && len(parts) >= 2 {
|
if includeSource && len(parts) >= 2 {
|
||||||
p.defs.Delete(parts[1])
|
p.defs.Delete(parts[1])
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case "#IFDEF":
|
case "#IFDEF":
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return nil, fmt.Errorf("#IFDEF requires exactly one argument at %s:%d", fr.path, fr.line)
|
return nil, fmt.Errorf("#IFDEF requires exactly one argument at %s:%d", currFrame.path, currFrame.line)
|
||||||
}
|
}
|
||||||
p.cond = append(p.cond, p.defs.Defined(parts[1]))
|
p.cond = append(p.cond, p.defs.Defined(parts[1]))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case "#IFNDEF":
|
case "#IFNDEF":
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return nil, fmt.Errorf("#IFNDEF requires exactly one argument at %s:%d", fr.path, fr.line)
|
return nil, fmt.Errorf("#IFNDEF requires exactly one argument at %s:%d", currFrame.path, currFrame.line)
|
||||||
}
|
}
|
||||||
p.cond = append(p.cond, !p.defs.Defined(parts[1]))
|
p.cond = append(p.cond, !p.defs.Defined(parts[1]))
|
||||||
continue
|
continue
|
||||||
|
|
@ -135,43 +187,43 @@ func (p *preproc) run(root string) ([]Line, error) {
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case "#INCLUDE":
|
case "#INCLUDE":
|
||||||
if active {
|
if includeSource {
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
return nil, fmt.Errorf("#INCLUDE without path at %s:%d", fr.path, fr.line)
|
return nil, fmt.Errorf("#INCLUDE without path at %s:%d", currFrame.path, currFrame.line)
|
||||||
}
|
}
|
||||||
incPathRaw := parts[1]
|
incPathRaw := parts[1]
|
||||||
nextPath, err := resolveInclude(incPathRaw, fr.dir)
|
nextPath, err := resolveInclude(incPathRaw, currFrame.dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s at %s:%d", err, fr.path, fr.line)
|
return nil, fmt.Errorf("%s at %s:%d", err, currFrame.path, currFrame.line)
|
||||||
}
|
}
|
||||||
next, err := newFrame(nextPath)
|
next, err := newFrame(nextPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("include open failed %q: %w", nextPath, err)
|
return nil, fmt.Errorf("include open failed %q: %w", nextPath, err)
|
||||||
}
|
}
|
||||||
stack = append(stack, next)
|
frameStack = append(frameStack, next)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case "#PRINT":
|
case "#PRINT":
|
||||||
if active {
|
if includeSource {
|
||||||
msg := strings.TrimSpace(strings.TrimPrefix(trim, "#PRINT"))
|
msg := strings.TrimSpace(strings.TrimPrefix(trim, "#PRINT"))
|
||||||
msg = p.defs.ReplaceDefines(msg)
|
msg = p.defs.ReplaceDefines(msg)
|
||||||
fmt.Println(msg)
|
fmt.Println(msg)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case "#HALT":
|
case "#HALT":
|
||||||
if active {
|
if includeSource {
|
||||||
return nil, HaltError{}
|
return nil, HaltError{}
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case "#PRAGMA":
|
case "#PRAGMA":
|
||||||
if active && len(parts) >= 2 {
|
if includeSource && len(parts) >= 2 {
|
||||||
name := strings.ToUpper(parts[1])
|
name := strings.ToUpper(parts[1])
|
||||||
val := ""
|
val := ""
|
||||||
if len(parts) > 2 {
|
if len(parts) > 2 {
|
||||||
val = strings.Join(parts[2:], " ")
|
val = strings.Join(parts[2:], " ")
|
||||||
val = p.defs.ReplaceDefines(val)
|
val = p.defs.ReplaceDefines(val)
|
||||||
}
|
}
|
||||||
p.pragma[name] = val
|
p.pragma.AddPragma(name, val)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
|
|
@ -180,7 +232,7 @@ func (p *preproc) run(root string) ([]Line, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !active {
|
if !includeSource {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,22 +240,17 @@ func (p *preproc) run(root string) ([]Line, error) {
|
||||||
text := p.defs.ReplaceDefines(raw)
|
text := p.defs.ReplaceDefines(raw)
|
||||||
out = append(out, Line{
|
out = append(out, Line{
|
||||||
Text: text,
|
Text: text,
|
||||||
Filename: fr.path,
|
Filename: currFrame.path,
|
||||||
LineNo: fr.line,
|
LineNo: currFrame.line,
|
||||||
Tokens: strings.Fields(text),
|
Tokens: strings.Fields(text),
|
||||||
|
PragmaSetIndex: p.pragma.GetCurrentPragmaSetIndex(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check any scanner errors on remaining frames (if any)
|
|
||||||
for _, fr := range stack {
|
|
||||||
if err := fr.s.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *preproc) isActive() bool {
|
func (p *preproc) shouldIncludeSource() bool {
|
||||||
for _, v := range p.cond {
|
for _, v := range p.cond {
|
||||||
if !v {
|
if !v {
|
||||||
return false
|
return false
|
||||||
|
|
@ -237,26 +284,3 @@ func resolveInclude(spec string, curDir string) (string, error) {
|
||||||
}
|
}
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fieldsCollapsed splits on spaces and tabs, ignoring empties (i.e., collapses runs).
|
|
||||||
/*
|
|
||||||
func fieldsCollapsed(s string) []string {
|
|
||||||
out := make([]string, 0, 8)
|
|
||||||
field := strings.Builder{}
|
|
||||||
flush := func() {
|
|
||||||
if field.Len() > 0 {
|
|
||||||
out = append(out, field.String())
|
|
||||||
field.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, r := range s {
|
|
||||||
if r == ' ' || r == '\t' {
|
|
||||||
flush()
|
|
||||||
} else {
|
|
||||||
field.WriteRune(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
flush()
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue