224 lines
5.3 KiB
Go
224 lines
5.3 KiB
Go
|
package parser
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"strings"
|
||
|
|
||
|
"go.arsenm.dev/amu/ast"
|
||
|
"go.arsenm.dev/amu/scanner"
|
||
|
)
|
||
|
|
||
|
// parsePara attempts to parse a paragraph until untilEOLAmt
|
||
|
// newlines are encountered
|
||
|
func (p *Parser) parsePara(untilEOLAmt int) *ast.Para {
|
||
|
// Create new empty para
|
||
|
para := &ast.Para{}
|
||
|
|
||
|
parseLoop:
|
||
|
for {
|
||
|
// Scan token
|
||
|
tok, lit := p.scan()
|
||
|
|
||
|
switch tok {
|
||
|
case scanner.WS:
|
||
|
// Add whitespace to para
|
||
|
para.Fragments = append(para.Fragments, ast.ParaFragment{Whitespace: &lit})
|
||
|
case scanner.PUNCT:
|
||
|
if lit == "[" {
|
||
|
// Attempt to parse link
|
||
|
link, _ := p.parseLink()
|
||
|
// If successful
|
||
|
if link != nil {
|
||
|
// Add link to para
|
||
|
para.Fragments = append(para.Fragments, ast.ParaFragment{Link: link})
|
||
|
// Continue to next token
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
// Add punctuation to para
|
||
|
para.Fragments = append(para.Fragments, ast.ParaFragment{Punct: &lit})
|
||
|
case scanner.WORD:
|
||
|
if strings.HasPrefix(lit, "+") {
|
||
|
// Attempt to parse function
|
||
|
function := p.parseFunc(tok, lit)
|
||
|
// If successful
|
||
|
if function != nil {
|
||
|
// Add function to para
|
||
|
para.Fragments = append(para.Fragments, ast.ParaFragment{Func: function})
|
||
|
// Continue to next token
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
// Add word to para
|
||
|
para.Fragments = append(para.Fragments, ast.ParaFragment{Word: &lit})
|
||
|
case scanner.FORMAT:
|
||
|
// Create new nil slice of ast.FormatType
|
||
|
var types []ast.FormatType
|
||
|
if strings.HasPrefix(lit, "_") {
|
||
|
// Remove leading and trailing "_"
|
||
|
lit = strings.Trim(lit, "_")
|
||
|
// Add italic format to slice
|
||
|
types = append(types, ast.FormatTypeItalic)
|
||
|
}
|
||
|
if strings.HasPrefix(lit, "*") {
|
||
|
// Remove leading and trailing "*"
|
||
|
lit = strings.Trim(lit, "*")
|
||
|
// Add bold format to slice
|
||
|
types = append(types, ast.FormatTypeBold)
|
||
|
}
|
||
|
if strings.HasPrefix(lit, "$") {
|
||
|
// Remove leading and trailing "$"
|
||
|
lit = strings.Trim(lit, "$")
|
||
|
// Add math format to slice
|
||
|
types = append(types, ast.FormatTypeMath)
|
||
|
}
|
||
|
if strings.HasPrefix(lit, "`") {
|
||
|
// Remove leading and trailing "`"
|
||
|
lit = strings.Trim(lit, "`")
|
||
|
// Add code format to slice
|
||
|
types = []ast.FormatType{ast.FormatTypeCode}
|
||
|
}
|
||
|
if strings.HasPrefix(lit, "~") {
|
||
|
// Remove leading and trailing "~"
|
||
|
lit = strings.Trim(lit, "~")
|
||
|
// Add strike format to slice
|
||
|
types = []ast.FormatType{ast.FormatTypeStrike}
|
||
|
}
|
||
|
// Add format to para
|
||
|
para.Fragments = append(para.Fragments, ast.ParaFragment{Format: &ast.Format{
|
||
|
Types: types,
|
||
|
Text: lit,
|
||
|
}})
|
||
|
case scanner.EOL:
|
||
|
// If untilEOLAmt or more newlines encountered
|
||
|
if strings.Count(lit, "\n") >= untilEOLAmt {
|
||
|
// Stop parsing
|
||
|
break parseLoop
|
||
|
}
|
||
|
// Add EOL to para
|
||
|
para.Fragments = append(para.Fragments, ast.ParaFragment{Whitespace: &lit})
|
||
|
case scanner.EOF:
|
||
|
// Stop parsing
|
||
|
break parseLoop
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If nothing in para
|
||
|
if len(para.Fragments) == 0 {
|
||
|
// Return nothing
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return para
|
||
|
}
|
||
|
|
||
|
// parseFunc appempts to parse a function call
|
||
|
func (p *Parser) parseFunc(tok scanner.Token, lit string) *ast.Func {
|
||
|
// Create new function
|
||
|
function := &ast.Func{}
|
||
|
|
||
|
// If the token is not a word or does not have a prefix of "+"
|
||
|
if tok != scanner.WORD || !strings.HasPrefix(lit, "+") {
|
||
|
// Return nil as this is an invalid function call
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Set function name to literal, trimming "+" prefix
|
||
|
function.Name = strings.TrimPrefix(lit, "+")
|
||
|
|
||
|
// Scan token
|
||
|
tok, lit = p.scan()
|
||
|
|
||
|
// If token is not punctuatuion or is not "["
|
||
|
if tok != scanner.PUNCT || lit != "[" {
|
||
|
// Unscan token
|
||
|
p.unscan()
|
||
|
// Return nil as this is an invalid function call
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Parse arguments
|
||
|
function.Args = p.parseArgs()
|
||
|
|
||
|
return function
|
||
|
}
|
||
|
|
||
|
// Attempt to parse link
|
||
|
func (p *Parser) parseLink() (*ast.Link, bool) {
|
||
|
// Create new link
|
||
|
link := &ast.Link{}
|
||
|
|
||
|
// Initialize buffers for link properties
|
||
|
textBuf := &bytes.Buffer{}
|
||
|
linkBuf := &bytes.Buffer{}
|
||
|
// Set current buffer to text buffer
|
||
|
currentBuf := textBuf
|
||
|
|
||
|
// Declare variable for last literal
|
||
|
var lastLit string
|
||
|
|
||
|
// Define variable for amount of scans performed
|
||
|
amtScans := 0
|
||
|
parseLoop:
|
||
|
for {
|
||
|
// Scan token
|
||
|
tok, lit := p.scan()
|
||
|
// Increment amtScans
|
||
|
amtScans++
|
||
|
|
||
|
switch tok {
|
||
|
case scanner.WORD:
|
||
|
// Write word to current buffer
|
||
|
currentBuf.WriteString(lit)
|
||
|
case scanner.WS:
|
||
|
// Write word to current buffer
|
||
|
currentBuf.WriteString(lit)
|
||
|
case scanner.PUNCT:
|
||
|
// If closing bracket found but no text stored
|
||
|
if lit == "]" && currentBuf.Len() == 0 {
|
||
|
// Unscan token
|
||
|
p.unscan()
|
||
|
// Return nil as this is an invalid link
|
||
|
return nil, false
|
||
|
}
|
||
|
// If last literal is "]" and current is "("
|
||
|
if lastLit == "]" && lit == "(" {
|
||
|
// Switch current buffer to link buffer
|
||
|
currentBuf = linkBuf
|
||
|
// Continue to next token
|
||
|
continue
|
||
|
}
|
||
|
// If literal is ")"
|
||
|
if lit == ")" {
|
||
|
// Stop parsing
|
||
|
break parseLoop
|
||
|
}
|
||
|
// If literal is not "]"
|
||
|
if lit != "]" {
|
||
|
// Write literal to current buffer
|
||
|
currentBuf.WriteString(lit)
|
||
|
}
|
||
|
case scanner.EOL, scanner.EOF:
|
||
|
// Unscan all performed scans
|
||
|
p.unscanMulti(amtScans)
|
||
|
// Return nil as this is an invalid link
|
||
|
return nil, false
|
||
|
}
|
||
|
|
||
|
// Set last literal
|
||
|
lastLit = lit
|
||
|
}
|
||
|
|
||
|
// If no text
|
||
|
if textBuf.Len() == 0 {
|
||
|
// Use link as text
|
||
|
textBuf.WriteString(linkBuf.String())
|
||
|
}
|
||
|
|
||
|
// Set properties
|
||
|
link.Text = textBuf.String()
|
||
|
link.Link = linkBuf.String()
|
||
|
|
||
|
return link, false
|
||
|
}
|