1package ansiterm
2
3import (
4	"errors"
5	"log"
6	"os"
7)
8
9type AnsiParser struct {
10	currState          state
11	eventHandler       AnsiEventHandler
12	context            *ansiContext
13	csiEntry           state
14	csiParam           state
15	dcsEntry           state
16	escape             state
17	escapeIntermediate state
18	error              state
19	ground             state
20	oscString          state
21	stateMap           []state
22
23	logf func(string, ...interface{})
24}
25
26type Option func(*AnsiParser)
27
28func WithLogf(f func(string, ...interface{})) Option {
29	return func(ap *AnsiParser) {
30		ap.logf = f
31	}
32}
33
34func CreateParser(initialState string, evtHandler AnsiEventHandler, opts ...Option) *AnsiParser {
35	ap := &AnsiParser{
36		eventHandler: evtHandler,
37		context:      &ansiContext{},
38	}
39	for _, o := range opts {
40		o(ap)
41	}
42
43	if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" {
44		logFile, _ := os.Create("ansiParser.log")
45		logger := log.New(logFile, "", log.LstdFlags)
46		if ap.logf != nil {
47			l := ap.logf
48			ap.logf = func(s string, v ...interface{}) {
49				l(s, v...)
50				logger.Printf(s, v...)
51			}
52		} else {
53			ap.logf = logger.Printf
54		}
55	}
56
57	if ap.logf == nil {
58		ap.logf = func(string, ...interface{}) {}
59	}
60
61	ap.csiEntry = csiEntryState{baseState{name: "CsiEntry", parser: ap}}
62	ap.csiParam = csiParamState{baseState{name: "CsiParam", parser: ap}}
63	ap.dcsEntry = dcsEntryState{baseState{name: "DcsEntry", parser: ap}}
64	ap.escape = escapeState{baseState{name: "Escape", parser: ap}}
65	ap.escapeIntermediate = escapeIntermediateState{baseState{name: "EscapeIntermediate", parser: ap}}
66	ap.error = errorState{baseState{name: "Error", parser: ap}}
67	ap.ground = groundState{baseState{name: "Ground", parser: ap}}
68	ap.oscString = oscStringState{baseState{name: "OscString", parser: ap}}
69
70	ap.stateMap = []state{
71		ap.csiEntry,
72		ap.csiParam,
73		ap.dcsEntry,
74		ap.escape,
75		ap.escapeIntermediate,
76		ap.error,
77		ap.ground,
78		ap.oscString,
79	}
80
81	ap.currState = getState(initialState, ap.stateMap)
82
83	ap.logf("CreateParser: parser %p", ap)
84	return ap
85}
86
87func getState(name string, states []state) state {
88	for _, el := range states {
89		if el.Name() == name {
90			return el
91		}
92	}
93
94	return nil
95}
96
97func (ap *AnsiParser) Parse(bytes []byte) (int, error) {
98	for i, b := range bytes {
99		if err := ap.handle(b); err != nil {
100			return i, err
101		}
102	}
103
104	return len(bytes), ap.eventHandler.Flush()
105}
106
107func (ap *AnsiParser) handle(b byte) error {
108	ap.context.currentChar = b
109	newState, err := ap.currState.Handle(b)
110	if err != nil {
111		return err
112	}
113
114	if newState == nil {
115		ap.logf("WARNING: newState is nil")
116		return errors.New("New state of 'nil' is invalid.")
117	}
118
119	if newState != ap.currState {
120		if err := ap.changeState(newState); err != nil {
121			return err
122		}
123	}
124
125	return nil
126}
127
128func (ap *AnsiParser) changeState(newState state) error {
129	ap.logf("ChangeState %s --> %s", ap.currState.Name(), newState.Name())
130
131	// Exit old state
132	if err := ap.currState.Exit(); err != nil {
133		ap.logf("Exit state '%s' failed with : '%v'", ap.currState.Name(), err)
134		return err
135	}
136
137	// Perform transition action
138	if err := ap.currState.Transition(newState); err != nil {
139		ap.logf("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err)
140		return err
141	}
142
143	// Enter new state
144	if err := newState.Enter(); err != nil {
145		ap.logf("Enter state '%s' failed with: '%v'", newState.Name(), err)
146		return err
147	}
148
149	ap.currState = newState
150	return nil
151}
152