define a DSL

This commit is contained in:
2025-08-15 01:51:43 -06:00
parent 92e053d48a
commit e9a422ef07
7 changed files with 1598 additions and 0 deletions

18
.idea/copilotDiffState.xml generated Normal file

File diff suppressed because one or more lines are too long

157
debug.go Normal file
View File

@ -0,0 +1,157 @@
package main
import (
"fmt"
"io/ioutil"
"masonry/lang"
)
func main() {
// Read the example.masonry file
content, err := ioutil.ReadFile("example.masonry")
if err != nil {
fmt.Printf("Error reading example.masonry: %v\n", err)
return
}
input := string(content)
ast, err := lang.ParseInput(input)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("🎉 Successfully parsed complete DSL with pages!\n\n")
for _, def := range ast.Definitions {
if def.Server != nil {
fmt.Printf("📡 Server: %s\n", def.Server.Name)
for _, setting := range def.Server.Settings {
if setting.Host != nil {
fmt.Printf(" host: %s\n", *setting.Host)
}
if setting.Port != nil {
fmt.Printf(" port: %d\n", *setting.Port)
}
}
fmt.Printf("\n")
}
if def.Entity != nil {
entity := def.Entity
fmt.Printf("🏗️ Entity: %s", entity.Name)
if entity.Description != nil {
fmt.Printf(" - %s", *entity.Description)
}
fmt.Printf("\n")
for _, field := range entity.Fields {
fmt.Printf(" %s: %s", field.Name, field.Type)
if field.Required {
fmt.Printf(" (required)")
}
if field.Unique {
fmt.Printf(" (unique)")
}
if field.Default != nil {
fmt.Printf(" default=%s", *field.Default)
}
fmt.Printf("\n")
}
fmt.Printf("\n")
}
if def.Endpoint != nil {
endpoint := def.Endpoint
fmt.Printf("🚀 Endpoint: %s %s", endpoint.Method, endpoint.Path)
if endpoint.Entity != nil {
fmt.Printf(" (for %s)", *endpoint.Entity)
}
if endpoint.Description != nil {
fmt.Printf(" - %s", *endpoint.Description)
}
if endpoint.Auth {
fmt.Printf(" [AUTH]")
}
fmt.Printf("\n\n")
}
if def.Page != nil {
page := def.Page
fmt.Printf("🎨 Page: %s at %s", page.Name, page.Path)
if page.Title != nil {
fmt.Printf(" - %s", *page.Title)
}
if page.Auth {
fmt.Printf(" [AUTH]")
}
fmt.Printf("\n")
fmt.Printf(" Layout: %s\n", page.Layout)
for _, meta := range page.Meta {
fmt.Printf(" Meta %s: %s\n", meta.Name, meta.Content)
}
for _, comp := range page.Components {
fmt.Printf(" 📦 Component: %s", comp.Type)
if comp.Entity != nil {
fmt.Printf(" for %s", *comp.Entity)
}
fmt.Printf("\n")
for _, attr := range comp.Config {
if attr.Fields != nil {
fmt.Printf(" fields: %v\n", attr.Fields.Fields)
}
if attr.Actions != nil {
fmt.Printf(" actions: ")
for i, action := range attr.Actions.Actions {
if i > 0 {
fmt.Printf(", ")
}
fmt.Printf("%s", action.Name)
if action.Endpoint != nil {
fmt.Printf(" via %s", *action.Endpoint)
}
}
fmt.Printf("\n")
}
if attr.DataSource != nil {
fmt.Printf(" data from: %s\n", attr.DataSource.Endpoint)
}
if attr.Style != nil {
fmt.Printf(" style: %s", *attr.Style.Theme)
if len(attr.Style.Classes) > 0 {
fmt.Printf(" classes: %v", attr.Style.Classes)
}
fmt.Printf("\n")
}
if attr.Pagination != nil {
fmt.Printf(" pagination: enabled")
if attr.Pagination.PageSize != nil {
fmt.Printf(" size %d", *attr.Pagination.PageSize)
}
fmt.Printf("\n")
}
if attr.Filters != nil {
fmt.Printf(" filters: ")
for i, filter := range attr.Filters.Filters {
if i > 0 {
fmt.Printf(", ")
}
fmt.Printf("%s as %s", filter.Field, filter.Type)
if filter.Label != nil {
fmt.Printf(" (%s)", *filter.Label)
}
}
fmt.Printf("\n")
}
if attr.Validation {
fmt.Printf(" validation: enabled\n")
}
}
}
fmt.Printf("\n")
}
}
}
}

125
example.masonry Normal file
View File

@ -0,0 +1,125 @@
// Example Masonry DSL definition
// This demonstrates the comprehensive language structure
// Server configuration
server MyApp host "localhost" port 8080
// Entity definitions with various field types and relationships
entity User desc "User account management"
id: uuid required unique
email: string required validate email validate min_length "5"
name: string default "Anonymous"
created_at: timestamp default "now()"
profile_id: uuid relates to Profile as one via "user_id"
entity Profile desc "User profile information"
id: uuid required unique
user_id: uuid required relates to User as one
bio: text validate max_length "500"
avatar_url: string validate url
updated_at: timestamp
posts: uuid relates to Post as many
entity Post desc "Blog posts"
id: uuid required unique
title: string required validate min_length "1" validate max_length "200"
content: text required
author_id: uuid required relates to User as one
published: boolean default "false"
created_at: timestamp default "now()"
tags: uuid relates to Tag as many through "post_tags"
entity Tag desc "Content tags"
id: uuid required unique
name: string required unique validate min_length "1" validate max_length "50"
slug: string required unique indexed
created_at: timestamp default "now()"
// API Endpoints with different HTTP methods and parameter sources
endpoint GET "/users" for User desc "List users" auth
param page: int from query
param limit: int required from query
returns list as "json" fields [id, email, name]
endpoint POST "/users" for User desc "Create user"
param user_data: object required from body
returns object as "json" fields [id, email, name]
endpoint PUT "/users/{id}" for User desc "Update user"
param id: uuid required from path
param user_data: object required from body
returns object
custom "update_user_logic"
endpoint DELETE "/users/{id}" for User desc "Delete user" auth
param id: uuid required from path
returns object
endpoint GET "/posts" for Post desc "List posts"
param author_id: uuid from query
param published: boolean from query
param page: int from query
returns list as "json" fields [id, title, author_id, published]
endpoint POST "/posts" for Post desc "Create post" auth
param post_data: object required from body
returns object fields [id, title, content, author_id]
// Frontend pages with components
page UserManagement at "/admin/users" layout AdminLayout title "User Management" auth
meta description "Manage system users"
meta keywords "users, admin, management"
component Table for User
fields [email, name, id]
actions [edit via "/users/{id}", delete via "/users/{id}", create via "/users"]
data from "/users"
style modern classes ["table-striped", "table-hover"]
pagination size 20
filters [email as text label "Search email", name as text label "Search name"]
validate
component Form for User
fields [email, name]
actions [save via "/users", cancel]
style clean
validate
page UserList at "/users" layout MainLayout title "Users"
meta description "Browse all users"
component Table for User
fields [email, name]
data from "/users"
pagination size 10
filters [name as text label "Search by name"]
page PostManagement at "/admin/posts" layout AdminLayout title "Post Management" auth
meta description "Manage blog posts"
meta keywords "posts, blog, content"
component Table for Post
fields [title, author_id, published, created_at]
actions [edit via "/posts/{id}", delete via "/posts/{id}", create via "/posts"]
data from "/posts"
style modern
pagination size 15
filters [title as text label "Search title", published as select label "Published status"]
validate
page CreatePost at "/posts/new" layout MainLayout title "Create Post" auth
component Form for Post
fields [title, content]
actions [save via "/posts", cancel]
style clean
validate
page BlogList at "/blog" layout PublicLayout title "Blog Posts"
meta description "Read our latest blog posts"
meta keywords "blog, articles, content"
component Table for Post
fields [title, created_at]
data from "/posts"
pagination size 5
filters [title as text label "Search posts"]

1
go.mod
View File

@ -8,6 +8,7 @@ require (
)
require (
github.com/alecthomas/participle/v2 v2.1.4 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect

2
go.sum
View File

@ -1,3 +1,5 @@
github.com/alecthomas/participle/v2 v2.1.4 h1:W/H79S8Sat/krZ3el6sQMvMaahJ+XcM9WSI2naI7w2U=
github.com/alecthomas/participle/v2 v2.1.4/go.mod h1:8tqVbpTX20Ru4NfYQgZf4mP18eXPTBViyMWiArNEgGI=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=

181
lang/lang.go Normal file
View File

@ -0,0 +1,181 @@
package lang
import (
"github.com/alecthomas/participle/v2"
)
// Root AST node containing all definitions
type AST struct {
Definitions []Definition `parser:"@@*"`
}
// Union type for top-level definitions
type Definition struct {
Server *Server `parser:"@@"`
Entity *Entity `parser:"| @@"`
Endpoint *Endpoint `parser:"| @@"`
Page *Page `parser:"| @@"`
}
// Clean server syntax
type Server struct {
Name string `parser:"'server' @Ident"`
Settings []ServerSetting `parser:"@@*"`
}
type ServerSetting struct {
Host *string `parser:"('host' @String)"`
Port *int `parser:"| ('port' @Int)"`
}
// Clean entity syntax with better readability
type Entity struct {
Name string `parser:"'entity' @Ident"`
Description *string `parser:"('desc' @String)?"`
Fields []Field `parser:"@@*"`
}
// Much cleaner field syntax
type Field struct {
Name string `parser:"@Ident ':'"`
Type string `parser:"@Ident"`
Required bool `parser:"@'required'?"`
Unique bool `parser:"@'unique'?"`
Index bool `parser:"@'indexed'?"`
Default *string `parser:"('default' @String)?"`
Validations []Validation `parser:"@@*"`
Relationship *Relationship `parser:"@@?"`
}
// Simple validation syntax
type Validation struct {
Type string `parser:"'validate' @Ident"`
Value *string `parser:"@String?"`
}
// Clear relationship syntax
type Relationship struct {
Type string `parser:"'relates' 'to' @Ident"`
Cardinality string `parser:"'as' @('one' | 'many')"`
ForeignKey *string `parser:"('via' @String)?"`
Through *string `parser:"('through' @String)?"`
}
// Endpoint definitions with clean, readable syntax
type Endpoint struct {
Method string `parser:"'endpoint' @('GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH')"`
Path string `parser:"@String"`
Entity *string `parser:"('for' @Ident)?"`
Description *string `parser:"('desc' @String)?"`
Auth bool `parser:"@'auth'?"`
Params []EndpointParam `parser:"@@*"`
Response *ResponseSpec `parser:"@@?"`
CustomLogic *string `parser:"('custom' @String)?"`
}
// Clean parameter syntax
type EndpointParam struct {
Name string `parser:"'param' @Ident ':'"`
Type string `parser:"@Ident"`
Required bool `parser:"@'required'?"`
Source string `parser:"'from' @('path' | 'query' | 'body')"`
}
// Response specification
type ResponseSpec struct {
Type string `parser:"'returns' @Ident"`
Format *string `parser:"('as' @String)?"`
Fields []string `parser:"('fields' '[' @Ident (',' @Ident)* ']')?"`
}
// Page definitions for frontend with clean syntax
type Page struct {
Name string `parser:"'page' @Ident"`
Path string `parser:"'at' @String"`
Layout string `parser:"'layout' @Ident"`
Title *string `parser:"('title' @String)?"`
Description *string `parser:"('desc' @String)?"`
Auth bool `parser:"@'auth'?"`
Meta []MetaTag `parser:"@@*"`
Components []Component `parser:"@@*"`
}
// Meta tags for SEO
type MetaTag struct {
Name string `parser:"'meta' @Ident"`
Content string `parser:"@String"`
}
// Component definitions with endpoint references
type Component struct {
Type string `parser:"'component' @Ident"`
Entity *string `parser:"('for' @Ident)?"`
Config []ComponentAttr `parser:"@@*"`
}
// Component attributes and configurations
type ComponentAttr struct {
Fields *ComponentFields `parser:"@@"`
Actions *ComponentActions `parser:"| @@"`
DataSource *ComponentDataSource `parser:"| @@"`
Style *ComponentStyle `parser:"| @@"`
Pagination *ComponentPagination `parser:"| @@"`
Filters *ComponentFilters `parser:"| @@"`
Validation bool `parser:"| @'validate'"`
}
// Component field specification
type ComponentFields struct {
Fields []string `parser:"'fields' '[' @Ident (',' @Ident)* ']'"`
}
// Component actions (can reference endpoints)
type ComponentActions struct {
Actions []ComponentAction `parser:"'actions' '[' @@ (',' @@)* ']'"`
}
type ComponentAction struct {
Name string `parser:"@Ident"`
Endpoint *string `parser:"('via' @String)?"`
}
// Data source configuration (can reference endpoints)
type ComponentDataSource struct {
Endpoint string `parser:"'data' 'from' @String"`
}
// Component styling
type ComponentStyle struct {
Theme *string `parser:"'style' @Ident"`
Classes []string `parser:"('classes' '[' @String (',' @String)* ']')?"`
}
// Pagination configuration
type ComponentPagination struct {
PageSize *int `parser:"'pagination' ('size' @Int)?"`
}
// Filter specifications
type ComponentFilters struct {
Filters []ComponentFilter `parser:"'filters' '[' @@ (',' @@)* ']'"`
}
type ComponentFilter struct {
Field string `parser:"@Ident"`
Type string `parser:"'as' @('text' | 'select' | 'date' | 'number')"`
Label *string `parser:"('label' @String)?"`
}
func ParseInput(input string) (AST, error) {
parser, err := participle.Build[AST](
participle.Unquote("String"),
)
if err != nil {
return AST{}, err
}
ast, err := parser.ParseString("", input)
if err != nil {
return AST{}, err
}
return *ast, nil
}

1114
lang/lang_test.go Normal file

File diff suppressed because it is too large Load Diff