package interpreter import ( "fmt" "masonry/lang" "strings" ) // HTMLInterpreter converts Masonry AST to HTML/JavaScript type HTMLInterpreter struct { entities map[string]*lang.Entity pages map[string]*lang.Page server *lang.Server } // NewHTMLInterpreter creates a new HTML interpreter func NewHTMLInterpreter() *HTMLInterpreter { return &HTMLInterpreter{ entities: make(map[string]*lang.Entity), pages: make(map[string]*lang.Page), } } // cleanString removes surrounding quotes from string literals func (hi *HTMLInterpreter) cleanString(s string) string { if len(s) >= 2 && ((s[0] == '"' && s[len(s)-1] == '"') || (s[0] == '\'' && s[len(s)-1] == '\'')) { return s[1 : len(s)-1] } return s } // escapeHTML escapes HTML special characters func (hi *HTMLInterpreter) escapeHTML(s string) string { s = strings.ReplaceAll(s, "&", "&") s = strings.ReplaceAll(s, "<", "<") s = strings.ReplaceAll(s, ">", ">") s = strings.ReplaceAll(s, "\"", """) s = strings.ReplaceAll(s, "'", "'") return s } // GenerateHTML converts a Masonry AST to HTML output func (hi *HTMLInterpreter) GenerateHTML(ast *lang.AST) (map[string]string, error) { // First pass: collect entities, pages, and server config for _, def := range ast.Definitions { if def.Entity != nil { hi.entities[def.Entity.Name] = def.Entity } if def.Page != nil { hi.pages[def.Page.Name] = def.Page } if def.Server != nil { hi.server = def.Server } } // Second pass: generate HTML for each page htmlFiles := make(map[string]string) for pageName, page := range hi.pages { html, err := hi.generatePageHTML(page) if err != nil { return nil, fmt.Errorf("error generating HTML for page %s: %w", pageName, err) } htmlFiles[fmt.Sprintf("%s.html", strings.ToLower(pageName))] = html } return htmlFiles, nil } // generatePageHTML creates HTML for a single page func (hi *HTMLInterpreter) generatePageHTML(page *lang.Page) (string, error) { var html strings.Builder // HTML document structure html.WriteString("\n\n\n") html.WriteString(" \n") html.WriteString(" \n") // Page title title := page.Name if page.Title != nil { title = hi.cleanString(*page.Title) } html.WriteString(fmt.Sprintf(" %s\n", hi.escapeHTML(title))) // Meta tags for _, meta := range page.Meta { cleanName := hi.cleanString(meta.Name) cleanContent := hi.cleanString(meta.Content) html.WriteString(fmt.Sprintf(" \n", hi.escapeHTML(cleanName), hi.escapeHTML(cleanContent))) } // Basic CSS for styling html.WriteString(" \n") html.WriteString("\n\n") // Page content html.WriteString("
\n") html.WriteString(fmt.Sprintf("

%s

\n", hi.escapeHTML(title))) // Generate sections for _, section := range page.Sections { sectionHTML, err := hi.generateSectionHTML(§ion, 2) if err != nil { return "", err } html.WriteString(sectionHTML) } // Generate direct components for _, component := range page.Components { componentHTML, err := hi.generateComponentHTML(&component, 2) if err != nil { return "", err } html.WriteString(componentHTML) } html.WriteString("
\n") // JavaScript for interactivity html.WriteString(" \n") html.WriteString("\n") return html.String(), nil } // generateServerConfigJS generates JavaScript code that handles server configuration at runtime func (hi *HTMLInterpreter) generateServerConfigJS() string { var js strings.Builder // Default API base URL js.WriteString(" // Server configuration\n") js.WriteString(" let apiHost = 'localhost';\n") js.WriteString(" let apiPort = 8080;\n\n") if hi.server != nil { for _, setting := range hi.server.Settings { if setting.Host != nil { js.WriteString(hi.generateConfigValueJS("apiHost", setting.Host)) } if setting.Port != nil { js.WriteString(hi.generateIntValueJS("apiPort", setting.Port)) } } } js.WriteString(" const API_BASE_URL = `http://${apiHost}:${apiPort}`;\n") js.WriteString(" console.log('API Base URL:', API_BASE_URL);\n") return js.String() } // generateConfigValueJS generates JavaScript code to resolve a ConfigValue at runtime func (hi *HTMLInterpreter) generateConfigValueJS(varName string, configValue *lang.ConfigValue) string { var js strings.Builder if configValue.Literal != nil { // Simple literal assignment js.WriteString(fmt.Sprintf(" %s = %q;\n", varName, *configValue.Literal)) } else if configValue.EnvVar != nil { // Environment variable resolution in browser (note: this is limited in browsers) // For client-side, we'll need to use a different approach since browsers can't access server env vars // We'll generate code that looks for the env var in localStorage or a global config object js.WriteString(fmt.Sprintf(" // Check for %s in global config or localStorage\n", configValue.EnvVar.Name)) js.WriteString(fmt.Sprintf(" if (window.CONFIG && window.CONFIG['%s']) {\n", configValue.EnvVar.Name)) js.WriteString(fmt.Sprintf(" %s = window.CONFIG['%s'];\n", varName, configValue.EnvVar.Name)) js.WriteString(fmt.Sprintf(" } else if (localStorage.getItem('%s')) {\n", configValue.EnvVar.Name)) js.WriteString(fmt.Sprintf(" %s = localStorage.getItem('%s');\n", varName, configValue.EnvVar.Name)) if configValue.EnvVar.Default != nil { js.WriteString(" } else {\n") js.WriteString(fmt.Sprintf(" %s = %q;\n", varName, *configValue.EnvVar.Default)) } js.WriteString(" }\n") } return js.String() } // generateIntValueJS generates JavaScript code to resolve an IntValue at runtime func (hi *HTMLInterpreter) generateIntValueJS(varName string, intValue *lang.IntValue) string { var js strings.Builder if intValue.Literal != nil { // Simple literal assignment js.WriteString(fmt.Sprintf(" %s = %d;\n", varName, *intValue.Literal)) } else if intValue.EnvVar != nil { // Environment variable resolution for integers js.WriteString(fmt.Sprintf(" // Check for %s in global config or localStorage\n", intValue.EnvVar.Name)) js.WriteString(fmt.Sprintf(" if (window.CONFIG && window.CONFIG['%s']) {\n", intValue.EnvVar.Name)) js.WriteString(fmt.Sprintf(" const val = parseInt(window.CONFIG['%s']);\n", intValue.EnvVar.Name)) js.WriteString(fmt.Sprintf(" if (!isNaN(val)) %s = val;\n", varName)) js.WriteString(fmt.Sprintf(" } else if (localStorage.getItem('%s')) {\n", intValue.EnvVar.Name)) js.WriteString(fmt.Sprintf(" const val = parseInt(localStorage.getItem('%s'));\n", intValue.EnvVar.Name)) js.WriteString(fmt.Sprintf(" if (!isNaN(val)) %s = val;\n", varName)) if intValue.EnvVar.Default != nil { js.WriteString(" } else {\n") js.WriteString(fmt.Sprintf(" %s = %s;\n", varName, *intValue.EnvVar.Default)) } js.WriteString(" }\n") } return js.String() } // generateSectionHTML creates HTML for a section func (hi *HTMLInterpreter) generateSectionHTML(section *lang.Section, indent int) (string, error) { var html strings.Builder indentStr := strings.Repeat(" ", indent) sectionType := "container" if section.Type != nil { sectionType = *section.Type } switch sectionType { case "tab": html.WriteString(fmt.Sprintf("%s
\n", indentStr, section.Name)) // Generate tab buttons for _, element := range section.Elements { if element.Section != nil && element.Section.Label != nil { activeClass := "" if element.Section.Active { activeClass = " active" } cleanLabel := hi.cleanString(*element.Section.Label) html.WriteString(fmt.Sprintf("%s \n", indentStr, activeClass, element.Section.Name, hi.escapeHTML(cleanLabel))) } } // Generate tab content for _, element := range section.Elements { if element.Section != nil { activeClass := "" if element.Section.Active { activeClass = " active" } html.WriteString(fmt.Sprintf("%s
\n", indentStr, activeClass, element.Section.Name)) sectionHTML, err := hi.generateSectionHTML(element.Section, indent+2) if err != nil { return "", err } html.WriteString(sectionHTML) html.WriteString(fmt.Sprintf("%s
\n", indentStr)) } } html.WriteString(fmt.Sprintf("%s
\n", indentStr)) case "modal": html.WriteString(fmt.Sprintf("%s
\n", indentStr, section.Name)) html.WriteString(fmt.Sprintf("%s
\n", indentStr)) if section.Label != nil { cleanLabel := hi.cleanString(*section.Label) html.WriteString(fmt.Sprintf("%s

%s

\n", indentStr, hi.escapeHTML(cleanLabel))) } // Generate modal content for _, element := range section.Elements { elementHTML, err := hi.generateSectionElementHTML(&element, indent+2) if err != nil { return "", err } html.WriteString(elementHTML) } html.WriteString(fmt.Sprintf("%s \n", indentStr, section.Name)) html.WriteString(fmt.Sprintf("%s
\n", indentStr)) html.WriteString(fmt.Sprintf("%s
\n", indentStr)) default: // container, panel, master, detail cssClass := "section" if section.Class != nil { cssClass = hi.cleanString(*section.Class) } html.WriteString(fmt.Sprintf("%s
\n", indentStr, hi.escapeHTML(cssClass), section.Name)) if section.Label != nil { cleanLabel := hi.cleanString(*section.Label) html.WriteString(fmt.Sprintf("%s

%s

\n", indentStr, hi.escapeHTML(cleanLabel))) } // Generate section content for _, element := range section.Elements { elementHTML, err := hi.generateSectionElementHTML(&element, indent+1) if err != nil { return "", err } html.WriteString(elementHTML) } html.WriteString(fmt.Sprintf("%s
\n", indentStr)) } return html.String(), nil } // generateSectionElementHTML creates HTML for section elements func (hi *HTMLInterpreter) generateSectionElementHTML(element *lang.SectionElement, indent int) (string, error) { if element.Component != nil { return hi.generateComponentHTML(element.Component, indent) } if element.Section != nil { return hi.generateSectionHTML(element.Section, indent) } if element.When != nil { return hi.generateWhenConditionHTML(element.When, indent) } return "", nil } // generateComponentHTML creates HTML for a component func (hi *HTMLInterpreter) generateComponentHTML(component *lang.Component, indent int) (string, error) { var html strings.Builder indentStr := strings.Repeat(" ", indent) switch component.Type { case "form": formId := fmt.Sprintf("form_%s", component.Type) if component.Entity != nil { formId = fmt.Sprintf("form_%s", *component.Entity) } html.WriteString(fmt.Sprintf("%s
\n", indentStr, formId, formId)) // Generate form fields for _, element := range component.Elements { if element.Field != nil { fieldHTML, err := hi.generateFieldHTML(element.Field, indent+1) if err != nil { return "", err } html.WriteString(fieldHTML) } if element.Button != nil { buttonHTML, err := hi.generateButtonHTML(element.Button, indent+1) if err != nil { return "", err } html.WriteString(buttonHTML) } } html.WriteString(fmt.Sprintf("%s \n", indentStr)) html.WriteString(fmt.Sprintf("%s
\n", indentStr)) case "table": html.WriteString(fmt.Sprintf("%s\n", indentStr)) html.WriteString(fmt.Sprintf("%s \n", indentStr)) // Generate table headers from fields for _, element := range component.Elements { if element.Field != nil { label := element.Field.Name for _, attr := range element.Field.Attributes { if attr.Label != nil { label = hi.cleanString(*attr.Label) break } } html.WriteString(fmt.Sprintf("%s \n", indentStr, hi.escapeHTML(label))) } } html.WriteString(fmt.Sprintf("%s \n", indentStr)) html.WriteString(fmt.Sprintf("%s \n", indentStr)) html.WriteString(fmt.Sprintf("%s \n", indentStr)) html.WriteString(fmt.Sprintf("%s \n", indentStr)) html.WriteString(fmt.Sprintf("%s
%s
Data will be loaded here...
\n", indentStr)) default: html.WriteString(fmt.Sprintf("%s
\n", indentStr, component.Type)) // Generate component content for _, element := range component.Elements { if element.Field != nil { fieldHTML, err := hi.generateFieldHTML(element.Field, indent+1) if err != nil { return "", err } html.WriteString(fieldHTML) } if element.Button != nil { buttonHTML, err := hi.generateButtonHTML(element.Button, indent+1) if err != nil { return "", err } html.WriteString(buttonHTML) } } html.WriteString(fmt.Sprintf("%s
\n", indentStr)) } return html.String(), nil } // generateFieldHTML creates HTML for a field func (hi *HTMLInterpreter) generateFieldHTML(field *lang.ComponentField, indent int) (string, error) { var html strings.Builder indentStr := strings.Repeat(" ", indent) // Get field attributes var label, placeholder, defaultValue string var required bool var options []string label = field.Name for _, attr := range field.Attributes { if attr.Label != nil { label = hi.cleanString(*attr.Label) } if attr.Placeholder != nil { placeholder = hi.cleanString(*attr.Placeholder) } if attr.Default != nil { defaultValue = hi.cleanString(*attr.Default) } if attr.Required { required = true } if attr.Options != nil { // Clean each option in the array options = make([]string, len(attr.Options)) for i, opt := range attr.Options { options[i] = hi.cleanString(opt) } } } html.WriteString(fmt.Sprintf("%s
\n", indentStr)) requiredAttr := "" if required { requiredAttr = " required" } html.WriteString(fmt.Sprintf("%s \n", indentStr, field.Name, hi.escapeHTML(label))) switch field.Type { case "text", "email", "password", "number", "tel", "url": html.WriteString(fmt.Sprintf("%s \n", indentStr, field.Type, field.Name, field.Name, hi.escapeHTML(placeholder), hi.escapeHTML(defaultValue), requiredAttr)) case "textarea": html.WriteString(fmt.Sprintf("%s \n", indentStr, field.Name, field.Name, hi.escapeHTML(placeholder), requiredAttr, hi.escapeHTML(defaultValue))) case "select": html.WriteString(fmt.Sprintf("%s \n", indentStr)) case "checkbox": checked := "" if defaultValue == "true" { checked = " checked" } html.WriteString(fmt.Sprintf("%s \n", indentStr, field.Name, field.Name, checked, requiredAttr)) case "file": html.WriteString(fmt.Sprintf("%s \n", indentStr, field.Name, field.Name, requiredAttr)) default: html.WriteString(fmt.Sprintf("%s \n", indentStr, field.Type, field.Name, field.Name, hi.escapeHTML(placeholder), hi.escapeHTML(defaultValue), requiredAttr)) } html.WriteString(fmt.Sprintf("%s
\n", indentStr)) return html.String(), nil } // generateButtonHTML creates HTML for a button func (hi *HTMLInterpreter) generateButtonHTML(button *lang.ComponentButton, indent int) (string, error) { var html strings.Builder indentStr := strings.Repeat(" ", indent) buttonClass := "button button-primary" onclick := "" // Process button attributes for _, attr := range button.Attributes { if attr.Style != nil { // Handle button style } if attr.Target != nil { // Handle button target/onclick } } cleanLabel := hi.cleanString(button.Label) html.WriteString(fmt.Sprintf("%s\n", indentStr, buttonClass, onclick, hi.escapeHTML(cleanLabel))) return html.String(), nil } // generateWhenConditionHTML creates HTML for conditional content func (hi *HTMLInterpreter) generateWhenConditionHTML(when *lang.WhenCondition, indent int) (string, error) { var html strings.Builder indentStr := strings.Repeat(" ", indent) // For now, we'll render the content as visible (real implementation would include JavaScript logic) html.WriteString(fmt.Sprintf("%s
\n", indentStr, when.Field, when.Operator, when.Value)) // Generate conditional content for _, field := range when.Fields { fieldHTML, err := hi.generateFieldHTML(&field, indent+1) if err != nil { return "", err } html.WriteString(fieldHTML) } for _, section := range when.Sections { sectionHTML, err := hi.generateSectionHTML(§ion, indent+1) if err != nil { return "", err } html.WriteString(sectionHTML) } for _, component := range when.Components { componentHTML, err := hi.generateComponentHTML(&component, indent+1) if err != nil { return "", err } html.WriteString(componentHTML) } for _, button := range when.Buttons { buttonHTML, err := hi.generateButtonHTML(&button, indent+1) if err != nil { return "", err } html.WriteString(buttonHTML) } html.WriteString(fmt.Sprintf("%s
\n", indentStr)) return html.String(), nil }