summaryrefslogtreecommitdiff
path: root/vendor/github.com/Azure/go-ansiterm/parser.go
blob: 03cec7ada62a548179a547f5c1d66eb9f0593a8f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package ansiterm

import (
	"errors"
	"log"
	"os"
)

type AnsiParser struct {
	currState          state
	eventHandler       AnsiEventHandler
	context            *ansiContext
	csiEntry           state
	csiParam           state
	dcsEntry           state
	escape             state
	escapeIntermediate state
	error              state
	ground             state
	oscString          state
	stateMap           []state

	logf func(string, ...interface{})
}

type Option func(*AnsiParser)

func WithLogf(f func(string, ...interface{})) Option {
	return func(ap *AnsiParser) {
		ap.logf = f
	}
}

func CreateParser(initialState string, evtHandler AnsiEventHandler, opts ...Option) *AnsiParser {
	ap := &AnsiParser{
		eventHandler: evtHandler,
		context:      &ansiContext{},
	}
	for _, o := range opts {
		o(ap)
	}

	if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" {
		logFile, _ := os.Create("ansiParser.log")
		logger := log.New(logFile, "", log.LstdFlags)
		if ap.logf != nil {
			l := ap.logf
			ap.logf = func(s string, v ...interface{}) {
				l(s, v...)
				logger.Printf(s, v...)
			}
		} else {
			ap.logf = logger.Printf
		}
	}

	if ap.logf == nil {
		ap.logf = func(string, ...interface{}) {}
	}

	ap.csiEntry = csiEntryState{baseState{name: "CsiEntry", parser: ap}}
	ap.csiParam = csiParamState{baseState{name: "CsiParam", parser: ap}}
	ap.dcsEntry = dcsEntryState{baseState{name: "DcsEntry", parser: ap}}
	ap.escape = escapeState{baseState{name: "Escape", parser: ap}}
	ap.escapeIntermediate = escapeIntermediateState{baseState{name: "EscapeIntermediate", parser: ap}}
	ap.error = errorState{baseState{name: "Error", parser: ap}}
	ap.ground = groundState{baseState{name: "Ground", parser: ap}}
	ap.oscString = oscStringState{baseState{name: "OscString", parser: ap}}

	ap.stateMap = []state{
		ap.csiEntry,
		ap.csiParam,
		ap.dcsEntry,
		ap.escape,
		ap.escapeIntermediate,
		ap.error,
		ap.ground,
		ap.oscString,
	}

	ap.currState = getState(initialState, ap.stateMap)

	ap.logf("CreateParser: parser %p", ap)
	return ap
}

func getState(name string, states []state) state {
	for _, el := range states {
		if el.Name() == name {
			return el
		}
	}

	return nil
}

func (ap *AnsiParser) Parse(bytes []byte) (int, error) {
	for i, b := range bytes {
		if err := ap.handle(b); err != nil {
			return i, err
		}
	}

	return len(bytes), ap.eventHandler.Flush()
}

func (ap *AnsiParser) handle(b byte) error {
	ap.context.currentChar = b
	newState, err := ap.currState.Handle(b)
	if err != nil {
		return err
	}

	if newState == nil {
		ap.logf("WARNING: newState is nil")
		return errors.New("New state of 'nil' is invalid.")
	}

	if newState != ap.currState {
		if err := ap.changeState(newState); err != nil {
			return err
		}
	}

	return nil
}

func (ap *AnsiParser) changeState(newState state) error {
	ap.logf("ChangeState %s --> %s", ap.currState.Name(), newState.Name())

	// Exit old state
	if err := ap.currState.Exit(); err != nil {
		ap.logf("Exit state '%s' failed with : '%v'", ap.currState.Name(), err)
		return err
	}

	// Perform transition action
	if err := ap.currState.Transition(newState); err != nil {
		ap.logf("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err)
		return err
	}

	// Enter new state
	if err := newState.Enter(); err != nil {
		ap.logf("Enter state '%s' failed with: '%v'", newState.Name(), err)
		return err
	}

	ap.currState = newState
	return nil
}