amu/parser/para.go
2021-10-02 15:12:57 -07:00

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
}