- 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
305 lines
7.5 KiB
Go
305 lines
7.5 KiB
Go
package ast
|
|
|
|
// Node is the base interface for all AST nodes
|
|
type Node interface {
|
|
NodeType() string
|
|
}
|
|
|
|
// StrataFile represents a parsed .strata file
|
|
type StrataFile struct {
|
|
Template *TemplateNode
|
|
Script *ScriptNode
|
|
Style *StyleNode
|
|
}
|
|
|
|
func (s *StrataFile) NodeType() string { return "StrataFile" }
|
|
|
|
// TemplateNode represents the <template> block
|
|
type TemplateNode struct {
|
|
Children []Node
|
|
}
|
|
|
|
func (t *TemplateNode) NodeType() string { return "Template" }
|
|
|
|
// ScriptNode represents the <script> block
|
|
type ScriptNode struct {
|
|
Content string
|
|
Lang string // "ts" or "js"
|
|
}
|
|
|
|
func (s *ScriptNode) NodeType() string { return "Script" }
|
|
|
|
// StyleNode represents the <style> block
|
|
type StyleNode struct {
|
|
Content string
|
|
Lang string // "css", "scss", "sass", "less"
|
|
Scoped bool
|
|
}
|
|
|
|
func (s *StyleNode) NodeType() string { return "Style" }
|
|
|
|
// ElementNode represents an HTML element
|
|
type ElementNode struct {
|
|
Tag string
|
|
Attributes map[string]string
|
|
Directives []Directive
|
|
Children []Node
|
|
IsComponent bool
|
|
}
|
|
|
|
func (e *ElementNode) NodeType() string { return "Element" }
|
|
|
|
// TextNode represents text content
|
|
type TextNode struct {
|
|
Content string
|
|
}
|
|
|
|
func (t *TextNode) NodeType() string { return "Text" }
|
|
|
|
// InterpolationNode represents { expression } variable binding
|
|
type InterpolationNode struct {
|
|
Expression string
|
|
}
|
|
|
|
func (i *InterpolationNode) NodeType() string { return "Interpolation" }
|
|
|
|
// ForBlockNode represents { s-for item in items } ... { /s-for }
|
|
type ForBlockNode struct {
|
|
Item string // Loop variable name (e.g., "item")
|
|
Index string // Optional index variable (e.g., "i" in "item, i")
|
|
Iterable string // Expression to iterate (e.g., "items")
|
|
Children []Node // Content inside the for block
|
|
}
|
|
|
|
func (f *ForBlockNode) NodeType() string { return "ForBlock" }
|
|
|
|
// IfBlockNode represents { s-if } ... { s-elif } ... { s-else } ... { /s-if }
|
|
type IfBlockNode struct {
|
|
Condition string // The if condition
|
|
Children []Node // Content when condition is true
|
|
ElseIf []*ElseIfBranch
|
|
Else []Node // Content for else branch
|
|
}
|
|
|
|
func (i *IfBlockNode) NodeType() string { return "IfBlock" }
|
|
|
|
// ElseIfBranch represents an elif branch in conditional
|
|
type ElseIfBranch struct {
|
|
Condition string
|
|
Children []Node
|
|
}
|
|
|
|
// ImportNode represents { s-imp "@alias/file" }
|
|
type ImportNode struct {
|
|
Path string // Import path (e.g., "@components/Button")
|
|
Alias string // Optional alias for the import
|
|
}
|
|
|
|
func (i *ImportNode) NodeType() string { return "Import" }
|
|
|
|
// DirectiveType represents the type of directive
|
|
type DirectiveType int
|
|
|
|
const (
|
|
DirectiveConditional DirectiveType = iota // s-if, s-else-if, s-else
|
|
DirectiveLoop // s-for
|
|
DirectiveModel // s-model
|
|
DirectiveEvent // s-on:event, @event
|
|
DirectiveBind // :attr
|
|
DirectiveClient // s-client:load, s-client:visible, etc.
|
|
DirectiveFetch // s-fetch
|
|
DirectiveRef // s-ref
|
|
DirectiveHTML // s-html
|
|
DirectiveStatic // s-static
|
|
)
|
|
|
|
// Directive represents a Strata directive
|
|
type Directive struct {
|
|
Type DirectiveType
|
|
Name string // Original attribute name
|
|
Value string // Attribute value
|
|
Arg string // Argument (e.g., "click" in @click)
|
|
Modifiers []string // Modifiers (e.g., "prevent" in @click.prevent)
|
|
}
|
|
|
|
// ComponentNode represents a component usage
|
|
type ComponentNode struct {
|
|
Name string
|
|
Props map[string]string
|
|
Directives []Directive
|
|
Slots map[string][]Node
|
|
}
|
|
|
|
func (c *ComponentNode) NodeType() string { return "Component" }
|
|
|
|
// SlotNode represents a <slot> element
|
|
type SlotNode struct {
|
|
Name string // default or named
|
|
Fallback []Node
|
|
}
|
|
|
|
func (s *SlotNode) NodeType() string { return "Slot" }
|
|
|
|
// ImportDeclaration represents an import statement
|
|
type ImportDeclaration struct {
|
|
Default string // Default import name
|
|
Named []string // Named imports
|
|
Source string // Import source path
|
|
}
|
|
|
|
// ComponentDefinition represents a component's parsed definition
|
|
type ComponentDefinition struct {
|
|
Name string
|
|
Props []PropDefinition
|
|
Imports []ImportDeclaration
|
|
Setup string // The setup function body
|
|
}
|
|
|
|
// PropDefinition represents a component prop
|
|
type PropDefinition struct {
|
|
Name string
|
|
Type string
|
|
Required bool
|
|
Default string
|
|
}
|
|
|
|
// StoreDefinition represents a parsed store
|
|
type StoreDefinition struct {
|
|
Name string
|
|
State map[string]string // Field name -> type
|
|
Actions []ActionDefinition
|
|
Encrypt []string // Fields to encrypt
|
|
Persist []string // Fields to persist
|
|
Shared []string // Fields to share across tabs
|
|
}
|
|
|
|
// ActionDefinition represents a store action
|
|
type ActionDefinition struct {
|
|
Name string
|
|
Params []string
|
|
Body string
|
|
Async bool
|
|
}
|
|
|
|
// ToHTML converts a TemplateNode to HTML string
|
|
func (t *TemplateNode) ToHTML() string {
|
|
var result string
|
|
for _, child := range t.Children {
|
|
result += nodeToHTML(child)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// nodeToHTML converts any AST node to HTML
|
|
func nodeToHTML(node Node) string {
|
|
if node == nil {
|
|
return ""
|
|
}
|
|
|
|
switch n := node.(type) {
|
|
case *ElementNode:
|
|
return elementToHTML(n)
|
|
case *TextNode:
|
|
return n.Content
|
|
case *InterpolationNode:
|
|
// For dev mode, output a placeholder that will be filled by JS
|
|
return `<span data-strata-bind="` + n.Expression + `"></span>`
|
|
case *ForBlockNode:
|
|
return forBlockToHTML(n)
|
|
case *IfBlockNode:
|
|
return ifBlockToHTML(n)
|
|
case *ImportNode:
|
|
// Imports are handled at compile time, not rendered
|
|
return ""
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// forBlockToHTML converts a ForBlockNode to HTML with data attributes
|
|
func forBlockToHTML(f *ForBlockNode) string {
|
|
html := `<template data-strata-for="` + f.Item
|
|
if f.Index != "" {
|
|
html += `, ` + f.Index
|
|
}
|
|
html += ` in ` + f.Iterable + `">`
|
|
for _, child := range f.Children {
|
|
html += nodeToHTML(child)
|
|
}
|
|
html += `</template>`
|
|
return html
|
|
}
|
|
|
|
// ifBlockToHTML converts an IfBlockNode to HTML with data attributes
|
|
func ifBlockToHTML(i *IfBlockNode) string {
|
|
html := `<template data-strata-if="` + i.Condition + `">`
|
|
for _, child := range i.Children {
|
|
html += nodeToHTML(child)
|
|
}
|
|
html += `</template>`
|
|
|
|
// Handle elif branches
|
|
for _, elif := range i.ElseIf {
|
|
html += `<template data-strata-elif="` + elif.Condition + `">`
|
|
for _, child := range elif.Children {
|
|
html += nodeToHTML(child)
|
|
}
|
|
html += `</template>`
|
|
}
|
|
|
|
// Handle else branch
|
|
if len(i.Else) > 0 {
|
|
html += `<template data-strata-else>`
|
|
for _, child := range i.Else {
|
|
html += nodeToHTML(child)
|
|
}
|
|
html += `</template>`
|
|
}
|
|
|
|
return html
|
|
}
|
|
|
|
// elementToHTML converts an ElementNode to HTML
|
|
func elementToHTML(el *ElementNode) string {
|
|
if el == nil {
|
|
return ""
|
|
}
|
|
|
|
html := "<" + el.Tag
|
|
|
|
// Add attributes
|
|
for name, value := range el.Attributes {
|
|
if value != "" {
|
|
html += ` ` + name + `="` + value + `"`
|
|
} else {
|
|
html += ` ` + name
|
|
}
|
|
}
|
|
|
|
// Add directive attributes for dev mode debugging
|
|
for _, dir := range el.Directives {
|
|
html += ` data-strata-` + dir.Name + `="` + dir.Value + `"`
|
|
}
|
|
|
|
html += ">"
|
|
|
|
// Add children
|
|
for _, child := range el.Children {
|
|
html += nodeToHTML(child)
|
|
}
|
|
|
|
// Close tag (unless void element)
|
|
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[el.Tag] {
|
|
html += "</" + el.Tag + ">"
|
|
}
|
|
|
|
return html
|
|
}
|