package interpreter import ( "fmt" "go/format" "masonry/lang" "strings" "golang.org/x/text/cases" "golang.org/x/text/language" ) // ServerInterpreter converts Masonry AST to a simple Go HTTP server type ServerInterpreter struct { entities map[string]*lang.Entity endpoints map[string]*lang.Endpoint server *lang.Server } // NewServerInterpreter creates a new server interpreter func NewServerInterpreter() *ServerInterpreter { return &ServerInterpreter{ entities: make(map[string]*lang.Entity), endpoints: make(map[string]*lang.Endpoint), } } // Interpret processes the AST and generates server code func (si *ServerInterpreter) Interpret(ast lang.AST) (string, error) { // First pass: collect all definitions for _, def := range ast.Definitions { if def.Server != nil { si.server = def.Server } if def.Entity != nil { si.entities[def.Entity.Name] = def.Entity } if def.Endpoint != nil { key := fmt.Sprintf("%s_%s", def.Endpoint.Method, strings.ReplaceAll(def.Endpoint.Path, "/", "_")) si.endpoints[key] = def.Endpoint } } // Generate the server code rawCode, err := si.generateServer() if err != nil { return "", err } // Format the code to remove unused imports and fix formatting formattedCode, err := format.Source([]byte(rawCode)) if err != nil { // If formatting fails, return the raw code with a warning comment return "// Warning: Code formatting failed, but code should still be functional\n" + rawCode, nil } return string(formattedCode), nil } // generateServer creates the complete server code func (si *ServerInterpreter) generateServer() (string, error) { var code strings.Builder // Package and imports - only include what's actually used code.WriteString(`package main import ( "encoding/json" "fmt" "log" "net/http" "sync" "time" "github.com/google/uuid" "github.com/gorilla/mux" ) `) // Generate entity structs code.WriteString("// Entity definitions\n") for _, entity := range si.entities { code.WriteString(si.generateEntityStruct(entity)) code.WriteString("\n") } // Generate in-memory database code.WriteString(si.generateInMemoryDB()) // Generate handlers code.WriteString("// HTTP Handlers\n") for _, entity := range si.entities { code.WriteString(si.generateEntityHandlers(entity)) } // Generate custom endpoint handlers for _, endpoint := range si.endpoints { if endpoint.Entity == nil { code.WriteString(si.generateCustomEndpointHandler(endpoint)) } } // Generate main function code.WriteString(si.generateMainFunction()) return code.String(), nil } // generateEntityStruct creates a Go struct for an entity func (si *ServerInterpreter) generateEntityStruct(entity *lang.Entity) string { var code strings.Builder if entity.Description != nil { code.WriteString(fmt.Sprintf("// %s - %s\n", entity.Name, *entity.Description)) } code.WriteString(fmt.Sprintf("type %s struct {\n", entity.Name)) // Always add ID field code.WriteString("\tID string `json:\"id\"`\n") code.WriteString("\tCreatedAt time.Time `json:\"created_at\"`\n") code.WriteString("\tUpdatedAt time.Time `json:\"updated_at\"`\n") for _, field := range entity.Fields { goType := si.convertToGoType(field.Type) jsonTag := strings.ToLower(field.Name) if !field.Required { goType = "*" + goType } code.WriteString(fmt.Sprintf("\t%s %s `json:\"%s\"`\n", cases.Title(language.English).String(field.Name), goType, jsonTag)) } code.WriteString("}\n") return code.String() } // convertToGoType maps Masonry types to Go types func (si *ServerInterpreter) convertToGoType(masonryType string) string { switch masonryType { case "string", "text", "email", "url": return "string" case "int", "number": return "int" case "float": return "float64" case "boolean": return "bool" case "uuid": return "string" case "timestamp": return "time.Time" default: return "string" // default fallback } } // generateInMemoryDB creates the in-memory database structure func (si *ServerInterpreter) generateInMemoryDB() string { var code strings.Builder code.WriteString("// In-memory database\n") code.WriteString("type InMemoryDB struct {\n") code.WriteString("\tmu sync.RWMutex\n") for entityName := range si.entities { code.WriteString(fmt.Sprintf("\t%s map[string]*%s\n", strings.ToLower(entityName)+"s", entityName)) } code.WriteString("}\n\n") code.WriteString("var db = &InMemoryDB{\n") for entityName := range si.entities { code.WriteString(fmt.Sprintf("\t%s: make(map[string]*%s),\n", strings.ToLower(entityName)+"s", entityName)) } code.WriteString("}\n\n") return code.String() } // generateEntityHandlers creates CRUD handlers for an entity func (si *ServerInterpreter) generateEntityHandlers(entity *lang.Entity) string { var code strings.Builder entityName := entity.Name entityLower := strings.ToLower(entityName) entityPlural := entityLower + "s" // List handler code.WriteString(fmt.Sprintf(`func list%sHandler(w http.ResponseWriter, r *http.Request) { db.mu.RLock() defer db.mu.RUnlock() var items []*%s for _, item := range db.%s { items = append(items, item) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(items) } `, entityName, entityName, entityPlural)) // Get by ID handler code.WriteString(fmt.Sprintf(`func get%sHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id := vars["id"] db.mu.RLock() item, exists := db.%s[id] db.mu.RUnlock() if !exists { http.Error(w, "%s not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(item) } `, entityName, entityPlural, entityName)) // Create handler code.WriteString(fmt.Sprintf(`func create%sHandler(w http.ResponseWriter, r *http.Request) { var item %s if err := json.NewDecoder(r.Body).Decode(&item); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } item.ID = uuid.New().String() item.CreatedAt = time.Now() item.UpdatedAt = time.Now() db.mu.Lock() db.%s[item.ID] = &item db.mu.Unlock() w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(item) } `, entityName, entityName, entityPlural)) // Update handler code.WriteString(fmt.Sprintf(`func update%sHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id := vars["id"] db.mu.Lock() defer db.mu.Unlock() existing, exists := db.%s[id] if !exists { http.Error(w, "%s not found", http.StatusNotFound) return } var updates %s if err := json.NewDecoder(r.Body).Decode(&updates); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } // Preserve ID and timestamps updates.ID = existing.ID updates.CreatedAt = existing.CreatedAt updates.UpdatedAt = time.Now() db.%s[id] = &updates w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(updates) } `, entityName, entityPlural, entityName, entityName, entityPlural)) // Delete handler code.WriteString(fmt.Sprintf(`func delete%sHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id := vars["id"] db.mu.Lock() defer db.mu.Unlock() if _, exists := db.%s[id]; !exists { http.Error(w, "%s not found", http.StatusNotFound) return } delete(db.%s, id) w.WriteHeader(http.StatusNoContent) } `, entityName, entityPlural, entityName, entityPlural)) return code.String() } // generateCustomEndpointHandler creates handlers for custom endpoints func (si *ServerInterpreter) generateCustomEndpointHandler(endpoint *lang.Endpoint) string { var code strings.Builder handlerName := fmt.Sprintf("%s%sHandler", strings.ToLower(endpoint.Method), strings.ReplaceAll(cases.Title(language.English).String(endpoint.Path), "/", "")) code.WriteString(fmt.Sprintf(`func %s(w http.ResponseWriter, r *http.Request) { // Custom endpoint: %s %s w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "message": "Custom endpoint %s %s", "status": "success", }) } `, handlerName, endpoint.Method, endpoint.Path, endpoint.Method, endpoint.Path)) return code.String() } // generateMainFunction creates the main function with routing func (si *ServerInterpreter) generateMainFunction() string { var code strings.Builder code.WriteString("func main() {\n") code.WriteString("\tr := mux.NewRouter()\n\n") // Add routes for each entity for entityName := range si.entities { entityLower := strings.ToLower(entityName) entityPlural := entityLower + "s" code.WriteString(fmt.Sprintf("\t// %s routes\n", entityName)) code.WriteString(fmt.Sprintf("\tr.HandleFunc(\"/%s\", list%sHandler).Methods(\"GET\")\n", entityPlural, entityName)) code.WriteString(fmt.Sprintf("\tr.HandleFunc(\"/%s/{id}\", get%sHandler).Methods(\"GET\")\n", entityPlural, entityName)) code.WriteString(fmt.Sprintf("\tr.HandleFunc(\"/%s\", create%sHandler).Methods(\"POST\")\n", entityPlural, entityName)) code.WriteString(fmt.Sprintf("\tr.HandleFunc(\"/%s/{id}\", update%sHandler).Methods(\"PUT\")\n", entityPlural, entityName)) code.WriteString(fmt.Sprintf("\tr.HandleFunc(\"/%s/{id}\", delete%sHandler).Methods(\"DELETE\")\n", entityPlural, entityName)) code.WriteString("\n") } // Add custom endpoint routes for _, endpoint := range si.endpoints { if endpoint.Entity == nil { handlerName := fmt.Sprintf("%s%sHandler", strings.ToLower(endpoint.Method), strings.ReplaceAll(cases.Title(language.English).String(endpoint.Path), "/", "")) code.WriteString(fmt.Sprintf("\tr.HandleFunc(\"%s\", %s).Methods(\"%s\")\n", endpoint.Path, handlerName, endpoint.Method)) } } // Server configuration host := "localhost" port := 8080 if si.server != nil { for _, setting := range si.server.Settings { if setting.Host != nil { host = *setting.Host } if setting.Port != nil { port = *setting.Port } } } code.WriteString(fmt.Sprintf("\n\taddr := \"%s:%d\"\n", host, port)) code.WriteString("\tfmt.Printf(\"Server starting on %s\\n\", addr)\n") code.WriteString("\tlog.Fatal(http.ListenAndServe(addr, r))\n") code.WriteString("}\n") return code.String() }