diff --git a/public/parsing.wasm b/public/parsing.wasm index 98484b9..bda6c88 100755 Binary files a/public/parsing.wasm and b/public/parsing.wasm differ diff --git a/src/parsing/embed.go b/src/parsing/embed.go index c342eba..0073686 100644 --- a/src/parsing/embed.go +++ b/src/parsing/embed.go @@ -104,15 +104,19 @@ func extract(re *regexp.Regexp, src []byte, subexpName string) []byte { return m[re.SubexpIndex(subexpName)] } -func extractMap(re *regexp.Regexp, src []byte, subexpNames ...string) map[string][]byte { +func extractMap(re *regexp.Regexp, src []byte) map[string][]byte { m := re.FindSubmatch(src) if m == nil { return nil } res := make(map[string][]byte) - for _, name := range subexpNames { - res[name] = m[re.SubexpIndex(name)] + for _, name := range re.SubexpNames() { + if name != "" { + i := re.SubexpIndex(name) + res[name] = m[i] + } } + res["all"] = m[0] return res } @@ -124,15 +128,22 @@ func extractAll(re *regexp.Regexp, src []byte, subexpName string) [][]byte { return m[re.SubexpIndex(subexpName)] } -func extractAllMap(re *regexp.Regexp, src []byte, subexpNames ...string) map[string][][]byte { +func extractAllMap(re *regexp.Regexp, src []byte) map[string][][]byte { m := re.FindAllSubmatch(src, -1) if m == nil { return nil } res := make(map[string][][]byte) - for _, name := range subexpNames { - res[name] = m[re.SubexpIndex(name)] + for i, name := range re.SubexpNames() { + if name != "" { + var vals [][]byte + for _, specificMatch := range m { + vals = append(vals, specificMatch[i]) + } + res[name] = vals + } } + res["all"] = m[0] return res } diff --git a/src/parsing/ggcode.go b/src/parsing/ggcode.go index 4c42895..405ec80 100644 --- a/src/parsing/ggcode.go +++ b/src/parsing/ggcode.go @@ -2,8 +2,10 @@ package parsing import ( "bytes" + "fmt" "regexp" + "git.handmade.network/hmn/hmn/src/hmnurl" "github.com/yuin/goldmark" "github.com/yuin/goldmark/ast" gast "github.com/yuin/goldmark/ast" @@ -38,6 +40,19 @@ import ( type ggcodeRenderer = func(w util.BufWriter, n *ggcodeNode, entering bool) error var ggcodeTags = map[string]ggcodeRenderer{ + "glossary": func(w util.BufWriter, n *ggcodeNode, entering bool) error { + if entering { + term, _ := n.Args["term"] + w.WriteString(fmt.Sprintf( + ``, + hmnurl.BuildEducationGlossary(term), + term, + )) + } else { + w.WriteString("") + } + return nil + }, "resource": func(w util.BufWriter, n *ggcodeNode, entering bool) error { if entering { w.WriteString("a node :o") @@ -53,25 +68,28 @@ var ggcodeTags = map[string]ggcodeRenderer{ // ---------------------- var reGGCodeBlockOpen = regexp.MustCompile(`^!!!(?P[a-zA-Z0-9-_]+)(\{(?P.*?)\})?$`) -var reGGCodeBlockArgs = regexp.MustCompile(`(?P[a-zA-Z0-9-_]+)="(?P.*?)"`) +var reGGCodeInline = regexp.MustCompile(`^!!(?P[a-zA-Z0-9-_]+)(\{(?P.*?)\})?(\((?P.*?)\))?`) +var reGGCodeArgs = regexp.MustCompile(`(?P[a-zA-Z0-9-_]+)="(?P.*?)"`) -type ggcodeParserBlock struct { +// Block parser stuff + +type ggcodeBlockParser struct { Preview bool } -var _ parser.BlockParser = ggcodeParserBlock{} +var _ parser.BlockParser = ggcodeBlockParser{} -func (s ggcodeParserBlock) Trigger() []byte { - return []byte("!!!") +func (s ggcodeBlockParser) Trigger() []byte { + return []byte("!") } -func (s ggcodeParserBlock) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) { +func (s ggcodeBlockParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) { restOfLine, _ := reader.PeekLine() - if match := extractMap(reGGCodeBlockOpen, bytes.TrimSpace(restOfLine), "name", "args"); match != nil { + if match := extractMap(reGGCodeBlockOpen, bytes.TrimSpace(restOfLine)); match != nil { name := string(match["name"]) var args map[string]string - if argsMatch := extractAllMap(reGGCodeBlockArgs, match["args"]); argsMatch != nil { + if argsMatch := extractAllMap(reGGCodeArgs, match["args"]); argsMatch != nil { args = make(map[string]string) for i := range argsMatch["arg"] { arg := string(argsMatch["arg"][i]) @@ -90,7 +108,7 @@ func (s ggcodeParserBlock) Open(parent ast.Node, reader text.Reader, pc parser.C } } -func (s ggcodeParserBlock) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State { +func (s ggcodeBlockParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State { line, _ := reader.PeekLine() if string(bytes.TrimSpace(line)) == "!!!" { reader.Advance(3) @@ -99,16 +117,80 @@ func (s ggcodeParserBlock) Continue(node ast.Node, reader text.Reader, pc parser return parser.Continue | parser.HasChildren } -func (s ggcodeParserBlock) Close(node ast.Node, reader text.Reader, pc parser.Context) {} +func (s ggcodeBlockParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {} -func (s ggcodeParserBlock) CanInterruptParagraph() bool { +func (s ggcodeBlockParser) CanInterruptParagraph() bool { return false } -func (s ggcodeParserBlock) CanAcceptIndentedLine() bool { +func (s ggcodeBlockParser) CanAcceptIndentedLine() bool { return false } +// Inline parser stuff + +type ggcodeInlineParser struct { + Preview bool +} + +var _ parser.InlineParser = ggcodeInlineParser{} + +func (s ggcodeInlineParser) Trigger() []byte { + return []byte("!()") +} + +func (s ggcodeInlineParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node { + restOfLine, segment := block.PeekLine() // Gets the rest of the line (starting at the current parser cursor index), and the segment representing the indices in the source text. + if match := extractMap(reGGCodeInline, restOfLine); match != nil { + name := string(match["name"]) + var args map[string]string + if argsMatch := extractAllMap(reGGCodeArgs, match["args"]); argsMatch != nil { + args = make(map[string]string) + for i := range argsMatch["arg"] { + arg := string(argsMatch["arg"][i]) + val := string(argsMatch["val"][i]) + args[arg] = val + } + } + + node := &ggcodeNode{ + Name: name, + Args: args, + } + contentLength := len(match["content"]) + if contentLength > 0 { + contentSegmentStart := segment.Start + len(match["all"]) - (contentLength + 1) // the 1 is for the end parenthesis + contentSegmentEnd := contentSegmentStart + contentLength + contentSegment := text.NewSegment(contentSegmentStart, contentSegmentEnd) + node.AppendChild(node, ast.NewTextSegment(contentSegment)) + } + + block.Advance(len(match["all"])) + return node + } else { + return nil + } +} + +type ggcodeDelimiterParser struct { + Node *ggcodeNode // We need to pass this through 🙄 +} + +func (p ggcodeDelimiterParser) IsDelimiter(b byte) bool { + fmt.Println("delmit", string(b)) + return b == '(' || b == ')' +} + +func (p ggcodeDelimiterParser) CanOpenCloser(opener, closer *parser.Delimiter) bool { + fmt.Println("oopen") + return opener.Char == '(' && closer.Char == ')' +} + +func (p ggcodeDelimiterParser) OnMatch(consumes int) gast.Node { + fmt.Println("out!") + return p.Node +} + // ---------------------- // AST node // ---------------------- @@ -180,7 +262,10 @@ type ggcodeExtension struct { func (e ggcodeExtension) Extend(m goldmark.Markdown) { m.Parser().AddOptions(parser.WithBlockParsers( - util.Prioritized(ggcodeParserBlock{Preview: e.Preview}, 500), + util.Prioritized(ggcodeBlockParser{Preview: e.Preview}, 500), + )) + m.Parser().AddOptions(parser.WithInlineParsers( + util.Prioritized(ggcodeInlineParser{Preview: e.Preview}, 500), )) m.Renderer().AddOptions(renderer.WithNodeRenderers( util.Prioritized(newGGCodeHTMLRenderer(), 500),