From 30b0f9052c47da9857dfff7b83ac827f0236bd25 Mon Sep 17 00:00:00 2001 From: xnacly <47723417+xNaCly@users.noreply.github.com> Date: Tue, 11 Apr 2023 17:58:07 +0200 Subject: [PATCH] feat: implement inline & block code this commit introduces correct parsing of inline code and block code, it also fixes an issue with heading parsing, adds support for toc generation as well as writing the html output to a file. - --toc flag enables table of content generation via the parser.Parser.GenerateToc function - add headings field to parser.Parser - replaced some spagetti - moved info logs with time tracking to main - new info log when full execution is done - write to file --- fleck.go | 33 ++++++++++++++++- parser/parser.go | 95 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 118 insertions(+), 10 deletions(-) diff --git a/fleck.go b/fleck.go index f85a062..d805d23 100644 --- a/fleck.go +++ b/fleck.go @@ -1,8 +1,11 @@ package main import ( + "bufio" "fmt" "os" + "strings" + "time" "github.com/xnacly/fleck/cli" "github.com/xnacly/fleck/logger" @@ -12,6 +15,7 @@ import ( ) func main() { + start := time.Now() cli.ARGUMENTS = cli.ParseCli() if len(cli.ARGUMENTS.InputFile) == 0 { cli.PrintShortHelp() @@ -30,14 +34,38 @@ func main() { fileName = fileName + ".fleck" } + lexerStart := time.Now() s := scanner.New(fileName) tokens := s.Lex() + logger.LInfo("lexed " + fmt.Sprint(len(tokens)) + " token, took " + time.Since(lexerStart).String()) + + parserStart := time.Now() p := parser.New(tokens) - fmt.Println(p.Parse()) + result := p.Parse() + logger.LInfo("parsed " + fmt.Sprint(len(result)) + " items, took " + time.Since(parserStart).String()) + + writeStart := time.Now() + name := strings.Split(fileName, ".")[0] + ".html" + out, err := os.Create(name) + writer := bufio.NewWriter(out) + + if err != nil { + logger.LError("failed to open file: " + err.Error()) + } + + if cli.GetFlag(cli.ARGUMENTS, "toc") { + writer.WriteString(p.GenerateToc()) + } + + for _, e := range result { + writer.WriteString(e.String() + "\n") + } + + writer.Flush() + logger.LInfo("wrote generated html to '" + name + "', took: " + time.Since(writeStart).String()) defer func() { if cli.GetFlag(cli.ARGUMENTS, "preprocessor-enabled") { - if cli.GetFlag(cli.ARGUMENTS, "keep-temp") { return } @@ -47,5 +75,6 @@ func main() { logger.LWarn(err.Error()) } } + logger.LInfo("did everything, took: " + time.Since(start).String()) }() } diff --git a/parser/parser.go b/parser/parser.go index 4faeb21..fa71a89 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -4,14 +4,16 @@ import ( "fmt" "strings" + "github.com/xnacly/fleck/cli" "github.com/xnacly/fleck/logger" "github.com/xnacly/fleck/scanner" ) type Parser struct { - tokens []scanner.Token - tags []Tag - current int + tokens []scanner.Token + tags []Tag + current int + headings []Heading } func New(tokens []scanner.Token) Parser { @@ -32,8 +34,9 @@ func (p *Parser) Parse() []Tag { } func (p *Parser) tag() Tag { - // parse headings just before paragraphs at the end, everything else before - if p.peek().Kind == scanner.HASH && (p.prev().Kind == scanner.NEWLINE || p.prev().Kind == 0) { + if p.check(scanner.BACKTICK) { + return p.code() + } else if p.check(scanner.HASH) && (p.prev().Kind == scanner.NEWLINE || p.prev().Kind == 0) { return p.heading() } else { // TODO: currently skips everything except headings, fix that, this here is a temp messuare to keep the program from endless looping @@ -42,15 +45,63 @@ func (p *Parser) tag() Tag { } } +func (p *Parser) code() Tag { + p.advance() + if p.check(scanner.TEXT) { + // skip the ` + p.advance() + if p.check(scanner.BACKTICK) { + return CodeInline{ + text: p.prev().Value, + } + } + } else if p.check(scanner.BACKTICK) { + p.advance() + if !p.check(scanner.BACKTICK) { + return CodeInline{ + text: "", + } + } + p.advance() + language := p.peek().Value + // skip lang definition + p.advance() + // skip newline + p.advance() + + b := strings.Builder{} + for !p.check(scanner.BACKTICK) { + if p.check(scanner.TEXT) { + b.WriteString(p.peek().Value) + } else { + b.WriteRune(scanner.TOKEN_SYMBOL_MAP[p.peek().Kind]) + } + p.advance() + } + + // skip the three ``` + p.advance() + p.advance() + p.advance() + + return CodeBlock{ + language: language, + text: b.String(), + } + } + return nil +} + func (p *Parser) paragraph() Tag { return Paragraph{} } +// TODO: find a way to parse the rest of the tokens as well, not just write them to the heading func (p *Parser) heading() Tag { var lvl uint = 0 children := make([]scanner.Token, 0) - for p.peek().Kind == scanner.HASH { + for p.check(scanner.HASH) { lvl++ p.advance() } @@ -60,6 +111,7 @@ func (p *Parser) heading() Tag { p.advance() } + // skip the newline p.advance() b := strings.Builder{} @@ -74,11 +126,38 @@ func (p *Parser) heading() Tag { b.WriteRune(scanner.TOKEN_SYMBOL_MAP[c.Kind]) } } - - return Heading{ + heading := Heading{ lvl: lvl, text: b.String(), } + + if cli.GetFlag(cli.ARGUMENTS, "toc") { + p.headings = append(p.headings, heading) + } + return heading +} + +func (p *Parser) GenerateToc() string { + headingMap := map[uint]uint{ + 1: 0, + 2: 0, + 3: 0, + } + + b := strings.Builder{} + b.WriteString("