267 lines
6.6 KiB
Go
267 lines
6.6 KiB
Go
package preproc
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
|
|
//"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Line represents one post-processed source line and its provenance.
|
|
type Line struct {
|
|
Text string // post-preprocessor line text (after define replacement)
|
|
Filename string // file the line came from (after resolving includes)
|
|
LineNo int // 1-based line number in Filename
|
|
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.
|
|
type HaltError struct{}
|
|
|
|
func (HaltError) Error() string { return "preprocessor HALT" }
|
|
|
|
// PreProcess processes the given root source file and returns the flattened, define-expanded
|
|
// lines. Directive lines are not emitted.
|
|
func PreProcess(rootFilename string, reader ...FileReader) ([]Line, error) {
|
|
var r FileReader
|
|
if len(reader) > 0 && reader[0] != nil {
|
|
r = reader[0]
|
|
} else {
|
|
r = NewDiskFileReader()
|
|
}
|
|
pp := newPreproc(r)
|
|
return pp.run(rootFilename)
|
|
}
|
|
|
|
// -------------------- internal --------------------
|
|
|
|
type preproc struct {
|
|
defs *DefineList // from definelist.go
|
|
pragma *Pragma // pragma handler
|
|
cond []bool // conditional stack; a line is active if all are true
|
|
inAsm bool // true when inside ASM/ENDASM block
|
|
reader FileReader // file reader abstraction
|
|
}
|
|
|
|
func newPreproc(reader FileReader) *preproc {
|
|
return &preproc{
|
|
defs: NewDefineList(),
|
|
pragma: NewPragma(),
|
|
cond: []bool{},
|
|
inAsm: false,
|
|
reader: reader,
|
|
}
|
|
}
|
|
|
|
func (p *preproc) run(root string) ([]Line, error) {
|
|
var out []Line
|
|
|
|
type frame struct {
|
|
path string
|
|
lines []string // file contents split into lines (no newline)
|
|
idx int // next line index (0-based)
|
|
line int // last emitted line number (1-based)
|
|
dir string
|
|
}
|
|
|
|
newFrame := func(spec string, curDir string) (*frame, error) {
|
|
lines, absPath, err := p.reader.ReadLines(spec, curDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &frame{
|
|
path: absPath,
|
|
lines: lines,
|
|
idx: 0,
|
|
line: 0,
|
|
dir: filepath.Dir(absPath),
|
|
}, nil
|
|
}
|
|
|
|
var frameStack []*frame
|
|
|
|
frRoot, err := newFrame(root, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
frameStack = append(frameStack, frRoot)
|
|
|
|
for len(frameStack) > 0 {
|
|
currFrame := frameStack[len(frameStack)-1]
|
|
|
|
// if we've exhausted lines in this frame, pop it
|
|
if currFrame.idx >= len(currFrame.lines) {
|
|
frameStack = frameStack[:len(frameStack)-1]
|
|
continue
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
trim := strings.TrimSpace(raw)
|
|
isDirective := strings.HasPrefix(trim, "#")
|
|
|
|
if isDirective {
|
|
parts := strings.Fields(trim)
|
|
if len(parts) == 0 {
|
|
continue
|
|
}
|
|
switch strings.ToUpper(parts[0]) {
|
|
case "#DEFINE":
|
|
if includeSource && len(parts) >= 2 {
|
|
name := parts[1]
|
|
val := ""
|
|
if len(parts) > 2 {
|
|
start := 2
|
|
if parts[2] == "=" {
|
|
start = 3
|
|
}
|
|
if start < len(parts) {
|
|
val = strings.Join(parts[start:], " ")
|
|
val = p.defs.ReplaceDefines(val)
|
|
}
|
|
}
|
|
p.defs.Add(name, val)
|
|
}
|
|
continue
|
|
case "#UNDEF":
|
|
if includeSource && len(parts) >= 2 {
|
|
p.defs.Delete(parts[1])
|
|
}
|
|
continue
|
|
case "#IFDEF":
|
|
if len(parts) != 2 {
|
|
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]))
|
|
continue
|
|
|
|
case "#IFNDEF":
|
|
if len(parts) != 2 {
|
|
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]))
|
|
continue
|
|
case "#IFEND":
|
|
if len(p.cond) > 0 {
|
|
p.cond = p.cond[:len(p.cond)-1]
|
|
}
|
|
continue
|
|
case "#INCLUDE":
|
|
if includeSource {
|
|
if len(parts) < 2 {
|
|
return nil, fmt.Errorf("#INCLUDE without path at %s:%d", currFrame.path, currFrame.line)
|
|
}
|
|
incPathRaw := parts[1]
|
|
next, err := newFrame(incPathRaw, currFrame.dir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("include failed at %s:%d: %w", currFrame.path, currFrame.line, err)
|
|
}
|
|
frameStack = append(frameStack, next)
|
|
}
|
|
continue
|
|
case "#PRINT":
|
|
if includeSource {
|
|
msg := strings.TrimSpace(strings.TrimPrefix(trim, "#PRINT"))
|
|
msg = p.defs.ReplaceDefines(msg)
|
|
fmt.Println(msg)
|
|
}
|
|
continue
|
|
case "#HALT":
|
|
if includeSource {
|
|
return out, HaltError{}
|
|
}
|
|
continue
|
|
case "#PRAGMA":
|
|
if includeSource && len(parts) >= 2 {
|
|
name := strings.ToUpper(parts[1])
|
|
val := ""
|
|
if len(parts) > 2 {
|
|
val = strings.Join(parts[2:], " ")
|
|
val = p.defs.ReplaceDefines(val)
|
|
}
|
|
p.pragma.AddPragma(name, val)
|
|
}
|
|
continue
|
|
default:
|
|
// Unknown directive: drop (do not emit)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if !includeSource {
|
|
continue
|
|
}
|
|
|
|
// Non-directive: expand defines and emit.
|
|
text := p.defs.ReplaceDefines(raw)
|
|
out = append(out, Line{
|
|
Text: text,
|
|
Filename: currFrame.path,
|
|
LineNo: currFrame.line,
|
|
Tokens: strings.Fields(text),
|
|
PragmaSetIndex: p.pragma.GetCurrentPragmaSetIndex(),
|
|
})
|
|
}
|
|
|
|
if len(p.cond) > 0 {
|
|
return nil, fmt.Errorf("unbalanced conditionals: %d unclosed #IFDEF/#IFNDEF directives", len(p.cond))
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func (p *preproc) shouldIncludeSource() bool {
|
|
for _, v := range p.cond {
|
|
if !v {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|