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, "+") { p.unscan() // Attempt to parse function function := p.parseFunc() // 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() *ast.Func { // Create new function function := &ast.Func{} tok, lit := p.scan() // 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 }