You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

502 lines
10 KiB

  1. package parser
  2. import (
  3. "bufio"
  4. "container/list"
  5. "fmt"
  6. "io"
  7. "regexp"
  8. )
  9. const (
  10. tokEOF = -(iota + 1)
  11. tokDoctype
  12. tokComment
  13. tokIndent
  14. tokOutdent
  15. tokBlank
  16. tokId
  17. tokClassName
  18. tokTag
  19. tokText
  20. tokAttribute
  21. tokIf
  22. tokElse
  23. tokEach
  24. tokAssignment
  25. tokImport
  26. tokNamedBlock
  27. tokExtends
  28. tokMixin
  29. tokMixinCall
  30. )
  31. const (
  32. scnNewLine = iota
  33. scnLine
  34. scnEOF
  35. )
  36. type scanner struct {
  37. reader *bufio.Reader
  38. indentStack *list.List
  39. stash *list.List
  40. state int32
  41. buffer string
  42. line int
  43. col int
  44. lastTokenLine int
  45. lastTokenCol int
  46. lastTokenSize int
  47. readRaw bool
  48. }
  49. type token struct {
  50. Kind rune
  51. Value string
  52. Data map[string]string
  53. }
  54. func newScanner(r io.Reader) *scanner {
  55. s := new(scanner)
  56. s.reader = bufio.NewReader(r)
  57. s.indentStack = list.New()
  58. s.stash = list.New()
  59. s.state = scnNewLine
  60. s.line = -1
  61. s.col = 0
  62. return s
  63. }
  64. func (s *scanner) Pos() SourcePosition {
  65. return SourcePosition{s.lastTokenLine + 1, s.lastTokenCol + 1, s.lastTokenSize, ""}
  66. }
  67. // Returns next token found in buffer
  68. func (s *scanner) Next() *token {
  69. if s.readRaw {
  70. s.readRaw = false
  71. return s.NextRaw()
  72. }
  73. s.ensureBuffer()
  74. if stashed := s.stash.Front(); stashed != nil {
  75. tok := stashed.Value.(*token)
  76. s.stash.Remove(stashed)
  77. return tok
  78. }
  79. switch s.state {
  80. case scnEOF:
  81. if outdent := s.indentStack.Back(); outdent != nil {
  82. s.indentStack.Remove(outdent)
  83. return &token{tokOutdent, "", nil}
  84. }
  85. return &token{tokEOF, "", nil}
  86. case scnNewLine:
  87. s.state = scnLine
  88. if tok := s.scanIndent(); tok != nil {
  89. return tok
  90. }
  91. return s.Next()
  92. case scnLine:
  93. if tok := s.scanMixin(); tok != nil {
  94. return tok
  95. }
  96. if tok := s.scanMixinCall(); tok != nil {
  97. return tok
  98. }
  99. if tok := s.scanDoctype(); tok != nil {
  100. return tok
  101. }
  102. if tok := s.scanCondition(); tok != nil {
  103. return tok
  104. }
  105. if tok := s.scanEach(); tok != nil {
  106. return tok
  107. }
  108. if tok := s.scanImport(); tok != nil {
  109. return tok
  110. }
  111. if tok := s.scanExtends(); tok != nil {
  112. return tok
  113. }
  114. if tok := s.scanBlock(); tok != nil {
  115. return tok
  116. }
  117. if tok := s.scanAssignment(); tok != nil {
  118. return tok
  119. }
  120. if tok := s.scanTag(); tok != nil {
  121. return tok
  122. }
  123. if tok := s.scanId(); tok != nil {
  124. return tok
  125. }
  126. if tok := s.scanClassName(); tok != nil {
  127. return tok
  128. }
  129. if tok := s.scanAttribute(); tok != nil {
  130. return tok
  131. }
  132. if tok := s.scanComment(); tok != nil {
  133. return tok
  134. }
  135. if tok := s.scanText(); tok != nil {
  136. return tok
  137. }
  138. }
  139. return nil
  140. }
  141. func (s *scanner) NextRaw() *token {
  142. result := ""
  143. level := 0
  144. for {
  145. s.ensureBuffer()
  146. switch s.state {
  147. case scnEOF:
  148. return &token{tokText, result, map[string]string{"Mode": "raw"}}
  149. case scnNewLine:
  150. s.state = scnLine
  151. if tok := s.scanIndent(); tok != nil {
  152. if tok.Kind == tokIndent {
  153. level++
  154. } else if tok.Kind == tokOutdent {
  155. level--
  156. } else {
  157. result = result + "\n"
  158. continue
  159. }
  160. if level < 0 {
  161. s.stash.PushBack(&token{tokOutdent, "", nil})
  162. if len(result) > 0 && result[len(result)-1] == '\n' {
  163. result = result[:len(result)-1]
  164. }
  165. return &token{tokText, result, map[string]string{"Mode": "raw"}}
  166. }
  167. }
  168. case scnLine:
  169. if len(result) > 0 {
  170. result = result + "\n"
  171. }
  172. for i := 0; i < level; i++ {
  173. result += "\t"
  174. }
  175. result = result + s.buffer
  176. s.consume(len(s.buffer))
  177. }
  178. }
  179. return nil
  180. }
  181. var rgxIndent = regexp.MustCompile(`^(\s+)`)
  182. func (s *scanner) scanIndent() *token {
  183. if len(s.buffer) == 0 {
  184. return &token{tokBlank, "", nil}
  185. }
  186. var head *list.Element
  187. for head = s.indentStack.Front(); head != nil; head = head.Next() {
  188. value := head.Value.(*regexp.Regexp)
  189. if match := value.FindString(s.buffer); len(match) != 0 {
  190. s.consume(len(match))
  191. } else {
  192. break
  193. }
  194. }
  195. newIndent := rgxIndent.FindString(s.buffer)
  196. if len(newIndent) != 0 && head == nil {
  197. s.indentStack.PushBack(regexp.MustCompile(regexp.QuoteMeta(newIndent)))
  198. s.consume(len(newIndent))
  199. return &token{tokIndent, newIndent, nil}
  200. }
  201. if len(newIndent) == 0 && head != nil {
  202. for head != nil {
  203. next := head.Next()
  204. s.indentStack.Remove(head)
  205. if next == nil {
  206. return &token{tokOutdent, "", nil}
  207. } else {
  208. s.stash.PushBack(&token{tokOutdent, "", nil})
  209. }
  210. head = next
  211. }
  212. }
  213. if len(newIndent) != 0 && head != nil {
  214. panic("Mismatching indentation. Please use a coherent indent schema.")
  215. }
  216. return nil
  217. }
  218. var rgxDoctype = regexp.MustCompile(`^(!!!|doctype)\s*(.*)`)
  219. func (s *scanner) scanDoctype() *token {
  220. if sm := rgxDoctype.FindStringSubmatch(s.buffer); len(sm) != 0 {
  221. if len(sm[2]) == 0 {
  222. sm[2] = "html"
  223. }
  224. s.consume(len(sm[0]))
  225. return &token{tokDoctype, sm[2], nil}
  226. }
  227. return nil
  228. }
  229. var rgxIf = regexp.MustCompile(`^if\s+(.+)$`)
  230. var rgxElse = regexp.MustCompile(`^else\s*`)
  231. func (s *scanner) scanCondition() *token {
  232. if sm := rgxIf.FindStringSubmatch(s.buffer); len(sm) != 0 {
  233. s.consume(len(sm[0]))
  234. return &token{tokIf, sm[1], nil}
  235. }
  236. if sm := rgxElse.FindStringSubmatch(s.buffer); len(sm) != 0 {
  237. s.consume(len(sm[0]))
  238. return &token{tokElse, "", nil}
  239. }
  240. return nil
  241. }
  242. var rgxEach = regexp.MustCompile(`^each\s+(\$[\w0-9\-_]*)(?:\s*,\s*(\$[\w0-9\-_]*))?\s+in\s+(.+)$`)
  243. func (s *scanner) scanEach() *token {
  244. if sm := rgxEach.FindStringSubmatch(s.buffer); len(sm) != 0 {
  245. s.consume(len(sm[0]))
  246. return &token{tokEach, sm[3], map[string]string{"X": sm[1], "Y": sm[2]}}
  247. }
  248. return nil
  249. }
  250. var rgxAssignment = regexp.MustCompile(`^(\$[\w0-9\-_]*)?\s*=\s*(.+)$`)
  251. func (s *scanner) scanAssignment() *token {
  252. if sm := rgxAssignment.FindStringSubmatch(s.buffer); len(sm) != 0 {
  253. s.consume(len(sm[0]))
  254. return &token{tokAssignment, sm[2], map[string]string{"X": sm[1]}}
  255. }
  256. return nil
  257. }
  258. var rgxComment = regexp.MustCompile(`^\/\/(-)?\s*(.*)$`)
  259. func (s *scanner) scanComment() *token {
  260. if sm := rgxComment.FindStringSubmatch(s.buffer); len(sm) != 0 {
  261. mode := "embed"
  262. if len(sm[1]) != 0 {
  263. mode = "silent"
  264. }
  265. s.consume(len(sm[0]))
  266. return &token{tokComment, sm[2], map[string]string{"Mode": mode}}
  267. }
  268. return nil
  269. }
  270. var rgxId = regexp.MustCompile(`^#([\w-]+)(?:\s*\?\s*(.*)$)?`)
  271. func (s *scanner) scanId() *token {
  272. if sm := rgxId.FindStringSubmatch(s.buffer); len(sm) != 0 {
  273. s.consume(len(sm[0]))
  274. return &token{tokId, sm[1], map[string]string{"Condition": sm[2]}}
  275. }
  276. return nil
  277. }
  278. var rgxClassName = regexp.MustCompile(`^\.([\w-]+)(?:\s*\?\s*(.*)$)?`)
  279. func (s *scanner) scanClassName() *token {
  280. if sm := rgxClassName.FindStringSubmatch(s.buffer); len(sm) != 0 {
  281. s.consume(len(sm[0]))
  282. return &token{tokClassName, sm[1], map[string]string{"Condition": sm[2]}}
  283. }
  284. return nil
  285. }
  286. var rgxAttribute = regexp.MustCompile(`^\[([\w\-]+)\s*(?:=\s*(\"([^\"\\]*)\"|([^\]]+)))?\](?:\s*\?\s*(.*)$)?`)
  287. func (s *scanner) scanAttribute() *token {
  288. if sm := rgxAttribute.FindStringSubmatch(s.buffer); len(sm) != 0 {
  289. s.consume(len(sm[0]))
  290. if len(sm[3]) != 0 || sm[2] == "" {
  291. return &token{tokAttribute, sm[1], map[string]string{"Content": sm[3], "Mode": "raw", "Condition": sm[5]}}
  292. }
  293. return &token{tokAttribute, sm[1], map[string]string{"Content": sm[4], "Mode": "expression", "Condition": sm[5]}}
  294. }
  295. return nil
  296. }
  297. var rgxImport = regexp.MustCompile(`^import\s+([0-9a-zA-Z_\-\. \/]*)$`)
  298. func (s *scanner) scanImport() *token {
  299. if sm := rgxImport.FindStringSubmatch(s.buffer); len(sm) != 0 {
  300. s.consume(len(sm[0]))
  301. return &token{tokImport, sm[1], nil}
  302. }
  303. return nil
  304. }
  305. var rgxExtends = regexp.MustCompile(`^extends\s+([0-9a-zA-Z_\-\. \/]*)$`)
  306. func (s *scanner) scanExtends() *token {
  307. if sm := rgxExtends.FindStringSubmatch(s.buffer); len(sm) != 0 {
  308. s.consume(len(sm[0]))
  309. return &token{tokExtends, sm[1], nil}
  310. }
  311. return nil
  312. }
  313. var rgxBlock = regexp.MustCompile(`^block\s+(?:(append|prepend)\s+)?([0-9a-zA-Z_\-\. \/]*)$`)
  314. func (s *scanner) scanBlock() *token {
  315. if sm := rgxBlock.FindStringSubmatch(s.buffer); len(sm) != 0 {
  316. s.consume(len(sm[0]))
  317. return &token{tokNamedBlock, sm[2], map[string]string{"Modifier": sm[1]}}
  318. }
  319. return nil
  320. }
  321. var rgxTag = regexp.MustCompile(`^(\w[-:\w]*)`)
  322. func (s *scanner) scanTag() *token {
  323. if sm := rgxTag.FindStringSubmatch(s.buffer); len(sm) != 0 {
  324. s.consume(len(sm[0]))
  325. return &token{tokTag, sm[1], nil}
  326. }
  327. return nil
  328. }
  329. var rgxMixin = regexp.MustCompile(`^mixin ([a-zA-Z_]+\w*)(\(((\$\w*(,\s)?)*)\))?$`)
  330. func (s *scanner) scanMixin() *token {
  331. if sm := rgxMixin.FindStringSubmatch(s.buffer); len(sm) != 0 {
  332. s.consume(len(sm[0]))
  333. return &token{tokMixin, sm[1], map[string]string{"Args": sm[3]}}
  334. }
  335. return nil
  336. }
  337. var rgxMixinCall = regexp.MustCompile(`^\+([A-Za-z_]+\w*)(\((.+(,\s)?)*\))?$`)
  338. func (s *scanner) scanMixinCall() *token {
  339. if sm := rgxMixinCall.FindStringSubmatch(s.buffer); len(sm) != 0 {
  340. s.consume(len(sm[0]))
  341. return &token{tokMixinCall, sm[1], map[string]string{"Args": sm[3]}}
  342. }
  343. return nil
  344. }
  345. var rgxText = regexp.MustCompile(`^(\|)? ?(.*)$`)
  346. func (s *scanner) scanText() *token {
  347. if sm := rgxText.FindStringSubmatch(s.buffer); len(sm) != 0 {
  348. s.consume(len(sm[0]))
  349. mode := "inline"
  350. if sm[1] == "|" {
  351. mode = "piped"
  352. }
  353. return &token{tokText, sm[2], map[string]string{"Mode": mode}}
  354. }
  355. return nil
  356. }
  357. // Moves position forward, and removes beginning of s.buffer (len bytes)
  358. func (s *scanner) consume(runes int) {
  359. if len(s.buffer) < runes {
  360. panic(fmt.Sprintf("Unable to consume %d runes from buffer.", runes))
  361. }
  362. s.lastTokenLine = s.line
  363. s.lastTokenCol = s.col
  364. s.lastTokenSize = runes
  365. s.buffer = s.buffer[runes:]
  366. s.col += runes
  367. }
  368. // Reads string into s.buffer
  369. func (s *scanner) ensureBuffer() {
  370. if len(s.buffer) > 0 {
  371. return
  372. }
  373. buf, err := s.reader.ReadString('\n')
  374. if err != nil && err != io.EOF {
  375. panic(err)
  376. } else if err != nil && len(buf) == 0 {
  377. s.state = scnEOF
  378. } else {
  379. // endline "LF only" or "\n" use Unix, Linux, modern MacOS X, FreeBSD, BeOS, RISC OS
  380. if buf[len(buf)-1] == '\n' {
  381. buf = buf[:len(buf)-1]
  382. }
  383. // endline "CR+LF" or "\r\n" use internet protocols, DEC RT-11, Windows, CP/M, MS-DOS, OS/2, Symbian OS
  384. if len(buf) > 0 && buf[len(buf)-1] == '\r' {
  385. buf = buf[:len(buf)-1]
  386. }
  387. s.state = scnNewLine
  388. s.buffer = buf
  389. s.line += 1
  390. s.col = 0
  391. }
  392. }