summaryrefslogtreecommitdiff
path: root/vendor/github.com/yuin/goldmark/parser/parser.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/yuin/goldmark/parser/parser.go')
-rw-r--r--vendor/github.com/yuin/goldmark/parser/parser.go1261
1 files changed, 0 insertions, 1261 deletions
diff --git a/vendor/github.com/yuin/goldmark/parser/parser.go b/vendor/github.com/yuin/goldmark/parser/parser.go
deleted file mode 100644
index b05db1356..000000000
--- a/vendor/github.com/yuin/goldmark/parser/parser.go
+++ /dev/null
@@ -1,1261 +0,0 @@
-// Package parser contains stuff that are related to parsing a Markdown text.
-package parser
-
-import (
- "fmt"
- "strings"
- "sync"
-
- "github.com/yuin/goldmark/ast"
- "github.com/yuin/goldmark/text"
- "github.com/yuin/goldmark/util"
-)
-
-// A Reference interface represents a link reference in Markdown text.
-type Reference interface {
- // String implements Stringer.
- String() string
-
- // Label returns a label of the reference.
- Label() []byte
-
- // Destination returns a destination(URL) of the reference.
- Destination() []byte
-
- // Title returns a title of the reference.
- Title() []byte
-}
-
-type reference struct {
- label []byte
- destination []byte
- title []byte
-}
-
-// NewReference returns a new Reference.
-func NewReference(label, destination, title []byte) Reference {
- return &reference{label, destination, title}
-}
-
-func (r *reference) Label() []byte {
- return r.label
-}
-
-func (r *reference) Destination() []byte {
- return r.destination
-}
-
-func (r *reference) Title() []byte {
- return r.title
-}
-
-func (r *reference) String() string {
- return fmt.Sprintf("Reference{Label:%s, Destination:%s, Title:%s}", r.label, r.destination, r.title)
-}
-
-// An IDs interface is a collection of the element ids.
-type IDs interface {
- // Generate generates a new element id.
- Generate(value []byte, kind ast.NodeKind) []byte
-
- // Put puts a given element id to the used ids table.
- Put(value []byte)
-}
-
-type ids struct {
- values map[string]bool
-}
-
-func newIDs() IDs {
- return &ids{
- values: map[string]bool{},
- }
-}
-
-func (s *ids) Generate(value []byte, kind ast.NodeKind) []byte {
- value = util.TrimLeftSpace(value)
- value = util.TrimRightSpace(value)
- result := []byte{}
- for i := 0; i < len(value); {
- v := value[i]
- l := util.UTF8Len(v)
- i += int(l)
- if l != 1 {
- continue
- }
- if util.IsAlphaNumeric(v) {
- if 'A' <= v && v <= 'Z' {
- v += 'a' - 'A'
- }
- result = append(result, v)
- } else if util.IsSpace(v) || v == '-' || v == '_' {
- result = append(result, '-')
- }
- }
- if len(result) == 0 {
- if kind == ast.KindHeading {
- result = []byte("heading")
- } else {
- result = []byte("id")
- }
- }
- if _, ok := s.values[util.BytesToReadOnlyString(result)]; !ok {
- s.values[util.BytesToReadOnlyString(result)] = true
- return result
- }
- for i := 1; ; i++ {
- newResult := fmt.Sprintf("%s-%d", result, i)
- if _, ok := s.values[newResult]; !ok {
- s.values[newResult] = true
- return []byte(newResult)
- }
-
- }
-}
-
-func (s *ids) Put(value []byte) {
- s.values[util.BytesToReadOnlyString(value)] = true
-}
-
-// ContextKey is a key that is used to set arbitrary values to the context.
-type ContextKey int
-
-// ContextKeyMax is a maximum value of the ContextKey.
-var ContextKeyMax ContextKey
-
-// NewContextKey return a new ContextKey value.
-func NewContextKey() ContextKey {
- ContextKeyMax++
- return ContextKeyMax
-}
-
-// A Context interface holds a information that are necessary to parse
-// Markdown text.
-type Context interface {
- // String implements Stringer.
- String() string
-
- // Get returns a value associated with the given key.
- Get(ContextKey) interface{}
-
- // ComputeIfAbsent computes a value if a value associated with the given key is absent and returns the value.
- ComputeIfAbsent(ContextKey, func() interface{}) interface{}
-
- // Set sets the given value to the context.
- Set(ContextKey, interface{})
-
- // AddReference adds the given reference to this context.
- AddReference(Reference)
-
- // Reference returns (a reference, true) if a reference associated with
- // the given label exists, otherwise (nil, false).
- Reference(label string) (Reference, bool)
-
- // References returns a list of references.
- References() []Reference
-
- // IDs returns a collection of the element ids.
- IDs() IDs
-
- // BlockOffset returns a first non-space character position on current line.
- // This value is valid only for BlockParser.Open.
- // BlockOffset returns -1 if current line is blank.
- BlockOffset() int
-
- // BlockOffset sets a first non-space character position on current line.
- // This value is valid only for BlockParser.Open.
- SetBlockOffset(int)
-
- // BlockIndent returns an indent width on current line.
- // This value is valid only for BlockParser.Open.
- // BlockIndent returns -1 if current line is blank.
- BlockIndent() int
-
- // BlockIndent sets an indent width on current line.
- // This value is valid only for BlockParser.Open.
- SetBlockIndent(int)
-
- // FirstDelimiter returns a first delimiter of the current delimiter list.
- FirstDelimiter() *Delimiter
-
- // LastDelimiter returns a last delimiter of the current delimiter list.
- LastDelimiter() *Delimiter
-
- // PushDelimiter appends the given delimiter to the tail of the current
- // delimiter list.
- PushDelimiter(delimiter *Delimiter)
-
- // RemoveDelimiter removes the given delimiter from the current delimiter list.
- RemoveDelimiter(d *Delimiter)
-
- // ClearDelimiters clears the current delimiter list.
- ClearDelimiters(bottom ast.Node)
-
- // OpenedBlocks returns a list of nodes that are currently in parsing.
- OpenedBlocks() []Block
-
- // SetOpenedBlocks sets a list of nodes that are currently in parsing.
- SetOpenedBlocks([]Block)
-
- // LastOpenedBlock returns a last node that is currently in parsing.
- LastOpenedBlock() Block
-
- // IsInLinkLabel returns true if current position seems to be in link label.
- IsInLinkLabel() bool
-}
-
-// A ContextConfig struct is a data structure that holds configuration of the Context.
-type ContextConfig struct {
- IDs IDs
-}
-
-// An ContextOption is a functional option type for the Context.
-type ContextOption func(*ContextConfig)
-
-// WithIDs is a functional option for the Context.
-func WithIDs(ids IDs) ContextOption {
- return func(c *ContextConfig) {
- c.IDs = ids
- }
-}
-
-type parseContext struct {
- store []interface{}
- ids IDs
- refs map[string]Reference
- blockOffset int
- blockIndent int
- delimiters *Delimiter
- lastDelimiter *Delimiter
- openedBlocks []Block
-}
-
-// NewContext returns a new Context.
-func NewContext(options ...ContextOption) Context {
- cfg := &ContextConfig{
- IDs: newIDs(),
- }
- for _, option := range options {
- option(cfg)
- }
-
- return &parseContext{
- store: make([]interface{}, ContextKeyMax+1),
- refs: map[string]Reference{},
- ids: cfg.IDs,
- blockOffset: -1,
- blockIndent: -1,
- delimiters: nil,
- lastDelimiter: nil,
- openedBlocks: []Block{},
- }
-}
-
-func (p *parseContext) Get(key ContextKey) interface{} {
- return p.store[key]
-}
-
-func (p *parseContext) ComputeIfAbsent(key ContextKey, f func() interface{}) interface{} {
- v := p.store[key]
- if v == nil {
- v = f()
- p.store[key] = v
- }
- return v
-}
-
-func (p *parseContext) Set(key ContextKey, value interface{}) {
- p.store[key] = value
-}
-
-func (p *parseContext) IDs() IDs {
- return p.ids
-}
-
-func (p *parseContext) BlockOffset() int {
- return p.blockOffset
-}
-
-func (p *parseContext) SetBlockOffset(v int) {
- p.blockOffset = v
-}
-
-func (p *parseContext) BlockIndent() int {
- return p.blockIndent
-}
-
-func (p *parseContext) SetBlockIndent(v int) {
- p.blockIndent = v
-}
-
-func (p *parseContext) LastDelimiter() *Delimiter {
- return p.lastDelimiter
-}
-
-func (p *parseContext) FirstDelimiter() *Delimiter {
- return p.delimiters
-}
-
-func (p *parseContext) PushDelimiter(d *Delimiter) {
- if p.delimiters == nil {
- p.delimiters = d
- p.lastDelimiter = d
- } else {
- l := p.lastDelimiter
- p.lastDelimiter = d
- l.NextDelimiter = d
- d.PreviousDelimiter = l
- }
-}
-
-func (p *parseContext) RemoveDelimiter(d *Delimiter) {
- if d.PreviousDelimiter == nil {
- p.delimiters = d.NextDelimiter
- } else {
- d.PreviousDelimiter.NextDelimiter = d.NextDelimiter
- if d.NextDelimiter != nil {
- d.NextDelimiter.PreviousDelimiter = d.PreviousDelimiter
- }
- }
- if d.NextDelimiter == nil {
- p.lastDelimiter = d.PreviousDelimiter
- }
- if p.delimiters != nil {
- p.delimiters.PreviousDelimiter = nil
- }
- if p.lastDelimiter != nil {
- p.lastDelimiter.NextDelimiter = nil
- }
- d.NextDelimiter = nil
- d.PreviousDelimiter = nil
- if d.Length != 0 {
- ast.MergeOrReplaceTextSegment(d.Parent(), d, d.Segment)
- } else {
- d.Parent().RemoveChild(d.Parent(), d)
- }
-}
-
-func (p *parseContext) ClearDelimiters(bottom ast.Node) {
- if p.lastDelimiter == nil {
- return
- }
- var c ast.Node
- for c = p.lastDelimiter; c != nil && c != bottom; {
- prev := c.PreviousSibling()
- if d, ok := c.(*Delimiter); ok {
- p.RemoveDelimiter(d)
- }
- c = prev
- }
-}
-
-func (p *parseContext) AddReference(ref Reference) {
- key := util.ToLinkReference(ref.Label())
- if _, ok := p.refs[key]; !ok {
- p.refs[key] = ref
- }
-}
-
-func (p *parseContext) Reference(label string) (Reference, bool) {
- v, ok := p.refs[label]
- return v, ok
-}
-
-func (p *parseContext) References() []Reference {
- ret := make([]Reference, 0, len(p.refs))
- for _, v := range p.refs {
- ret = append(ret, v)
- }
- return ret
-}
-
-func (p *parseContext) String() string {
- refs := []string{}
- for _, r := range p.refs {
- refs = append(refs, r.String())
- }
-
- return fmt.Sprintf("Context{Store:%#v, Refs:%s}", p.store, strings.Join(refs, ","))
-}
-
-func (p *parseContext) OpenedBlocks() []Block {
- return p.openedBlocks
-}
-
-func (p *parseContext) SetOpenedBlocks(v []Block) {
- p.openedBlocks = v
-}
-
-func (p *parseContext) LastOpenedBlock() Block {
- if l := len(p.openedBlocks); l != 0 {
- return p.openedBlocks[l-1]
- }
- return Block{}
-}
-
-func (p *parseContext) IsInLinkLabel() bool {
- tlist := p.Get(linkLabelStateKey)
- return tlist != nil
-}
-
-// State represents parser's state.
-// State is designed to use as a bit flag.
-type State int
-
-const (
- // None is a default value of the [State].
- None State = 1 << iota
-
- // Continue indicates parser can continue parsing.
- Continue
-
- // Close indicates parser cannot parse anymore.
- Close
-
- // HasChildren indicates parser may have child blocks.
- HasChildren
-
- // NoChildren indicates parser does not have child blocks.
- NoChildren
-
- // RequireParagraph indicates parser requires that the last node
- // must be a paragraph and is not converted to other nodes by
- // ParagraphTransformers.
- RequireParagraph
-)
-
-// A Config struct is a data structure that holds configuration of the Parser.
-type Config struct {
- Options map[OptionName]interface{}
- BlockParsers util.PrioritizedSlice /*<BlockParser>*/
- InlineParsers util.PrioritizedSlice /*<InlineParser>*/
- ParagraphTransformers util.PrioritizedSlice /*<ParagraphTransformer>*/
- ASTTransformers util.PrioritizedSlice /*<ASTTransformer>*/
- EscapedSpace bool
-}
-
-// NewConfig returns a new Config.
-func NewConfig() *Config {
- return &Config{
- Options: map[OptionName]interface{}{},
- BlockParsers: util.PrioritizedSlice{},
- InlineParsers: util.PrioritizedSlice{},
- ParagraphTransformers: util.PrioritizedSlice{},
- ASTTransformers: util.PrioritizedSlice{},
- }
-}
-
-// An Option interface is a functional option type for the Parser.
-type Option interface {
- SetParserOption(*Config)
-}
-
-// OptionName is a name of parser options.
-type OptionName string
-
-// Attribute is an option name that spacify attributes of elements.
-const optAttribute OptionName = "Attribute"
-
-type withAttribute struct {
-}
-
-func (o *withAttribute) SetParserOption(c *Config) {
- c.Options[optAttribute] = true
-}
-
-// WithAttribute is a functional option that enables custom attributes.
-func WithAttribute() Option {
- return &withAttribute{}
-}
-
-// A Parser interface parses Markdown text into AST nodes.
-type Parser interface {
- // Parse parses the given Markdown text into AST nodes.
- Parse(reader text.Reader, opts ...ParseOption) ast.Node
-
- // AddOption adds the given option to this parser.
- AddOptions(...Option)
-}
-
-// A SetOptioner interface sets the given option to the object.
-type SetOptioner interface {
- // SetOption sets the given option to the object.
- // Unacceptable options may be passed.
- // Thus implementations must ignore unacceptable options.
- SetOption(name OptionName, value interface{})
-}
-
-// A BlockParser interface parses a block level element like Paragraph, List,
-// Blockquote etc.
-type BlockParser interface {
- // Trigger returns a list of characters that triggers Parse method of
- // this parser.
- // If Trigger returns a nil, Open will be called with any lines.
- Trigger() []byte
-
- // Open parses the current line and returns a result of parsing.
- //
- // Open must not parse beyond the current line.
- // If Open has been able to parse the current line, Open must advance a reader
- // position by consumed byte length.
- //
- // If Open has not been able to parse the current line, Open should returns
- // (nil, NoChildren). If Open has been able to parse the current line, Open
- // should returns a new Block node and returns HasChildren or NoChildren.
- Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State)
-
- // Continue parses the current line and returns a result of parsing.
- //
- // Continue must not parse beyond the current line.
- // If Continue has been able to parse the current line, Continue must advance
- // a reader position by consumed byte length.
- //
- // If Continue has not been able to parse the current line, Continue should
- // returns Close. If Continue has been able to parse the current line,
- // Continue should returns (Continue | NoChildren) or
- // (Continue | HasChildren)
- Continue(node ast.Node, reader text.Reader, pc Context) State
-
- // Close will be called when the parser returns Close.
- Close(node ast.Node, reader text.Reader, pc Context)
-
- // CanInterruptParagraph returns true if the parser can interrupt paragraphs,
- // otherwise false.
- CanInterruptParagraph() bool
-
- // CanAcceptIndentedLine returns true if the parser can open new node when
- // the given line is being indented more than 3 spaces.
- CanAcceptIndentedLine() bool
-}
-
-// An InlineParser interface parses an inline level element like CodeSpan, Link etc.
-type InlineParser interface {
- // Trigger returns a list of characters that triggers Parse method of
- // this parser.
- // Trigger characters must be a punctuation or a halfspace.
- // Halfspaces triggers this parser when character is any spaces characters or
- // a head of line
- Trigger() []byte
-
- // Parse parse the given block into an inline node.
- //
- // Parse can parse beyond the current line.
- // If Parse has been able to parse the current line, it must advance a reader
- // position by consumed byte length.
- Parse(parent ast.Node, block text.Reader, pc Context) ast.Node
-}
-
-// A CloseBlocker interface is a callback function that will be
-// called when block is closed in the inline parsing.
-type CloseBlocker interface {
- // CloseBlock will be called when a block is closed.
- CloseBlock(parent ast.Node, block text.Reader, pc Context)
-}
-
-// A ParagraphTransformer transforms parsed Paragraph nodes.
-// For example, link references are searched in parsed Paragraphs.
-type ParagraphTransformer interface {
- // Transform transforms the given paragraph.
- Transform(node *ast.Paragraph, reader text.Reader, pc Context)
-}
-
-// ASTTransformer transforms entire Markdown document AST tree.
-type ASTTransformer interface {
- // Transform transforms the given AST tree.
- Transform(node *ast.Document, reader text.Reader, pc Context)
-}
-
-// DefaultBlockParsers returns a new list of default BlockParsers.
-// Priorities of default BlockParsers are:
-//
-// SetextHeadingParser, 100
-// ThematicBreakParser, 200
-// ListParser, 300
-// ListItemParser, 400
-// CodeBlockParser, 500
-// ATXHeadingParser, 600
-// FencedCodeBlockParser, 700
-// BlockquoteParser, 800
-// HTMLBlockParser, 900
-// ParagraphParser, 1000
-func DefaultBlockParsers() []util.PrioritizedValue {
- return []util.PrioritizedValue{
- util.Prioritized(NewSetextHeadingParser(), 100),
- util.Prioritized(NewThematicBreakParser(), 200),
- util.Prioritized(NewListParser(), 300),
- util.Prioritized(NewListItemParser(), 400),
- util.Prioritized(NewCodeBlockParser(), 500),
- util.Prioritized(NewATXHeadingParser(), 600),
- util.Prioritized(NewFencedCodeBlockParser(), 700),
- util.Prioritized(NewBlockquoteParser(), 800),
- util.Prioritized(NewHTMLBlockParser(), 900),
- util.Prioritized(NewParagraphParser(), 1000),
- }
-}
-
-// DefaultInlineParsers returns a new list of default InlineParsers.
-// Priorities of default InlineParsers are:
-//
-// CodeSpanParser, 100
-// LinkParser, 200
-// AutoLinkParser, 300
-// RawHTMLParser, 400
-// EmphasisParser, 500
-func DefaultInlineParsers() []util.PrioritizedValue {
- return []util.PrioritizedValue{
- util.Prioritized(NewCodeSpanParser(), 100),
- util.Prioritized(NewLinkParser(), 200),
- util.Prioritized(NewAutoLinkParser(), 300),
- util.Prioritized(NewRawHTMLParser(), 400),
- util.Prioritized(NewEmphasisParser(), 500),
- }
-}
-
-// DefaultParagraphTransformers returns a new list of default ParagraphTransformers.
-// Priorities of default ParagraphTransformers are:
-//
-// LinkReferenceParagraphTransformer, 100
-func DefaultParagraphTransformers() []util.PrioritizedValue {
- return []util.PrioritizedValue{
- util.Prioritized(LinkReferenceParagraphTransformer, 100),
- }
-}
-
-// A Block struct holds a node and correspond parser pair.
-type Block struct {
- // Node is a BlockNode.
- Node ast.Node
- // Parser is a BlockParser.
- Parser BlockParser
-}
-
-type parser struct {
- options map[OptionName]interface{}
- blockParsers [256][]BlockParser
- freeBlockParsers []BlockParser
- inlineParsers [256][]InlineParser
- closeBlockers []CloseBlocker
- paragraphTransformers []ParagraphTransformer
- astTransformers []ASTTransformer
- escapedSpace bool
- config *Config
- initSync sync.Once
-}
-
-type withBlockParsers struct {
- value []util.PrioritizedValue
-}
-
-func (o *withBlockParsers) SetParserOption(c *Config) {
- c.BlockParsers = append(c.BlockParsers, o.value...)
-}
-
-// WithBlockParsers is a functional option that allow you to add
-// BlockParsers to the parser.
-func WithBlockParsers(bs ...util.PrioritizedValue) Option {
- return &withBlockParsers{bs}
-}
-
-type withInlineParsers struct {
- value []util.PrioritizedValue
-}
-
-func (o *withInlineParsers) SetParserOption(c *Config) {
- c.InlineParsers = append(c.InlineParsers, o.value...)
-}
-
-// WithInlineParsers is a functional option that allow you to add
-// InlineParsers to the parser.
-func WithInlineParsers(bs ...util.PrioritizedValue) Option {
- return &withInlineParsers{bs}
-}
-
-type withParagraphTransformers struct {
- value []util.PrioritizedValue
-}
-
-func (o *withParagraphTransformers) SetParserOption(c *Config) {
- c.ParagraphTransformers = append(c.ParagraphTransformers, o.value...)
-}
-
-// WithParagraphTransformers is a functional option that allow you to add
-// ParagraphTransformers to the parser.
-func WithParagraphTransformers(ps ...util.PrioritizedValue) Option {
- return &withParagraphTransformers{ps}
-}
-
-type withASTTransformers struct {
- value []util.PrioritizedValue
-}
-
-func (o *withASTTransformers) SetParserOption(c *Config) {
- c.ASTTransformers = append(c.ASTTransformers, o.value...)
-}
-
-// WithASTTransformers is a functional option that allow you to add
-// ASTTransformers to the parser.
-func WithASTTransformers(ps ...util.PrioritizedValue) Option {
- return &withASTTransformers{ps}
-}
-
-type withEscapedSpace struct {
-}
-
-func (o *withEscapedSpace) SetParserOption(c *Config) {
- c.EscapedSpace = true
-}
-
-// WithEscapedSpace is a functional option indicates that a '\' escaped half-space(0x20) should not trigger parsers.
-func WithEscapedSpace() Option {
- return &withEscapedSpace{}
-}
-
-type withOption struct {
- name OptionName
- value interface{}
-}
-
-func (o *withOption) SetParserOption(c *Config) {
- c.Options[o.name] = o.value
-}
-
-// WithOption is a functional option that allow you to set
-// an arbitrary option to the parser.
-func WithOption(name OptionName, value interface{}) Option {
- return &withOption{name, value}
-}
-
-// NewParser returns a new Parser with given options.
-func NewParser(options ...Option) Parser {
- config := NewConfig()
- for _, opt := range options {
- opt.SetParserOption(config)
- }
-
- p := &parser{
- options: map[OptionName]interface{}{},
- config: config,
- }
-
- return p
-}
-
-func (p *parser) AddOptions(opts ...Option) {
- for _, opt := range opts {
- opt.SetParserOption(p.config)
- }
-}
-
-func (p *parser) addBlockParser(v util.PrioritizedValue, options map[OptionName]interface{}) {
- bp, ok := v.Value.(BlockParser)
- if !ok {
- panic(fmt.Sprintf("%v is not a BlockParser", v.Value))
- }
- tcs := bp.Trigger()
- so, ok := v.Value.(SetOptioner)
- if ok {
- for oname, ovalue := range options {
- so.SetOption(oname, ovalue)
- }
- }
- if tcs == nil {
- p.freeBlockParsers = append(p.freeBlockParsers, bp)
- } else {
- for _, tc := range tcs {
- if p.blockParsers[tc] == nil {
- p.blockParsers[tc] = []BlockParser{}
- }
- p.blockParsers[tc] = append(p.blockParsers[tc], bp)
- }
- }
-}
-
-func (p *parser) addInlineParser(v util.PrioritizedValue, options map[OptionName]interface{}) {
- ip, ok := v.Value.(InlineParser)
- if !ok {
- panic(fmt.Sprintf("%v is not a InlineParser", v.Value))
- }
- tcs := ip.Trigger()
- so, ok := v.Value.(SetOptioner)
- if ok {
- for oname, ovalue := range options {
- so.SetOption(oname, ovalue)
- }
- }
- if cb, ok := ip.(CloseBlocker); ok {
- p.closeBlockers = append(p.closeBlockers, cb)
- }
- for _, tc := range tcs {
- if p.inlineParsers[tc] == nil {
- p.inlineParsers[tc] = []InlineParser{}
- }
- p.inlineParsers[tc] = append(p.inlineParsers[tc], ip)
- }
-}
-
-func (p *parser) addParagraphTransformer(v util.PrioritizedValue, options map[OptionName]interface{}) {
- pt, ok := v.Value.(ParagraphTransformer)
- if !ok {
- panic(fmt.Sprintf("%v is not a ParagraphTransformer", v.Value))
- }
- so, ok := v.Value.(SetOptioner)
- if ok {
- for oname, ovalue := range options {
- so.SetOption(oname, ovalue)
- }
- }
- p.paragraphTransformers = append(p.paragraphTransformers, pt)
-}
-
-func (p *parser) addASTTransformer(v util.PrioritizedValue, options map[OptionName]interface{}) {
- at, ok := v.Value.(ASTTransformer)
- if !ok {
- panic(fmt.Sprintf("%v is not a ASTTransformer", v.Value))
- }
- so, ok := v.Value.(SetOptioner)
- if ok {
- for oname, ovalue := range options {
- so.SetOption(oname, ovalue)
- }
- }
- p.astTransformers = append(p.astTransformers, at)
-}
-
-// A ParseConfig struct is a data structure that holds configuration of the Parser.Parse.
-type ParseConfig struct {
- Context Context
-}
-
-// A ParseOption is a functional option type for the Parser.Parse.
-type ParseOption func(c *ParseConfig)
-
-// WithContext is a functional option that allow you to override
-// a default context.
-func WithContext(context Context) ParseOption {
- return func(c *ParseConfig) {
- c.Context = context
- }
-}
-
-func (p *parser) Parse(reader text.Reader, opts ...ParseOption) ast.Node {
- p.initSync.Do(func() {
- p.config.BlockParsers.Sort()
- for _, v := range p.config.BlockParsers {
- p.addBlockParser(v, p.config.Options)
- }
- for i := range p.blockParsers {
- if p.blockParsers[i] != nil {
- p.blockParsers[i] = append(p.blockParsers[i], p.freeBlockParsers...)
- }
- }
-
- p.config.InlineParsers.Sort()
- for _, v := range p.config.InlineParsers {
- p.addInlineParser(v, p.config.Options)
- }
- p.config.ParagraphTransformers.Sort()
- for _, v := range p.config.ParagraphTransformers {
- p.addParagraphTransformer(v, p.config.Options)
- }
- p.config.ASTTransformers.Sort()
- for _, v := range p.config.ASTTransformers {
- p.addASTTransformer(v, p.config.Options)
- }
- p.escapedSpace = p.config.EscapedSpace
- p.config = nil
- })
- c := &ParseConfig{}
- for _, opt := range opts {
- opt(c)
- }
- if c.Context == nil {
- c.Context = NewContext()
- }
- pc := c.Context
- root := ast.NewDocument()
- p.parseBlocks(root, reader, pc)
-
- blockReader := text.NewBlockReader(reader.Source(), nil)
- p.walkBlock(root, func(node ast.Node) {
- p.parseBlock(blockReader, node, pc)
- })
- for _, at := range p.astTransformers {
- at.Transform(root, reader, pc)
- }
-
- // root.Dump(reader.Source(), 0)
- return root
-}
-
-func (p *parser) transformParagraph(node *ast.Paragraph, reader text.Reader, pc Context) bool {
- for _, pt := range p.paragraphTransformers {
- pt.Transform(node, reader, pc)
- if node.Parent() == nil {
- return true
- }
- }
- return false
-}
-
-func (p *parser) closeBlocks(from, to int, reader text.Reader, pc Context) {
- blocks := pc.OpenedBlocks()
- for i := from; i >= to; i-- {
- node := blocks[i].Node
- paragraph, ok := node.(*ast.Paragraph)
- if ok && node.Parent() != nil {
- p.transformParagraph(paragraph, reader, pc)
- }
- if node.Parent() != nil { // closes only if node has not been transformed
- blocks[i].Parser.Close(blocks[i].Node, reader, pc)
- }
- }
- if from == len(blocks)-1 {
- blocks = blocks[0:to]
- } else {
- blocks = append(blocks[0:to], blocks[from+1:]...)
- }
- pc.SetOpenedBlocks(blocks)
-}
-
-type blockOpenResult int
-
-const (
- paragraphContinuation blockOpenResult = iota + 1
- newBlocksOpened
- noBlocksOpened
-)
-
-func (p *parser) openBlocks(parent ast.Node, blankLine bool, reader text.Reader, pc Context) blockOpenResult {
- result := blockOpenResult(noBlocksOpened)
- continuable := false
- lastBlock := pc.LastOpenedBlock()
- if lastBlock.Node != nil {
- continuable = ast.IsParagraph(lastBlock.Node)
- }
-retry:
- var bps []BlockParser
- line, _ := reader.PeekLine()
- w, pos := util.IndentWidth(line, reader.LineOffset())
- if w >= len(line) {
- pc.SetBlockOffset(-1)
- pc.SetBlockIndent(-1)
- } else {
- pc.SetBlockOffset(pos)
- pc.SetBlockIndent(w)
- }
- if line == nil || line[0] == '\n' {
- goto continuable
- }
- bps = p.freeBlockParsers
- if pos < len(line) {
- bps = p.blockParsers[line[pos]]
- if bps == nil {
- bps = p.freeBlockParsers
- }
- }
- if bps == nil {
- goto continuable
- }
-
- for _, bp := range bps {
- if continuable && result == noBlocksOpened && !bp.CanInterruptParagraph() {
- continue
- }
- if w > 3 && !bp.CanAcceptIndentedLine() {
- continue
- }
- lastBlock = pc.LastOpenedBlock()
- last := lastBlock.Node
- node, state := bp.Open(parent, reader, pc)
- if node != nil {
- // Parser requires last node to be a paragraph.
- // With table extension:
- //
- // 0
- // -:
- // -
- //
- // '-' on 3rd line seems a Setext heading because 1st and 2nd lines
- // are being paragraph when the Settext heading parser tries to parse the 3rd
- // line.
- // But 1st line and 2nd line are a table. Thus this paragraph will be transformed
- // by a paragraph transformer. So this text should be converted to a table and
- // an empty list.
- if state&RequireParagraph != 0 {
- if last == parent.LastChild() {
- // Opened paragraph may be transformed by ParagraphTransformers in
- // closeBlocks().
- lastBlock.Parser.Close(last, reader, pc)
- blocks := pc.OpenedBlocks()
- pc.SetOpenedBlocks(blocks[0 : len(blocks)-1])
- if p.transformParagraph(last.(*ast.Paragraph), reader, pc) {
- // Paragraph has been transformed.
- // So this parser is considered as failing.
- continuable = false
- goto retry
- }
- }
- }
- node.SetBlankPreviousLines(blankLine)
- if last != nil && last.Parent() == nil {
- lastPos := len(pc.OpenedBlocks()) - 1
- p.closeBlocks(lastPos, lastPos, reader, pc)
- }
- parent.AppendChild(parent, node)
- result = newBlocksOpened
- be := Block{node, bp}
- pc.SetOpenedBlocks(append(pc.OpenedBlocks(), be))
- if state&HasChildren != 0 {
- parent = node
- goto retry // try child block
- }
- break // no children, can not open more blocks on this line
- }
- }
-
-continuable:
- if result == noBlocksOpened && continuable {
- state := lastBlock.Parser.Continue(lastBlock.Node, reader, pc)
- if state&Continue != 0 {
- result = paragraphContinuation
- }
- }
- return result
-}
-
-type lineStat struct {
- lineNum int
- level int
- isBlank bool
-}
-
-func isBlankLine(lineNum, level int, stats []lineStat) bool {
- ret := true
- for i := len(stats) - 1 - level; i >= 0; i-- {
- ret = false
- s := stats[i]
- if s.lineNum == lineNum {
- if s.level < level && s.isBlank {
- return true
- } else if s.level == level {
- return s.isBlank
- }
- }
- if s.lineNum < lineNum {
- return ret
- }
- }
- return ret
-}
-
-func (p *parser) parseBlocks(parent ast.Node, reader text.Reader, pc Context) {
- pc.SetOpenedBlocks([]Block{})
- blankLines := make([]lineStat, 0, 128)
- var isBlank bool
- for { // process blocks separated by blank lines
- _, lines, ok := reader.SkipBlankLines()
- if !ok {
- return
- }
- lineNum, _ := reader.Position()
- if lines != 0 {
- blankLines = blankLines[0:0]
- l := len(pc.OpenedBlocks())
- for i := 0; i < l; i++ {
- blankLines = append(blankLines, lineStat{lineNum - 1, i, lines != 0})
- }
- }
- isBlank = isBlankLine(lineNum-1, 0, blankLines)
- // first, we try to open blocks
- if p.openBlocks(parent, isBlank, reader, pc) != newBlocksOpened {
- return
- }
- reader.AdvanceLine()
- for { // process opened blocks line by line
- openedBlocks := pc.OpenedBlocks()
- l := len(openedBlocks)
- if l == 0 {
- break
- }
- lastIndex := l - 1
- for i := 0; i < l; i++ {
- be := openedBlocks[i]
- line, _ := reader.PeekLine()
- if line == nil {
- p.closeBlocks(lastIndex, 0, reader, pc)
- reader.AdvanceLine()
- return
- }
- lineNum, _ := reader.Position()
- blankLines = append(blankLines, lineStat{lineNum, i, util.IsBlank(line)})
- // If node is a paragraph, p.openBlocks determines whether it is continuable.
- // So we do not process paragraphs here.
- if !ast.IsParagraph(be.Node) {
- state := be.Parser.Continue(be.Node, reader, pc)
- if state&Continue != 0 {
- // When current node is a container block and has no children,
- // we try to open new child nodes
- if state&HasChildren != 0 && i == lastIndex {
- isBlank = isBlankLine(lineNum-1, i, blankLines)
- p.openBlocks(be.Node, isBlank, reader, pc)
- break
- }
- continue
- }
- }
- // current node may be closed or lazy continuation
- isBlank = isBlankLine(lineNum-1, i, blankLines)
- thisParent := parent
- if i != 0 {
- thisParent = openedBlocks[i-1].Node
- }
- lastNode := openedBlocks[lastIndex].Node
- result := p.openBlocks(thisParent, isBlank, reader, pc)
- if result != paragraphContinuation {
- // lastNode is a paragraph and was transformed by the paragraph
- // transformers.
- if openedBlocks[lastIndex].Node != lastNode {
- lastIndex--
- }
- p.closeBlocks(lastIndex, i, reader, pc)
- }
- break
- }
-
- reader.AdvanceLine()
- }
- }
-}
-
-func (p *parser) walkBlock(block ast.Node, cb func(node ast.Node)) {
- for c := block.FirstChild(); c != nil; c = c.NextSibling() {
- p.walkBlock(c, cb)
- }
- cb(block)
-}
-
-const (
- lineBreakHard uint8 = 1 << iota
- lineBreakSoft
- lineBreakVisible
-)
-
-func (p *parser) parseBlock(block text.BlockReader, parent ast.Node, pc Context) {
- if parent.IsRaw() {
- return
- }
- escaped := false
- source := block.Source()
- block.Reset(parent.Lines())
- for {
- retry:
- line, _ := block.PeekLine()
- if line == nil {
- break
- }
- lineLength := len(line)
- var lineBreakFlags uint8
- hasNewLine := line[lineLength-1] == '\n'
- if ((lineLength >= 3 && line[lineLength-2] == '\\' &&
- line[lineLength-3] != '\\') || (lineLength == 2 && line[lineLength-2] == '\\')) && hasNewLine { // ends with \\n
- lineLength -= 2
- lineBreakFlags |= lineBreakHard | lineBreakVisible
- } else if ((lineLength >= 4 && line[lineLength-3] == '\\' && line[lineLength-2] == '\r' &&
- line[lineLength-4] != '\\') || (lineLength == 3 && line[lineLength-3] == '\\' && line[lineLength-2] == '\r')) &&
- hasNewLine { // ends with \\r\n
- lineLength -= 3
- lineBreakFlags |= lineBreakHard | lineBreakVisible
- } else if lineLength >= 3 && line[lineLength-3] == ' ' && line[lineLength-2] == ' ' &&
- hasNewLine { // ends with [space][space]\n
- lineLength -= 3
- lineBreakFlags |= lineBreakHard
- } else if lineLength >= 4 && line[lineLength-4] == ' ' && line[lineLength-3] == ' ' &&
- line[lineLength-2] == '\r' && hasNewLine { // ends with [space][space]\r\n
- lineLength -= 4
- lineBreakFlags |= lineBreakHard
- } else if hasNewLine {
- // If the line ends with a newline character, but it is not a hardlineBreak, then it is a softLinebreak
- // If the line ends with a hardlineBreak, then it cannot end with a softLinebreak
- // See https://spec.commonmark.org/0.30/#soft-line-breaks
- lineBreakFlags |= lineBreakSoft
- }
-
- l, startPosition := block.Position()
- n := 0
- for i := 0; i < lineLength; i++ {
- c := line[i]
- if c == '\n' {
- break
- }
- isSpace := util.IsSpace(c) && c != '\r' && c != '\n'
- isPunct := util.IsPunct(c)
- if (isPunct && !escaped) || isSpace && !(escaped && p.escapedSpace) || i == 0 {
- parserChar := c
- if isSpace || (i == 0 && !isPunct) {
- parserChar = ' '
- }
- ips := p.inlineParsers[parserChar]
- if ips != nil {
- block.Advance(n)
- n = 0
- savedLine, savedPosition := block.Position()
- if i != 0 {
- _, currentPosition := block.Position()
- ast.MergeOrAppendTextSegment(parent, startPosition.Between(currentPosition))
- _, startPosition = block.Position()
- }
- var inlineNode ast.Node
- for _, ip := range ips {
- inlineNode = ip.Parse(parent, block, pc)
- if inlineNode != nil {
- break
- }
- block.SetPosition(savedLine, savedPosition)
- }
- if inlineNode != nil {
- parent.AppendChild(parent, inlineNode)
- goto retry
- }
- }
- }
- if escaped {
- escaped = false
- n++
- continue
- }
-
- if c == '\\' {
- escaped = true
- n++
- continue
- }
-
- escaped = false
- n++
- }
- if n != 0 {
- block.Advance(n)
- }
- currentL, currentPosition := block.Position()
- if l != currentL {
- continue
- }
- diff := startPosition.Between(currentPosition)
- var text *ast.Text
- if lineBreakFlags&(lineBreakHard|lineBreakVisible) == lineBreakHard|lineBreakVisible {
- text = ast.NewTextSegment(diff)
- } else {
- text = ast.NewTextSegment(diff.TrimRightSpace(source))
- }
- text.SetSoftLineBreak(lineBreakFlags&lineBreakSoft != 0)
- text.SetHardLineBreak(lineBreakFlags&lineBreakHard != 0)
- parent.AppendChild(parent, text)
- block.AdvanceLine()
- }
-
- ProcessDelimiters(nil, pc)
- for _, ip := range p.closeBlockers {
- ip.CloseBlock(parent, block, pc)
- }
-
-}