From 9b209dfe6334f170c5dd015be7f9071092bf1fd5 Mon Sep 17 00:00:00 2001 From: Mason Payne Date: Wed, 20 Aug 2025 00:13:41 -0600 Subject: [PATCH] try an SDK and a manually defined lang I don't think I like the SDK way, and langV2 seems to be so simialr to V1 that I'm probably going to stick with V1 for now and see if i can get it to do what I need. --- .idea/copilotDiffState.xml | 18 --- debug.go => examples/langV1/debug.go | 0 .../langV1/example.masonry | 0 examples/langV2/debug.go | 33 +++++ examples/langV2/example.masonry2 | 39 +++++ examples/sdk/debug_sdk.go | 136 ++++++++++++++++++ langV2/lang.go | 79 ++++++++++ sdk/sdk.go | 91 ++++++++++++ 8 files changed, 378 insertions(+), 18 deletions(-) delete mode 100644 .idea/copilotDiffState.xml rename debug.go => examples/langV1/debug.go (100%) rename example.masonry => examples/langV1/example.masonry (100%) create mode 100644 examples/langV2/debug.go create mode 100644 examples/langV2/example.masonry2 create mode 100644 examples/sdk/debug_sdk.go create mode 100644 langV2/lang.go create mode 100644 sdk/sdk.go diff --git a/.idea/copilotDiffState.xml b/.idea/copilotDiffState.xml deleted file mode 100644 index cf5d926..0000000 --- a/.idea/copilotDiffState.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/debug.go b/examples/langV1/debug.go similarity index 100% rename from debug.go rename to examples/langV1/debug.go diff --git a/example.masonry b/examples/langV1/example.masonry similarity index 100% rename from example.masonry rename to examples/langV1/example.masonry diff --git a/examples/langV2/debug.go b/examples/langV2/debug.go new file mode 100644 index 0000000..c757797 --- /dev/null +++ b/examples/langV2/debug.go @@ -0,0 +1,33 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "masonry/langV2" +) + +func main() { + // Read the example.masonry file + content, err := ioutil.ReadFile("example.masonry2") + if err != nil { + fmt.Printf("Error reading example.masonry: %v\n", err) + return + } + + input := string(content) + + ast, err := langV2.ParseInput(input) + if err != nil { + fmt.Printf("Error: %v\n", err) + } else { + fmt.Printf("🎉 Successfully parsed complete DSL with pages!\n\n") + + out, err := json.MarshalIndent(ast, "", " ") + if err != nil { + panic(err) + } + println(string(out)) + + } +} diff --git a/examples/langV2/example.masonry2 b/examples/langV2/example.masonry2 new file mode 100644 index 0000000..0f977de --- /dev/null +++ b/examples/langV2/example.masonry2 @@ -0,0 +1,39 @@ +entity User { + id: string required unique + email: string required unique validate email + name: string required + age: int validate min "18" + created_at: datetime +} + +entity Post { + id: string required unique + title: string required validate maxlen "100" + content: string required + author_id: string required relates to User as one via "user_id" + published: bool default "false" +} + +server UserService { + GET "/users" entity User + POST "/users" entity User + GET "/users/{id}" entity User + PUT "/users/{id}" entity User +} + +server BlogService { + GET "/posts" entity Post + POST "/posts" entity Post + GET "/posts/{id}" entity Post + DELETE "/posts/{id}" entity Post +} + +page UserProfile { + User from UserService + Post from BlogService +} + +page Dashboard { + User from UserService + Post from BlogService +} \ No newline at end of file diff --git a/examples/sdk/debug_sdk.go b/examples/sdk/debug_sdk.go new file mode 100644 index 0000000..28b9865 --- /dev/null +++ b/examples/sdk/debug_sdk.go @@ -0,0 +1,136 @@ +package main + +import ( + "encoding/json" + "masonry/sdk" +) + +func main() { + + userEntity := sdk.Entity{ + Name: "User", + Description: nil, + Fields: []sdk.Field{ + { + Name: "id", + Type: "int", + Required: true, + Unique: true, + Default: nil, + Validations: []sdk.Validation{ + { + Type: "validate", + Value: nil, + }, + }, + Relationship: nil, + }, + { + Name: "username", + Type: "string", + Required: true, + Unique: true, + Default: nil, + Validations: []sdk.Validation{ + { + Type: "validate", + Value: nil, + }, + }, + Relationship: nil, + }, + { + Name: "email", + Type: "string", + Required: true, + Unique: true, + Default: nil, + Validations: []sdk.Validation{ + { + Type: "validate", + Value: nil, + }, + }, + Relationship: nil, + }, + { + Name: "password", + Type: "string", + Required: true, + Unique: false, + Default: nil, + Validations: []sdk.Validation{ + { + Type: "validate", + Value: nil, + }, + }, + Relationship: nil, + }, + { + Name: "created_at", + Type: "datetime", + Required: true, + Unique: false, + Default: nil, + Validations: []sdk.Validation{ + { + Type: "validate", + Value: nil, + }, + }, + Relationship: nil, + }, + { + Name: "updated_at", + Type: "datetime", + Required: true, + Unique: false, + Default: nil, + Validations: []sdk.Validation{ + { + Type: "validate", + Value: nil, + }, + }, + Relationship: nil, + }, + }, + } + createUserDesc := "Create a new user with username, email, and password. Returns the created user object." + createUser := sdk.Endpoint{ + Method: "POST", + Path: "/user", + Entity: userEntity, + Description: &createUserDesc, + Auth: true, + Permissions: nil, + } + + server := sdk.Server{ + Name: "TestServer", + Settings: []sdk.ServerSetting{ + { + Host: "localhost", + Port: 8000, + }, + }, + Endpoints: []sdk.Endpoint{ + createUser, + }, + } + + def := sdk.Definition{ + Server: []sdk.Server{ + server, + }, + } + + out, err := json.MarshalIndent(def, "", " ") + if err != nil { + panic(err) + } + println(string(out)) + println("🎉 Successfully built SDK definition!") + println("You can now use this SDK definition in your application.") +} diff --git a/langV2/lang.go b/langV2/lang.go new file mode 100644 index 0000000..b601389 --- /dev/null +++ b/langV2/lang.go @@ -0,0 +1,79 @@ +package langV2 + +import ( + participle "github.com/alecthomas/participle/v2" +) + +// Root AST node containing all definitions +type AST struct { + Definitions []Definition `parser:"@@*"` +} + +type Definition struct { + Entity *EntityDef `parser:"@@"` + Server *ServerDef `parser:"| @@"` + Page *PageDef `parser:"| @@"` + Component *ComponentDef `parser:"| @@"` +} + +type EntityDef struct { + Name string `parser:"'entity' @Ident"` + Fields []Field `parser:"'{' @@* '}'"` +} + +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:"@@?"` +} + +type Validation struct { + Type string `parser:"'validate' @Ident"` + Value *string `parser:"@String?"` +} + +type Relationship struct { + Type string `parser:"'relates' 'to' @Ident"` + Cardinality string `parser:"'as' @('one' | 'many')"` + ForeignKey *string `parser:"('via' @String)?"` +} + +type ServerDef struct { + Name string `parser:"'server' @Ident"` + Endpoints []EndpointDef `parser:"'{' @@* '}'"` +} + +type EndpointDef struct { + Method string `parser:"@('GET'|'POST'|'PUT'|'DELETE')"` + Path string `parser:"@String"` + EntityRef string `parser:"'entity' @Ident"` +} + +type ComponentDef struct { + EntityRef string `parser:"@Ident"` + ServerRef string `parser:"('from' @Ident)?"` +} + +type PageDef struct { + Name string `parser:"'page' @Ident"` + Components []ComponentDef `parser:"'{' @@* '}'"` +} + +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 +} diff --git a/sdk/sdk.go b/sdk/sdk.go new file mode 100644 index 0000000..b8700eb --- /dev/null +++ b/sdk/sdk.go @@ -0,0 +1,91 @@ +package sdk + +type Definition struct { + Server []Server `json:"server,omitempty"` + Entity []Entity `json:"entity,omitempty"` + Endpoint []Endpoint `json:"endpoint,omitempty"` + Page []Page `json:"page,omitempty"` +} + +type Server struct { + Name string `json:"name"` + Settings []ServerSetting `json:"settings,omitempty"` + Endpoints []Endpoint `json:"endpoints,omitempty"` // Optional endpoints for the server +} + +type ServerSetting struct { + Host string `json:"host,omitempty"` + Port int `json:"port,omitempty"` + // TODO: Add more settings as needed e.g. TLS, CORS, etc. +} + +type Entity struct { + Name string `json:"name"` + Description *string `json:"description,omitempty"` + Fields []Field `json:"fields,omitempty"` +} + +type Field struct { + Name string `json:"name"` + Type string `json:"type"` + Required bool `json:"required,omitempty"` + Unique bool `json:"unique,omitempty"` + Index bool `json:"index,omitempty"` + Default *string `json:"default,omitempty"` + Validations []Validation `json:"validations,omitempty"` + Relationship *Relationship `json:"relationship,omitempty"` +} + +type Validation struct { + Type string `json:"type"` + Value *string `json:"value,omitempty"` +} + +type Relationship struct { + Type string `json:"type"` + Cardinality string `json:"cardinality"` + ForeignKey *string `json:"foreign_key,omitempty"` + Through *string `json:"through,omitempty"` +} + +type Endpoint struct { + Method string `json:"method"` + Path string `json:"path"` + Entity Entity `json:"entity,omitempty"` + Description *string `json:"description,omitempty"` + Auth bool `json:"auth,omitempty"` + Permissions []string `json:"permissions,omitempty"` +} + +type Page struct { + Name string `json:"name"` + Path string `json:"path"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` + Auth bool `json:"auth,omitempty"` + Permissions []string `json:"permissions,omitempty"` + Components []Component `json:"components,omitempty"` // List of components used in the page + Styles []string `json:"styles,omitempty"` // List of stylesheets or CSS + Script []string `json:"script,omitempty"` // List of scripts or JS files + Metadata map[string]string `json:"metadata,omitempty"` // Additional metadata for the page +} + +type Component struct { + Name string `json:"name"` // Name of the component + Description *string `json:"description,omitempty"` // Optional description of the component + Props []Prop `json:"props,omitempty"` // List of properties for the component + Type string `json:"type"` // Type of the component (e.g., "form", "table", etc.) + Entity Entity `json:"entity,omitempty"` // Optional entity this component is associated with + Styles []string `json:"styles,omitempty"` // List of stylesheets or CSS for the component + Script []string `json:"script,omitempty"` // List of scripts or JS files for the component +} + +type Prop struct { + Name string `json:"name"` // Name of the property + Description *string `json:"description,omitempty"` // Optional description of the property + Type string `json:"type"` // Type of the property (e.g., "string", "number", "boolean", etc.) + Required bool `json:"required,omitempty"` // Whether the property is required + Default *string `json:"default,omitempty"` // Default value for the property + Validations []Validation `json:"validations,omitempty"` // List of validations for the property + Relationship *Relationship `json:"relationship,omitempty"` // Optional relationship +}