github.com/xyproto/orbiton/v2@v2.65.12-0.20240516144430-e10a419274ec/quotestate.go (about) 1 package main 2 3 import ( 4 "errors" 5 6 "github.com/xyproto/mode" 7 ) 8 9 // QuoteState keeps track of if we're within a multi-line comment, single quotes, double quotes or multi-line quotes. 10 // Single line comments are not kept track of in the same way, they can be detected just by checking the current line. 11 // If one of the ints are > 0, the other ints should not be added to. 12 // MultiLine comments (/* ... */) are special. 13 // This could be a flag int instead 14 type QuoteState struct { 15 singleLineCommentMarker string 16 singleLineCommentMarkerRunes []rune 17 doubleQuote int 18 backtick int 19 mode mode.Mode 20 braCount int // square bracket count 21 parCount int // parenthesis count 22 singleQuote int 23 firstRuneInSingleLineCommentMarker rune 24 lastRuneInSingleLineCommentMarker rune 25 startedMultiLineComment bool 26 stoppedMultiLineComment bool 27 containsMultiLineComments bool 28 startedMultiLineString bool 29 hasSingleLineComment bool 30 multiLineComment bool 31 ignoreSingleQuotes bool 32 } 33 34 // NewQuoteState takes a singleLineCommentMarker (such as "//" or "#") and returns a pointer to a new QuoteState struct 35 func NewQuoteState(singleLineCommentMarker string, m mode.Mode, ignoreSingleQuotes bool) (*QuoteState, error) { 36 var q QuoteState 37 q.singleLineCommentMarker = singleLineCommentMarker 38 q.singleLineCommentMarkerRunes = []rune(singleLineCommentMarker) 39 lensr := len(q.singleLineCommentMarkerRunes) 40 if lensr == 0 { 41 return nil, errors.New("single line comment marker is empty") 42 } 43 q.firstRuneInSingleLineCommentMarker = q.singleLineCommentMarkerRunes[0] 44 q.lastRuneInSingleLineCommentMarker = q.singleLineCommentMarkerRunes[lensr-1] 45 q.mode = m 46 q.ignoreSingleQuotes = ignoreSingleQuotes 47 return &q, nil 48 } 49 50 // None returns true if we're not within ', "", `, /* ... */ or a single-line quote right now 51 func (q *QuoteState) None() bool { 52 return q.singleQuote == 0 && q.doubleQuote == 0 && q.backtick == 0 && !q.multiLineComment && !q.hasSingleLineComment 53 } 54 55 // ProcessRune is for processing single runes 56 func (q *QuoteState) ProcessRune(r, prevRune, prevPrevRune rune) { 57 switch r { 58 case '`': 59 if q.None() { 60 q.backtick++ 61 q.startedMultiLineString = true 62 } else { 63 q.backtick-- 64 if q.backtick < 0 { 65 q.backtick = 0 66 } 67 } 68 case '"': 69 if prevPrevRune == '"' && prevRune == '"' { 70 q.startedMultiLineString = q.None() 71 } else if prevRune != '\\' { 72 if q.None() { 73 q.doubleQuote++ 74 } else { 75 q.doubleQuote-- 76 if q.doubleQuote < 0 { 77 q.doubleQuote = 0 78 } 79 } 80 } 81 case '\'': 82 if prevRune != '\\' { 83 if q.ignoreSingleQuotes || q.mode == mode.Lisp || q.mode == mode.Clojure { 84 return 85 } 86 if q.None() { 87 q.singleQuote++ 88 } else { 89 q.singleQuote-- 90 if q.singleQuote < 0 { 91 q.singleQuote = 0 92 } 93 } 94 } 95 case '*': // support multi-line comments 96 if q.mode != mode.Shell && q.mode != mode.Make && q.mode != mode.Just && q.firstRuneInSingleLineCommentMarker != '#' && prevRune == '/' && (prevPrevRune == '\n' || prevPrevRune == ' ' || prevPrevRune == '\t') && q.None() { 97 // C-style 98 q.multiLineComment = true 99 q.startedMultiLineComment = true 100 } else if (q.mode == mode.StandardML || q.mode == mode.OCaml || q.mode == mode.Haskell) && prevRune == '(' && q.None() { 101 q.parCount-- // Not a parenthesis start after all, but the start of a multi-line comment 102 q.multiLineComment = true 103 q.startedMultiLineComment = true 104 } else if (q.mode == mode.Elm || q.mode == mode.Haskell) && prevRune == '{' && q.None() { 105 q.parCount-- // Not a parenthesis start after all, but the start of a multi-line comment 106 q.multiLineComment = true 107 q.startedMultiLineComment = true 108 } 109 case '{': 110 if q.mode == mode.ObjectPascal && q.None() { 111 q.multiLineComment = true 112 q.startedMultiLineComment = true 113 } 114 case '-': // support for HTML-style and XML-style multi-line comments 115 if q.mode != mode.Shell && q.mode != mode.Make && q.mode != mode.Just && prevRune == '!' && prevPrevRune == '<' && q.None() { 116 q.multiLineComment = true 117 q.startedMultiLineComment = true 118 } else if (q.mode == mode.Elm || q.mode == mode.Haskell) && prevRune == '{' { 119 q.multiLineComment = true 120 q.startedMultiLineComment = true 121 } 122 case q.lastRuneInSingleLineCommentMarker: 123 // TODO: Simplify by checking q.None() first, and assuming that the len of the marker is > 1 if it's not 1 since it's not 0 124 if !q.multiLineComment && !q.hasSingleLineComment && !q.startedMultiLineString && prevPrevRune != ':' && q.doubleQuote == 0 && q.singleQuote == 0 && q.backtick == 0 { 125 switch { 126 case len(q.singleLineCommentMarkerRunes) == 1: 127 fallthrough 128 case q.mode != mode.Shell && q.mode != mode.Make && q.mode != mode.Just && len(q.singleLineCommentMarkerRunes) > 1 && prevRune == q.firstRuneInSingleLineCommentMarker: 129 q.hasSingleLineComment = true 130 q.startedMultiLineString = false 131 q.stoppedMultiLineComment = false 132 q.multiLineComment = false 133 q.backtick = 0 134 q.doubleQuote = 0 135 q.singleQuote = 0 136 // We're in a single line comment, nothing more to do for this line 137 return 138 } 139 } 140 if r != '/' { 141 break 142 } 143 // r == '/' 144 fallthrough 145 case '/': // support C-style multi-line comments 146 if q.mode != mode.Shell && q.mode != mode.Make && q.mode != mode.Just && q.firstRuneInSingleLineCommentMarker != '#' && prevRune == '*' { 147 q.stoppedMultiLineComment = true 148 q.multiLineComment = false 149 if q.startedMultiLineComment { 150 q.containsMultiLineComments = true 151 } 152 } 153 case '(': 154 if q.None() { 155 q.parCount++ 156 } 157 case ';': 158 if q.mode == mode.Clojure && prevRune == ';' { 159 q.hasSingleLineComment = true 160 } 161 case ')': 162 if (q.mode == mode.StandardML || q.mode == mode.OCaml || q.mode == mode.Haskell) && prevRune == '*' { 163 q.stoppedMultiLineComment = true 164 q.multiLineComment = false 165 if q.startedMultiLineComment { 166 q.containsMultiLineComments = true 167 } 168 } else if q.None() { 169 q.parCount-- 170 } 171 case '}': 172 if (q.mode == mode.Elm || q.mode == mode.Haskell) && prevRune == '-' { 173 q.stoppedMultiLineComment = true 174 q.multiLineComment = false 175 if q.startedMultiLineComment { 176 q.containsMultiLineComments = true 177 } 178 } else if q.mode == mode.ObjectPascal { 179 q.stoppedMultiLineComment = true 180 q.multiLineComment = false 181 if q.startedMultiLineComment { 182 q.containsMultiLineComments = true 183 } 184 } 185 case '[': 186 if q.None() { 187 q.braCount++ 188 } 189 case ']': 190 if q.None() { 191 q.braCount-- 192 } 193 case '>': // support HTML-style and XML-style multi-line comments 194 if prevRune == '-' && (q.mode == mode.HTML || q.mode == mode.XML) { 195 q.stoppedMultiLineComment = true 196 q.multiLineComment = false 197 if q.startedMultiLineComment { 198 q.containsMultiLineComments = true 199 } 200 } 201 } 202 } 203 204 // Process takes a line of text and modifies the current quote state accordingly, 205 // depending on which runes are encountered. 206 func (q *QuoteState) Process(line string) (rune, rune) { 207 q.hasSingleLineComment = false 208 q.startedMultiLineString = false 209 q.stoppedMultiLineComment = false 210 q.containsMultiLineComments = false 211 prevRune := '\n' 212 prevPrevRune := '\n' 213 for _, r := range line { 214 q.ProcessRune(r, prevRune, prevPrevRune) 215 prevPrevRune = prevRune 216 prevRune = r 217 } 218 return prevRune, prevPrevRune 219 } 220 221 // ParBraCount will count the parenthesis and square brackets for a single line 222 // while skipping comments and multi-line strings 223 // and without modifying the QuoteState. 224 func (q *QuoteState) ParBraCount(line string) (int, int) { 225 qCopy := *q 226 qCopy.parCount = 0 227 qCopy.braCount = 0 228 qCopy.Process(line) 229 return qCopy.parCount, qCopy.braCount 230 }