git commit -m "feat: initial release of Strata framework v0.1.0
- Static compiler with STRC pattern (Static Template Resolution with
Compartmentalized Layers)
- Template syntax: { } interpolation, { s-for }, { s-if/s-elif/s-else
}
- File types: .strata, .compiler.sts, .service.sts, .api.sts, .sts,
.scss
- CLI tools: strata dev, strata build, strata g (generators)
- create-strata scaffolding CLI with Pokemon API example
- Dev server with WebSocket HMR (Hot Module Replacement)
- Documentation: README, ARCHITECTURE, CHANGELOG, CONTRIBUTING,
LICENSE
This commit is contained in:
688
compiler/internal/parser/strata.go
Normal file
688
compiler/internal/parser/strata.go
Normal file
@@ -0,0 +1,688 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/CarGDev/strata/internal/ast"
|
||||
)
|
||||
|
||||
// StrataParser parses .strata files
|
||||
type StrataParser struct {
|
||||
source string
|
||||
pos int
|
||||
template *ast.TemplateNode
|
||||
script *ast.ScriptNode
|
||||
style *ast.StyleNode
|
||||
}
|
||||
|
||||
// NewStrataParser creates a new parser
|
||||
func NewStrataParser(source string) *StrataParser {
|
||||
return &StrataParser{
|
||||
source: source,
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse parses a .strata file into AST nodes
|
||||
func (p *StrataParser) Parse() (*ast.StrataFile, error) {
|
||||
file := &ast.StrataFile{}
|
||||
|
||||
// Extract <template>, <script>, and <style> blocks
|
||||
templateContent := p.extractBlock("template")
|
||||
scriptContent := p.extractBlock("script")
|
||||
styleContent := p.extractBlock("style")
|
||||
|
||||
// Parse template
|
||||
if templateContent != "" {
|
||||
template, err := p.parseTemplate(templateContent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file.Template = template
|
||||
}
|
||||
|
||||
// Parse script
|
||||
if scriptContent != "" {
|
||||
script, err := p.parseScript(scriptContent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file.Script = script
|
||||
}
|
||||
|
||||
// Parse style
|
||||
if styleContent != "" {
|
||||
style, err := p.parseStyle(styleContent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file.Style = style
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// extractBlock extracts content between <tag> and </tag>
|
||||
func (p *StrataParser) extractBlock(tag string) string {
|
||||
// Match opening tag with optional attributes
|
||||
openPattern := regexp.MustCompile(`<` + tag + `(?:\s+[^>]*)?>`)
|
||||
closePattern := regexp.MustCompile(`</` + tag + `>`)
|
||||
|
||||
openMatch := openPattern.FindStringIndex(p.source)
|
||||
if openMatch == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
closeMatch := closePattern.FindStringIndex(p.source[openMatch[1]:])
|
||||
if closeMatch == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Extract content between tags
|
||||
start := openMatch[1]
|
||||
end := openMatch[1] + closeMatch[0]
|
||||
|
||||
return strings.TrimSpace(p.source[start:end])
|
||||
}
|
||||
|
||||
// parseTemplate parses the template content
|
||||
func (p *StrataParser) parseTemplate(content string) (*ast.TemplateNode, error) {
|
||||
template := &ast.TemplateNode{
|
||||
Children: make([]ast.Node, 0),
|
||||
}
|
||||
|
||||
// Parse HTML with Strata directives
|
||||
nodes, err := p.parseHTML(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
template.Children = nodes
|
||||
return template, nil
|
||||
}
|
||||
|
||||
// parseHTML parses HTML content into AST nodes
|
||||
func (p *StrataParser) parseHTML(content string) ([]ast.Node, error) {
|
||||
nodes := make([]ast.Node, 0)
|
||||
|
||||
// Simple tokenization - in production, use proper HTML parser
|
||||
pos := 0
|
||||
for pos < len(content) {
|
||||
// Skip whitespace
|
||||
for pos < len(content) && isWhitespace(content[pos]) {
|
||||
pos++
|
||||
}
|
||||
|
||||
if pos >= len(content) {
|
||||
break
|
||||
}
|
||||
|
||||
// Strata block directive { s-* } or variable interpolation { var }
|
||||
if content[pos] == '{' && pos+1 < len(content) && content[pos+1] != '{' {
|
||||
// Find closing }
|
||||
end := strings.Index(content[pos:], "}")
|
||||
if end != -1 {
|
||||
inner := strings.TrimSpace(content[pos+1 : pos+end])
|
||||
|
||||
// Check for block directives
|
||||
if strings.HasPrefix(inner, "s-for ") {
|
||||
// Parse { s-for item in items } ... { /s-for }
|
||||
node, newPos, err := p.parseForBlock(content, pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes = append(nodes, node)
|
||||
pos = newPos
|
||||
continue
|
||||
} else if strings.HasPrefix(inner, "s-if ") {
|
||||
// Parse { s-if cond } ... { /s-if }
|
||||
node, newPos, err := p.parseIfBlock(content, pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes = append(nodes, node)
|
||||
pos = newPos
|
||||
continue
|
||||
} else if strings.HasPrefix(inner, "s-imp ") {
|
||||
// Parse { s-imp "@alias/file" }
|
||||
importPath := strings.TrimSpace(inner[6:])
|
||||
importPath = strings.Trim(importPath, `"'`)
|
||||
nodes = append(nodes, &ast.ImportNode{
|
||||
Path: importPath,
|
||||
})
|
||||
pos = pos + end + 1
|
||||
continue
|
||||
} else if strings.HasPrefix(inner, "/s-") || strings.HasPrefix(inner, "s-elif") || inner == "s-else" {
|
||||
// Closing or branch tags - handled by parent parser
|
||||
break
|
||||
} else {
|
||||
// Variable interpolation { var }
|
||||
nodes = append(nodes, &ast.InterpolationNode{
|
||||
Expression: inner,
|
||||
})
|
||||
pos = pos + end + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy {{ }} interpolation (for backwards compatibility)
|
||||
if pos+1 < len(content) && content[pos:pos+2] == "{{" {
|
||||
end := strings.Index(content[pos:], "}}")
|
||||
if end != -1 {
|
||||
expr := strings.TrimSpace(content[pos+2 : pos+end])
|
||||
nodes = append(nodes, &ast.InterpolationNode{
|
||||
Expression: expr,
|
||||
})
|
||||
pos = pos + end + 2
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// HTML Element
|
||||
if content[pos] == '<' {
|
||||
// Comment
|
||||
if pos+4 < len(content) && content[pos:pos+4] == "<!--" {
|
||||
end := strings.Index(content[pos:], "-->")
|
||||
if end != -1 {
|
||||
pos = pos + end + 3
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Closing tag
|
||||
if pos+2 < len(content) && content[pos+1] == '/' {
|
||||
end := strings.Index(content[pos:], ">")
|
||||
if end != -1 {
|
||||
pos = pos + end + 1
|
||||
}
|
||||
break // Return to parent
|
||||
}
|
||||
|
||||
// Opening tag
|
||||
node, newPos, err := p.parseElement(content, pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if node != nil {
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
pos = newPos
|
||||
continue
|
||||
}
|
||||
|
||||
// Text content
|
||||
textEnd := strings.IndexAny(content[pos:], "<{")
|
||||
if textEnd == -1 {
|
||||
textEnd = len(content) - pos
|
||||
}
|
||||
text := strings.TrimSpace(content[pos : pos+textEnd])
|
||||
if text != "" {
|
||||
nodes = append(nodes, &ast.TextNode{Content: text})
|
||||
}
|
||||
pos = pos + textEnd
|
||||
}
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// parseForBlock parses { s-for item in items } ... { /s-for }
|
||||
func (p *StrataParser) parseForBlock(content string, pos int) (*ast.ForBlockNode, int, error) {
|
||||
// Find opening tag end
|
||||
openEnd := strings.Index(content[pos:], "}")
|
||||
if openEnd == -1 {
|
||||
return nil, pos, nil
|
||||
}
|
||||
|
||||
// Parse the for expression: "s-for item in items" or "s-for item, i in items"
|
||||
inner := strings.TrimSpace(content[pos+1 : pos+openEnd])
|
||||
inner = strings.TrimPrefix(inner, "s-for ")
|
||||
inner = strings.TrimSpace(inner)
|
||||
|
||||
// Split by " in "
|
||||
parts := strings.SplitN(inner, " in ", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, pos + openEnd + 1, nil
|
||||
}
|
||||
|
||||
forNode := &ast.ForBlockNode{
|
||||
Iterable: strings.TrimSpace(parts[1]),
|
||||
}
|
||||
|
||||
// Parse item and optional index: "item" or "item, i"
|
||||
itemPart := strings.TrimSpace(parts[0])
|
||||
if strings.Contains(itemPart, ",") {
|
||||
itemParts := strings.SplitN(itemPart, ",", 2)
|
||||
forNode.Item = strings.TrimSpace(itemParts[0])
|
||||
forNode.Index = strings.TrimSpace(itemParts[1])
|
||||
} else {
|
||||
forNode.Item = itemPart
|
||||
}
|
||||
|
||||
// Find content between opening and closing tags
|
||||
startPos := pos + openEnd + 1
|
||||
closeTag := "{ /s-for }"
|
||||
closeTagAlt := "{/s-for}"
|
||||
|
||||
// Search for closing tag
|
||||
closePos := strings.Index(content[startPos:], closeTag)
|
||||
if closePos == -1 {
|
||||
closePos = strings.Index(content[startPos:], closeTagAlt)
|
||||
if closePos == -1 {
|
||||
// Try more lenient matching
|
||||
closePos = p.findClosingTag(content[startPos:], "/s-for")
|
||||
}
|
||||
}
|
||||
|
||||
if closePos == -1 {
|
||||
return nil, pos + openEnd + 1, nil
|
||||
}
|
||||
|
||||
// Parse children
|
||||
childContent := content[startPos : startPos+closePos]
|
||||
children, err := p.parseHTML(childContent)
|
||||
if err != nil {
|
||||
return nil, pos, err
|
||||
}
|
||||
forNode.Children = children
|
||||
|
||||
// Calculate new position after closing tag
|
||||
newPos := startPos + closePos
|
||||
// Skip past the closing tag
|
||||
closeEnd := strings.Index(content[newPos:], "}")
|
||||
if closeEnd != -1 {
|
||||
newPos = newPos + closeEnd + 1
|
||||
}
|
||||
|
||||
return forNode, newPos, nil
|
||||
}
|
||||
|
||||
// parseIfBlock parses { s-if cond } ... { s-elif cond } ... { s-else } ... { /s-if }
|
||||
func (p *StrataParser) parseIfBlock(content string, pos int) (*ast.IfBlockNode, int, error) {
|
||||
// Find opening tag end
|
||||
openEnd := strings.Index(content[pos:], "}")
|
||||
if openEnd == -1 {
|
||||
return nil, pos, nil
|
||||
}
|
||||
|
||||
// Parse the if condition
|
||||
inner := strings.TrimSpace(content[pos+1 : pos+openEnd])
|
||||
condition := strings.TrimPrefix(inner, "s-if ")
|
||||
condition = strings.TrimSpace(condition)
|
||||
|
||||
ifNode := &ast.IfBlockNode{
|
||||
Condition: condition,
|
||||
ElseIf: make([]*ast.ElseIfBranch, 0),
|
||||
}
|
||||
|
||||
// Find content and branches
|
||||
startPos := pos + openEnd + 1
|
||||
currentPos := startPos
|
||||
|
||||
// Parse until we hit /s-if, collecting elif and else branches
|
||||
for currentPos < len(content) {
|
||||
// Look for next branch or closing tag
|
||||
nextBrace := strings.Index(content[currentPos:], "{")
|
||||
if nextBrace == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
bracePos := currentPos + nextBrace
|
||||
braceEnd := strings.Index(content[bracePos:], "}")
|
||||
if braceEnd == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
braceContent := strings.TrimSpace(content[bracePos+1 : bracePos+braceEnd])
|
||||
|
||||
if strings.HasPrefix(braceContent, "/s-if") {
|
||||
// Closing tag - parse content up to here
|
||||
childContent := content[startPos:bracePos]
|
||||
children, err := p.parseHTML(childContent)
|
||||
if err != nil {
|
||||
return nil, pos, err
|
||||
}
|
||||
|
||||
// Add children to appropriate branch
|
||||
if len(ifNode.ElseIf) == 0 && len(ifNode.Else) == 0 {
|
||||
ifNode.Children = children
|
||||
}
|
||||
|
||||
return ifNode, bracePos + braceEnd + 1, nil
|
||||
} else if strings.HasPrefix(braceContent, "s-elif ") {
|
||||
// Parse content for previous branch
|
||||
childContent := content[startPos:bracePos]
|
||||
children, err := p.parseHTML(childContent)
|
||||
if err != nil {
|
||||
return nil, pos, err
|
||||
}
|
||||
|
||||
if len(ifNode.ElseIf) == 0 {
|
||||
ifNode.Children = children
|
||||
} else {
|
||||
ifNode.ElseIf[len(ifNode.ElseIf)-1].Children = children
|
||||
}
|
||||
|
||||
// Add new elif branch
|
||||
elifCond := strings.TrimPrefix(braceContent, "s-elif ")
|
||||
elifCond = strings.TrimSpace(elifCond)
|
||||
ifNode.ElseIf = append(ifNode.ElseIf, &ast.ElseIfBranch{
|
||||
Condition: elifCond,
|
||||
})
|
||||
|
||||
startPos = bracePos + braceEnd + 1
|
||||
currentPos = startPos
|
||||
continue
|
||||
} else if braceContent == "s-else" {
|
||||
// Parse content for previous branch
|
||||
childContent := content[startPos:bracePos]
|
||||
children, err := p.parseHTML(childContent)
|
||||
if err != nil {
|
||||
return nil, pos, err
|
||||
}
|
||||
|
||||
if len(ifNode.ElseIf) == 0 {
|
||||
ifNode.Children = children
|
||||
} else {
|
||||
ifNode.ElseIf[len(ifNode.ElseIf)-1].Children = children
|
||||
}
|
||||
|
||||
// Find closing tag and parse else content
|
||||
elseStart := bracePos + braceEnd + 1
|
||||
closePos := p.findClosingTag(content[elseStart:], "/s-if")
|
||||
if closePos != -1 {
|
||||
elseContent := content[elseStart : elseStart+closePos]
|
||||
elseChildren, err := p.parseHTML(elseContent)
|
||||
if err != nil {
|
||||
return nil, pos, err
|
||||
}
|
||||
ifNode.Else = elseChildren
|
||||
|
||||
// Skip to after closing tag
|
||||
closeEnd := strings.Index(content[elseStart+closePos:], "}")
|
||||
if closeEnd != -1 {
|
||||
return ifNode, elseStart + closePos + closeEnd + 1, nil
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
currentPos = bracePos + braceEnd + 1
|
||||
}
|
||||
|
||||
return ifNode, currentPos, nil
|
||||
}
|
||||
|
||||
// findClosingTag finds position of { /tag } allowing for whitespace
|
||||
func (p *StrataParser) findClosingTag(content string, tag string) int {
|
||||
pos := 0
|
||||
for pos < len(content) {
|
||||
bracePos := strings.Index(content[pos:], "{")
|
||||
if bracePos == -1 {
|
||||
return -1
|
||||
}
|
||||
bracePos += pos
|
||||
|
||||
braceEnd := strings.Index(content[bracePos:], "}")
|
||||
if braceEnd == -1 {
|
||||
return -1
|
||||
}
|
||||
|
||||
inner := strings.TrimSpace(content[bracePos+1 : bracePos+braceEnd])
|
||||
if inner == tag || strings.TrimSpace(inner) == tag {
|
||||
return bracePos
|
||||
}
|
||||
|
||||
pos = bracePos + braceEnd + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// parseElement parses an HTML element
|
||||
func (p *StrataParser) parseElement(content string, pos int) (ast.Node, int, error) {
|
||||
// Find tag name
|
||||
tagStart := pos + 1
|
||||
tagEnd := tagStart
|
||||
for tagEnd < len(content) && !isWhitespace(content[tagEnd]) && content[tagEnd] != '>' && content[tagEnd] != '/' {
|
||||
tagEnd++
|
||||
}
|
||||
|
||||
tagName := content[tagStart:tagEnd]
|
||||
|
||||
element := &ast.ElementNode{
|
||||
Tag: tagName,
|
||||
Attributes: make(map[string]string),
|
||||
Directives: make([]ast.Directive, 0),
|
||||
Children: make([]ast.Node, 0),
|
||||
}
|
||||
|
||||
// Parse attributes
|
||||
attrPos := tagEnd
|
||||
for attrPos < len(content) && content[attrPos] != '>' && content[attrPos] != '/' {
|
||||
// Skip whitespace
|
||||
for attrPos < len(content) && isWhitespace(content[attrPos]) {
|
||||
attrPos++
|
||||
}
|
||||
|
||||
if attrPos >= len(content) || content[attrPos] == '>' || content[attrPos] == '/' {
|
||||
break
|
||||
}
|
||||
|
||||
// Parse attribute name
|
||||
nameStart := attrPos
|
||||
for attrPos < len(content) && !isWhitespace(content[attrPos]) && content[attrPos] != '=' && content[attrPos] != '>' {
|
||||
attrPos++
|
||||
}
|
||||
attrName := content[nameStart:attrPos]
|
||||
|
||||
// Parse attribute value
|
||||
attrValue := ""
|
||||
for attrPos < len(content) && isWhitespace(content[attrPos]) {
|
||||
attrPos++
|
||||
}
|
||||
if attrPos < len(content) && content[attrPos] == '=' {
|
||||
attrPos++
|
||||
for attrPos < len(content) && isWhitespace(content[attrPos]) {
|
||||
attrPos++
|
||||
}
|
||||
if attrPos < len(content) && (content[attrPos] == '"' || content[attrPos] == '\'') {
|
||||
quote := content[attrPos]
|
||||
attrPos++
|
||||
valueStart := attrPos
|
||||
for attrPos < len(content) && content[attrPos] != quote {
|
||||
attrPos++
|
||||
}
|
||||
attrValue = content[valueStart:attrPos]
|
||||
attrPos++
|
||||
}
|
||||
}
|
||||
|
||||
// Check for directives
|
||||
if directive := p.parseDirective(attrName, attrValue); directive != nil {
|
||||
element.Directives = append(element.Directives, *directive)
|
||||
} else {
|
||||
element.Attributes[attrName] = attrValue
|
||||
}
|
||||
}
|
||||
|
||||
// Self-closing tag
|
||||
if attrPos < len(content) && content[attrPos] == '/' {
|
||||
attrPos++
|
||||
for attrPos < len(content) && content[attrPos] != '>' {
|
||||
attrPos++
|
||||
}
|
||||
return element, attrPos + 1, nil
|
||||
}
|
||||
|
||||
// Find closing >
|
||||
for attrPos < len(content) && content[attrPos] != '>' {
|
||||
attrPos++
|
||||
}
|
||||
attrPos++ // Skip >
|
||||
|
||||
// Void elements (no children)
|
||||
voidElements := map[string]bool{
|
||||
"area": true, "base": true, "br": true, "col": true,
|
||||
"embed": true, "hr": true, "img": true, "input": true,
|
||||
"link": true, "meta": true, "param": true, "source": true,
|
||||
"track": true, "wbr": true,
|
||||
}
|
||||
|
||||
if voidElements[tagName] {
|
||||
return element, attrPos, nil
|
||||
}
|
||||
|
||||
// Parse children
|
||||
childContent := ""
|
||||
depth := 1
|
||||
childStart := attrPos
|
||||
for attrPos < len(content) && depth > 0 {
|
||||
if attrPos+1 < len(content) && content[attrPos] == '<' {
|
||||
if content[attrPos+1] == '/' {
|
||||
// Closing tag
|
||||
closeEnd := strings.Index(content[attrPos:], ">")
|
||||
if closeEnd != -1 {
|
||||
closeTag := content[attrPos+2 : attrPos+closeEnd]
|
||||
closeTag = strings.TrimSpace(closeTag)
|
||||
if closeTag == tagName {
|
||||
depth--
|
||||
if depth == 0 {
|
||||
childContent = content[childStart:attrPos]
|
||||
attrPos = attrPos + closeEnd + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if content[attrPos+1] != '!' {
|
||||
// Opening tag - check if same tag
|
||||
openEnd := attrPos + 1
|
||||
for openEnd < len(content) && !isWhitespace(content[openEnd]) && content[openEnd] != '>' && content[openEnd] != '/' {
|
||||
openEnd++
|
||||
}
|
||||
openTag := content[attrPos+1 : openEnd]
|
||||
if openTag == tagName {
|
||||
depth++
|
||||
}
|
||||
}
|
||||
}
|
||||
attrPos++
|
||||
}
|
||||
|
||||
// Parse children
|
||||
if childContent != "" {
|
||||
children, err := p.parseHTML(childContent)
|
||||
if err != nil {
|
||||
return nil, attrPos, err
|
||||
}
|
||||
element.Children = children
|
||||
}
|
||||
|
||||
return element, attrPos, nil
|
||||
}
|
||||
|
||||
// parseDirective parses Strata directives
|
||||
func (p *StrataParser) parseDirective(name, value string) *ast.Directive {
|
||||
directive := &ast.Directive{
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
// s-if, s-else-if, s-else
|
||||
if name == "s-if" || name == "s-else-if" || name == "s-else" {
|
||||
directive.Type = ast.DirectiveConditional
|
||||
return directive
|
||||
}
|
||||
|
||||
// s-for
|
||||
if name == "s-for" {
|
||||
directive.Type = ast.DirectiveLoop
|
||||
return directive
|
||||
}
|
||||
|
||||
// s-model (two-way binding)
|
||||
if name == "s-model" {
|
||||
directive.Type = ast.DirectiveModel
|
||||
return directive
|
||||
}
|
||||
|
||||
// s-on:event or @event (event handlers)
|
||||
if strings.HasPrefix(name, "s-on:") || strings.HasPrefix(name, "@") {
|
||||
directive.Type = ast.DirectiveEvent
|
||||
if strings.HasPrefix(name, "@") {
|
||||
directive.Arg = name[1:]
|
||||
} else {
|
||||
directive.Arg = name[5:]
|
||||
}
|
||||
return directive
|
||||
}
|
||||
|
||||
// :attr (attribute binding)
|
||||
if strings.HasPrefix(name, ":") {
|
||||
directive.Type = ast.DirectiveBind
|
||||
directive.Arg = name[1:]
|
||||
return directive
|
||||
}
|
||||
|
||||
// s-client:* (hydration strategies)
|
||||
if strings.HasPrefix(name, "s-client:") {
|
||||
directive.Type = ast.DirectiveClient
|
||||
directive.Arg = name[9:]
|
||||
return directive
|
||||
}
|
||||
|
||||
// s-fetch (data fetching)
|
||||
if name == "s-fetch" {
|
||||
directive.Type = ast.DirectiveFetch
|
||||
return directive
|
||||
}
|
||||
|
||||
// s-ref
|
||||
if name == "s-ref" {
|
||||
directive.Type = ast.DirectiveRef
|
||||
return directive
|
||||
}
|
||||
|
||||
// s-html (raw HTML)
|
||||
if name == "s-html" {
|
||||
directive.Type = ast.DirectiveHTML
|
||||
return directive
|
||||
}
|
||||
|
||||
// s-static (no JS)
|
||||
if name == "s-static" {
|
||||
directive.Type = ast.DirectiveStatic
|
||||
return directive
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseScript parses the script content
|
||||
func (p *StrataParser) parseScript(content string) (*ast.ScriptNode, error) {
|
||||
return &ast.ScriptNode{
|
||||
Content: content,
|
||||
Lang: "ts", // Default to TypeScript
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseStyle parses the style content
|
||||
func (p *StrataParser) parseStyle(content string) (*ast.StyleNode, error) {
|
||||
// Check for lang attribute in original source
|
||||
langPattern := regexp.MustCompile(`<style\s+lang="([^"]+)"`)
|
||||
matches := langPattern.FindStringSubmatch(p.source)
|
||||
lang := "css"
|
||||
if len(matches) > 1 {
|
||||
lang = matches[1]
|
||||
}
|
||||
|
||||
return &ast.StyleNode{
|
||||
Content: content,
|
||||
Lang: lang,
|
||||
Scoped: strings.Contains(p.source, "<style scoped") || strings.Contains(p.source, "<style lang=\"scss\" scoped"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func isWhitespace(c byte) bool {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r'
|
||||
}
|
||||
Reference in New Issue
Block a user