Added a DSL?
This commit is contained in:
@@ -22,18 +22,65 @@ entryPoints:
|
|||||||
A case of fan-in (get all posts and group by tags) and fan-out (generate all tag pages with back-links to posts)
|
A case of fan-in (get all posts and group by tags) and fan-out (generate all tag pages with back-links to posts)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
entryPoints:
|
build:
|
||||||
...
|
# Render homepage
|
||||||
- source: posts/{id}.md
|
- pipeline:
|
||||||
pipeline:
|
- source: index.html
|
||||||
- plugin: frontmatter
|
- use: layout
|
||||||
- plugin: group
|
path: layout/base.html
|
||||||
metadataKey: tag
|
- target: dist/index.html
|
||||||
key: tags
|
# Render each post
|
||||||
pipeline:
|
- pipeline:
|
||||||
- layout: layouts/tag.html
|
- source: posts/{id}.md
|
||||||
- layout: layouts/base.html
|
- use: markdown
|
||||||
- target: dist/tags/{tag}/index.html # ...{tag}... is the same as "metadataKey" (?)
|
- use: layout
|
||||||
|
path: layouts/base.html
|
||||||
|
- target: dist/posts/{id}/index.html
|
||||||
|
# Render "posts" page
|
||||||
|
- pipeline:
|
||||||
|
- source: posts/{id}.md
|
||||||
|
- use: frontmatter
|
||||||
|
- use: sort
|
||||||
|
key: publish_date
|
||||||
|
direction: descending
|
||||||
|
- use: slice
|
||||||
|
from: 0
|
||||||
|
to: 10
|
||||||
|
- use: template
|
||||||
|
path: layouts/list.html
|
||||||
|
- use: layout
|
||||||
|
path: layouts/base.html
|
||||||
|
- target: dist/posts/index.html
|
||||||
|
# Render next pages for "posts" page
|
||||||
|
- pipeline:
|
||||||
|
- source: posts/{id}.md
|
||||||
|
- use: frontmatter
|
||||||
|
- use: sort
|
||||||
|
key: publish_date
|
||||||
|
direction: descending
|
||||||
|
- use: slice
|
||||||
|
from: 10
|
||||||
|
to: end
|
||||||
|
- use: chunk # paginate items
|
||||||
|
size: 10
|
||||||
|
pipeline: # this pipeline gets called with every [size * n, size * n + size] range of items
|
||||||
|
- use: template # aggregate this items chunk in a single item
|
||||||
|
path: layouts/list.html
|
||||||
|
- use: layout
|
||||||
|
path: layouts/base.html
|
||||||
|
- target: dist/posts/{.Chunk.Index}/index.html
|
||||||
|
# Render "/tags/{tag}/" pages
|
||||||
|
- pipeline:
|
||||||
|
- source: posts/{id}.md
|
||||||
|
- use: frontmatter
|
||||||
|
- use: categorize
|
||||||
|
key: tags # each post contains a metadata field called "tags"
|
||||||
|
pipeline: # this pipeline gets called with all posts relative to one category
|
||||||
|
- use: template
|
||||||
|
path: layouts/tag.html
|
||||||
|
- use: layout
|
||||||
|
path: layouts/base.html
|
||||||
|
- target: dist/tags/{category}/index.html
|
||||||
```
|
```
|
||||||
|
|
||||||
### Pagination
|
### Pagination
|
||||||
@@ -54,4 +101,67 @@ entryPoints:
|
|||||||
pipeline:
|
pipeline:
|
||||||
- layout: layouts/list.html
|
- layout: layouts/list.html
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom DSL
|
||||||
|
|
||||||
|
```
|
||||||
|
12
|
||||||
|
"text"
|
||||||
|
#symbol
|
||||||
|
#(None)
|
||||||
|
#(Some 123)
|
||||||
|
true
|
||||||
|
false
|
||||||
|
[1 2 3]
|
||||||
|
{ a = 1, b = 2 }
|
||||||
|
{
|
||||||
|
a = 1
|
||||||
|
b = 2
|
||||||
|
}
|
||||||
|
fn 1 2 3
|
||||||
|
|
||||||
|
# Example
|
||||||
|
build [
|
||||||
|
pipeline [
|
||||||
|
source "index.html"
|
||||||
|
layout "layout/base.html"
|
||||||
|
target "dist/index.html"
|
||||||
|
]
|
||||||
|
pipeline [
|
||||||
|
source "posts/{id}.html"
|
||||||
|
markdown
|
||||||
|
layout "layout/base.html"
|
||||||
|
target "dist/posts/{{ .Id }}/index.html"
|
||||||
|
]
|
||||||
|
pipeline [
|
||||||
|
source "posts/{id}.md"
|
||||||
|
frontmatter
|
||||||
|
sort #descending { key = "publish_date" }
|
||||||
|
slice { to = 10 }
|
||||||
|
template "layouts/list.html"
|
||||||
|
layout "layouts/base.html"
|
||||||
|
target "dist/posts/index.html"
|
||||||
|
]
|
||||||
|
pipeline [
|
||||||
|
source "posts/{id}.md"
|
||||||
|
frontmatter
|
||||||
|
sort #descending { key = "publish_date" }
|
||||||
|
slice { from = 10 }
|
||||||
|
chunk 10 {
|
||||||
|
template "layouts/list.html"
|
||||||
|
layout "layouts/base.html"
|
||||||
|
target "dist/posts/{{ .Chunk.Index }}/index.html"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
pipeline [
|
||||||
|
source "posts/{id}.md"
|
||||||
|
frontmatter
|
||||||
|
categorize "tags" {
|
||||||
|
template "layouts/tag.html"
|
||||||
|
layout "layouts/base.html"
|
||||||
|
target "dist/tags/{{ .Category }}/index.html"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
```
|
```
|
||||||
@@ -20,6 +20,32 @@ type Content struct {
|
|||||||
Metadata Map
|
Metadata Map
|
||||||
}
|
}
|
||||||
|
|
||||||
type Operation interface {
|
type ListOperation interface {
|
||||||
Process(content Content) (*Content, error)
|
MapAll(contents []Content) ([]Content, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ItemOperation interface {
|
||||||
|
FlatMap(content Content) (*Content, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlatMapToMapAll struct{ FlatMapOperation }
|
||||||
|
|
||||||
|
func (op FlatMapToMapAll) MapAll(contents []Content) ([]Content, error) {
|
||||||
|
mapped := []Content{}
|
||||||
|
|
||||||
|
for _, item := range contents {
|
||||||
|
result, err := op.FlatMap(item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip terminal operations
|
||||||
|
if result == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
mapped = append(mapped, *result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapped, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-7
@@ -7,13 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Operation is an enum of various operations
|
// Operation is an enum of various operations
|
||||||
type Operation struct {
|
type Operation map[string]any
|
||||||
Layout string `yaml:",omitempty"`
|
|
||||||
Target string `yaml:",omitempty"`
|
|
||||||
Plugin string `yaml:",omitempty"`
|
|
||||||
|
|
||||||
Options map[string]any `yaml:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type EntryPoint struct {
|
type EntryPoint struct {
|
||||||
Source string `yaml:",omitempty"`
|
Source string `yaml:",omitempty"`
|
||||||
|
|||||||
+26
-17
@@ -19,24 +19,33 @@ type matchResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BuildOperation(op config.Operation) cabret.Operation {
|
func BuildOperation(op config.Operation) cabret.Operation {
|
||||||
switch {
|
if path, ok := op["read"]; ok {
|
||||||
case op.Layout != "":
|
return cabret.FlatMapToMapAll{&operation.Target{
|
||||||
path := op.Layout
|
PathTemplate: path.(string),
|
||||||
return &operation.Layout{
|
}}
|
||||||
TemplateFilesPattern: path,
|
}
|
||||||
Options: op.Options,
|
if path, ok := op["write"]; ok {
|
||||||
|
return cabret.FlatMapToMapAll{&operation.Target{
|
||||||
|
PathTemplate: path.(string),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
if name, ok := op["plugin"]; ok {
|
||||||
|
switch name {
|
||||||
|
case "layout":
|
||||||
|
path := op["path"].(string)
|
||||||
|
delete(op, "path")
|
||||||
|
|
||||||
|
return cabret.FlatMapToMapAll{&operation.Layout{
|
||||||
|
TemplateFilesPattern: path,
|
||||||
|
Options: op,
|
||||||
|
}}
|
||||||
|
case "markdown":
|
||||||
|
return cabret.FlatMapToMapAll{&operation.Markdown{
|
||||||
|
Options: op,
|
||||||
|
}}
|
||||||
|
default:
|
||||||
|
log.Fatalf(`invalid operation: %s`, name)
|
||||||
}
|
}
|
||||||
case op.Target != "":
|
|
||||||
path := op.Target
|
|
||||||
return &operation.Target{
|
|
||||||
PathTemplate: path,
|
|
||||||
}
|
|
||||||
case op.Plugin == "markdown":
|
|
||||||
return &operation.Markdown{
|
|
||||||
Options: op.Options,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
log.Fatalf(`invalid operation: %s`, op.Plugin)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -5,12 +5,15 @@ go 1.19
|
|||||||
require (
|
require (
|
||||||
github.com/alecthomas/participle/v2 v2.0.0-beta.5 // indirect
|
github.com/alecthomas/participle/v2 v2.0.0-beta.5 // indirect
|
||||||
github.com/alecthomas/repr v0.1.1 // indirect
|
github.com/alecthomas/repr v0.1.1 // indirect
|
||||||
|
github.com/bradleyjkemp/cupaloy v2.3.0+incompatible // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/fatih/color v1.13.0 // indirect
|
github.com/fatih/color v1.13.0 // indirect
|
||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
github.com/iancoleman/strcase v0.2.0 // indirect
|
github.com/iancoleman/strcase v0.2.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rakyll/gotest v0.0.6 // indirect
|
github.com/rakyll/gotest v0.0.6 // indirect
|
||||||
github.com/yuin/goldmark v1.5.3 // indirect
|
github.com/yuin/goldmark v1.5.3 // indirect
|
||||||
github.com/yuin/goldmark-meta v1.1.0 // indirect
|
github.com/yuin/goldmark-meta v1.1.0 // indirect
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ github.com/alecthomas/participle/v2 v2.0.0-beta.5 h1:y6dsSYVb1G5eK6mgmy+BgI3Mw35
|
|||||||
github.com/alecthomas/participle/v2 v2.0.0-beta.5/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM=
|
github.com/alecthomas/participle/v2 v2.0.0-beta.5/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM=
|
||||||
github.com/alecthomas/repr v0.1.1 h1:87P60cSmareLAxMc4Hro0r2RBY4ROm0dYwkJNpS4pPs=
|
github.com/alecthomas/repr v0.1.1 h1:87P60cSmareLAxMc4Hro0r2RBY4ROm0dYwkJNpS4pPs=
|
||||||
github.com/alecthomas/repr v0.1.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.1.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
|
github.com/bradleyjkemp/cupaloy v2.3.0+incompatible h1:UafIjBvWQmS9i/xRg+CamMrnLTKNzo+bdmT/oH34c2Y=
|
||||||
|
github.com/bradleyjkemp/cupaloy v2.3.0+incompatible/go.mod h1:Au1Xw1sgaJ5iSFktEhYsS0dbQiS1B0/XMXl+42y9Ilk=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
@@ -21,6 +25,8 @@ github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peK
|
|||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rakyll/gotest v0.0.6 h1:hBTqkO3jiuwYW/M9gL4bu0oTYcm8J6knQAAPUsJsz1I=
|
github.com/rakyll/gotest v0.0.6 h1:hBTqkO3jiuwYW/M9gL4bu0oTYcm8J6knQAAPUsJsz1I=
|
||||||
github.com/rakyll/gotest v0.0.6/go.mod h1:SkoesdNCWmiD4R2dljIUcfSnNdVZ12y8qK4ojDkc2Sc=
|
github.com/rakyll/gotest v0.0.6/go.mod h1:SkoesdNCWmiD4R2dljIUcfSnNdVZ12y8qK4ojDkc2Sc=
|
||||||
github.com/yuin/goldmark v1.5.3 h1:3HUJmBFbQW9fhQOzMgseU134xfi6hU+mjWywx5Ty+/M=
|
github.com/yuin/goldmark v1.5.3 h1:3HUJmBFbQW9fhQOzMgseU134xfi6hU+mjWywx5Ty+/M=
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package operation
|
||||||
|
|
||||||
|
import "github.com/aziis98/cabret"
|
||||||
|
|
||||||
|
type GroupBy struct {
|
||||||
|
Key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (op GroupBy) Process(content cabret.Content) (*cabret.Content, error) {
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
+2
-2
@@ -17,7 +17,7 @@ import (
|
|||||||
|
|
||||||
var HtmlMimeType = mime.TypeByExtension(".html")
|
var HtmlMimeType = mime.TypeByExtension(".html")
|
||||||
|
|
||||||
var _ cabret.Operation = Layout{}
|
var _ cabret.FlatMapOperation = Layout{}
|
||||||
|
|
||||||
type Layout struct {
|
type Layout struct {
|
||||||
// TemplateFilesPattern is a comma separated list of unix glob patterns
|
// TemplateFilesPattern is a comma separated list of unix glob patterns
|
||||||
@@ -26,7 +26,7 @@ type Layout struct {
|
|||||||
Options map[string]any
|
Options map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op Layout) Process(content cabret.Content) (*cabret.Content, error) {
|
func (op Layout) FlatMap(content cabret.Content) (*cabret.Content, error) {
|
||||||
var tmpl layout.Template
|
var tmpl layout.Template
|
||||||
|
|
||||||
patterns := strings.Split(op.TemplateFilesPattern, ",")
|
patterns := strings.Split(op.TemplateFilesPattern, ",")
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ import (
|
|||||||
"github.com/yuin/goldmark/parser"
|
"github.com/yuin/goldmark/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ cabret.FlatMapOperation = Markdown{}
|
||||||
|
|
||||||
type Markdown struct {
|
type Markdown struct {
|
||||||
Options map[string]any
|
Options map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op Markdown) Process(content cabret.Content) (*cabret.Content, error) {
|
func (op Markdown) FlatMap(content cabret.Content) (*cabret.Content, error) {
|
||||||
md := goldmark.New(
|
md := goldmark.New(
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
extension.GFM,
|
extension.GFM,
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package operation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mime"
|
||||||
|
"os"
|
||||||
|
gopath "path"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/aziis98/cabret"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ cabret.ListOperation = Read{}
|
||||||
|
|
||||||
|
type Read struct {
|
||||||
|
Patterns []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (op Read) MapAll(contents []cabret.Content) ([]cabret.Content, error) {
|
||||||
|
for _, pattern := range op.Patterns {
|
||||||
|
files, err := filepath.Glob(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
data, err := os.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contents = append(contents, cabret.Content{
|
||||||
|
Type: mime.TypeByExtension(gopath.Ext(file)),
|
||||||
|
Data: data,
|
||||||
|
Metadata: cabret.Map{},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contents, nil
|
||||||
|
}
|
||||||
+2
-2
@@ -10,13 +10,13 @@ import (
|
|||||||
"github.com/aziis98/cabret/path"
|
"github.com/aziis98/cabret/path"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ cabret.Operation = Target{}
|
var _ cabret.FlatMapOperation = Target{}
|
||||||
|
|
||||||
type Target struct {
|
type Target struct {
|
||||||
PathTemplate string
|
PathTemplate string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op Target) Process(c cabret.Content) (*cabret.Content, error) {
|
func (op Target) FlatMap(c cabret.Content) (*cabret.Content, error) {
|
||||||
mr, ok := c.Metadata[cabret.MatchResult].(map[string]string)
|
mr, ok := c.Metadata[cabret.MatchResult].(map[string]string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf(`invalid match result type %T`, c.Metadata[cabret.MatchResult])
|
return nil, fmt.Errorf(`invalid match result type %T`, c.Metadata[cabret.MatchResult])
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
(*script.Program)({
|
||||||
|
Statements: ([]script.Expression) (len=1) {
|
||||||
|
(*script.FunctionCall)({
|
||||||
|
Receiver: (*script.Identifier)({
|
||||||
|
Value: (string) (len=5) "match"
|
||||||
|
}),
|
||||||
|
Arguments: ([]script.ArgumentExpression) (len=2) {
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.Identifier)({
|
||||||
|
Value: (string) (len=1) "x"
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
}),
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.ListExpression)({
|
||||||
|
Values: ([]script.Expression) (len=2) {
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.Quote)({
|
||||||
|
Value: (*script.Identifier)({
|
||||||
|
Value: (string) (len=4) "None"
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
Operator: (string) (len=2) "->",
|
||||||
|
Rhs: (*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.LiteralNumber)({
|
||||||
|
Value: (float64) 0
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.Quote)({
|
||||||
|
Value: (*script.ParenthesizedExpression)({
|
||||||
|
Inner: (*script.FunctionCall)({
|
||||||
|
Receiver: (*script.Identifier)({
|
||||||
|
Value: (string) (len=4) "Some"
|
||||||
|
}),
|
||||||
|
Arguments: ([]script.ArgumentExpression) (len=1) {
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.Identifier)({
|
||||||
|
Value: (string) (len=5) "value"
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
Operator: (string) (len=2) "->",
|
||||||
|
Rhs: (*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.Identifier)({
|
||||||
|
Value: (string) (len=5) "value"
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
(*script.Program)({
|
||||||
|
Statements: ([]script.Expression) (len=1) {
|
||||||
|
(*script.FunctionCall)({
|
||||||
|
Receiver: (*script.Identifier)({
|
||||||
|
Value: (string) (len=3) "for"
|
||||||
|
}),
|
||||||
|
Arguments: ([]script.ArgumentExpression) (len=3) {
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.Identifier)({
|
||||||
|
Value: (string) (len=1) "n"
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
}),
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.ParenthesizedExpression)({
|
||||||
|
Inner: (*script.FunctionCall)({
|
||||||
|
Receiver: (*script.Identifier)({
|
||||||
|
Value: (string) (len=5) "range"
|
||||||
|
}),
|
||||||
|
Arguments: ([]script.ArgumentExpression) (len=2) {
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.LiteralNumber)({
|
||||||
|
Value: (float64) 1
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
}),
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.LiteralNumber)({
|
||||||
|
Value: (float64) 10
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
}),
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.ListExpression)({
|
||||||
|
Values: ([]script.Expression) (len=2) {
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.Identifier)({
|
||||||
|
Value: (string) (len=1) "m"
|
||||||
|
}),
|
||||||
|
Operator: (string) (len=2) ":=",
|
||||||
|
Rhs: (*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.Identifier)({
|
||||||
|
Value: (string) (len=1) "n"
|
||||||
|
}),
|
||||||
|
Operator: (string) (len=1) "*",
|
||||||
|
Rhs: (*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.Identifier)({
|
||||||
|
Value: (string) (len=1) "n"
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
(*script.FunctionCall)({
|
||||||
|
Receiver: (*script.Identifier)({
|
||||||
|
Value: (string) (len=8) "printfln"
|
||||||
|
}),
|
||||||
|
Arguments: ([]script.ArgumentExpression) (len=2) {
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.LiteralString)({
|
||||||
|
Value: (string) (len=8) "n^2 = %v"
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
}),
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.Identifier)({
|
||||||
|
Value: (string) (len=1) "m"
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
(*script.Program)({
|
||||||
|
Statements: ([]script.Expression) (len=1) {
|
||||||
|
(*script.FunctionCall)({
|
||||||
|
Receiver: (*script.Identifier)({
|
||||||
|
Value: (string) (len=3) "foo"
|
||||||
|
}),
|
||||||
|
Arguments: ([]script.ArgumentExpression) (len=3) {
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.ListExpression)({
|
||||||
|
Values: ([]script.Expression) (len=3) {
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.LiteralNumber)({
|
||||||
|
Value: (float64) 1
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
}),
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.LiteralNumber)({
|
||||||
|
Value: (float64) 2
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
}),
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.LiteralNumber)({
|
||||||
|
Value: (float64) 3
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
}),
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.DictExpression)({
|
||||||
|
Entries: ([]*script.DictEntry) (len=2) {
|
||||||
|
(*script.DictEntry)({
|
||||||
|
Key: (*script.Identifier)({
|
||||||
|
Value: (string) (len=1) "a"
|
||||||
|
}),
|
||||||
|
Value: (*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.LiteralNumber)({
|
||||||
|
Value: (float64) 1
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
(*script.DictEntry)({
|
||||||
|
Key: (*script.Identifier)({
|
||||||
|
Value: (string) (len=1) "b"
|
||||||
|
}),
|
||||||
|
Value: (*script.FunctionCall)({
|
||||||
|
Receiver: (*script.Identifier)({
|
||||||
|
Value: (string) (len=3) "foo"
|
||||||
|
}),
|
||||||
|
Arguments: ([]script.ArgumentExpression) (len=2) {
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.Quote)({
|
||||||
|
Value: (*script.Identifier)({
|
||||||
|
Value: (string) (len=5) "other"
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
}),
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.DictExpression)({
|
||||||
|
Entries: ([]*script.DictEntry) (len=1) {
|
||||||
|
(*script.DictEntry)({
|
||||||
|
Key: (*script.Identifier)({
|
||||||
|
Value: (string) (len=3) "bar"
|
||||||
|
}),
|
||||||
|
Value: (*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.LiteralNumber)({
|
||||||
|
Value: (float64) 456
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
}),
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.ListExpression)({
|
||||||
|
Values: ([]script.Expression) (len=3) {
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.LiteralNumber)({
|
||||||
|
Value: (float64) 4
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
}),
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.LiteralNumber)({
|
||||||
|
Value: (float64) 5
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
}),
|
||||||
|
(*script.BinaryOperation)({
|
||||||
|
Lhs: (*script.LiteralNumber)({
|
||||||
|
Value: (float64) 6
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Operator: (string) "",
|
||||||
|
Rhs: (script.Expression) <nil>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
# Script
|
||||||
|
|
||||||
|
Mini scripting language for querying the website "model".
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
### Values
|
||||||
|
|
||||||
|
```go
|
||||||
|
1
|
||||||
|
-1
|
||||||
|
0xFF
|
||||||
|
3.14
|
||||||
|
"some text"
|
||||||
|
'r' 'u' 'n' 'e'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Property Access
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Primitives
|
||||||
|
12
|
||||||
|
-3.14
|
||||||
|
"text"
|
||||||
|
|
||||||
|
-- Meta
|
||||||
|
#symbol
|
||||||
|
#(None)
|
||||||
|
#(Some 123)
|
||||||
|
|
||||||
|
-- Booleans
|
||||||
|
true
|
||||||
|
false
|
||||||
|
|
||||||
|
-- shorter form
|
||||||
|
#t
|
||||||
|
#f
|
||||||
|
|
||||||
|
-- Lists
|
||||||
|
[1 2 3]
|
||||||
|
|
||||||
|
-- Dicts
|
||||||
|
{ a = 1, b = 2 }
|
||||||
|
{
|
||||||
|
a = 1
|
||||||
|
b = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Function call
|
||||||
|
fn 1 2 3
|
||||||
|
|
||||||
|
-- as long as it continues "inline" its fine and still a single expression
|
||||||
|
fn [1 2
|
||||||
|
3] {
|
||||||
|
a = 1
|
||||||
|
b = (foo #other {
|
||||||
|
bar = 456
|
||||||
|
})
|
||||||
|
} [
|
||||||
|
4 5 6
|
||||||
|
]
|
||||||
|
|
||||||
|
-- Arithmetic (no precedence)
|
||||||
|
-- anything matching the following regex is considered an operator
|
||||||
|
[+-*/%<>=&|^?#@]+
|
||||||
|
|
||||||
|
-- Control Flow (just builtin macros)
|
||||||
|
if cond trueCaseExpr
|
||||||
|
ifelse cond trueCaseExpr falseCaseExpr
|
||||||
|
|
||||||
|
match value [
|
||||||
|
case valuePattern1 case1Expr
|
||||||
|
case valuePattern2 case2Expr
|
||||||
|
...
|
||||||
|
default defaultExpr
|
||||||
|
]
|
||||||
|
|
||||||
|
for var items body
|
||||||
|
|
||||||
|
-- Functions
|
||||||
|
fn [a] [
|
||||||
|
printfln "a = %v" a
|
||||||
|
]
|
||||||
|
|
||||||
|
fn example [a b] [
|
||||||
|
printfln "a + b = %v" (a + b)
|
||||||
|
]
|
||||||
|
|
||||||
|
example 42 69
|
||||||
|
|
||||||
|
-- Example
|
||||||
|
build [
|
||||||
|
pipeline [
|
||||||
|
source "index.html"
|
||||||
|
layout "layout/base.html"
|
||||||
|
target "dist/index.html"
|
||||||
|
]
|
||||||
|
pipeline [
|
||||||
|
source "posts/{id}.html"
|
||||||
|
markdown
|
||||||
|
layout "layout/base.html"
|
||||||
|
target "dist/posts/{{ .Id }}/index.html"
|
||||||
|
]
|
||||||
|
pipeline [
|
||||||
|
source "posts/{id}.md"
|
||||||
|
frontmatter
|
||||||
|
sort #descending { key = "publish_date" }
|
||||||
|
slice { to = 10 }
|
||||||
|
template "layouts/list.html"
|
||||||
|
layout "layouts/base.html"
|
||||||
|
target "dist/posts/index.html"
|
||||||
|
]
|
||||||
|
pipeline [
|
||||||
|
source "posts/{id}.md"
|
||||||
|
frontmatter
|
||||||
|
sort #descending { key = "publish_date" }
|
||||||
|
slice { from = 10 }
|
||||||
|
chunk 10 {
|
||||||
|
template "layouts/list.html"
|
||||||
|
layout "layouts/base.html"
|
||||||
|
target "dist/posts/{{ .Chunk.Index }}/index.html"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
pipeline [
|
||||||
|
source "posts/{id}.md"
|
||||||
|
frontmatter
|
||||||
|
categorize "tags" {
|
||||||
|
template "layouts/tag.html"
|
||||||
|
layout "layouts/base.html"
|
||||||
|
target "dist/tags/{{ .Category }}/index.html"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
```
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package script
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/alecthomas/participle/v2"
|
||||||
|
"github.com/alecthomas/participle/v2/lexer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Program struct {
|
||||||
|
Statements []Expression `parser:"Newline? ( @@ ( Newline @@ )* Newline? )?"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArgumentExpression interface{}
|
||||||
|
|
||||||
|
type Expression interface{}
|
||||||
|
|
||||||
|
type FunctionReceiver interface{}
|
||||||
|
|
||||||
|
var _ FunctionReceiver = &Identifier{}
|
||||||
|
var _ FunctionReceiver = &ParenthesizedExpression{}
|
||||||
|
|
||||||
|
type FunctionCall struct {
|
||||||
|
Receiver FunctionReceiver `parser:"@@"`
|
||||||
|
Arguments []ArgumentExpression `parser:"@@+"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParenthesizedExpression struct {
|
||||||
|
Inner Expression `parser:"'(' @@ ')'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListExpression struct {
|
||||||
|
Values []Expression `parser:"'[' ( Newline? @@ ( Newline? @@ )* Newline? )? ']'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DictExpression struct {
|
||||||
|
Entries []*DictEntry `parser:"'{' ( Newline? @@ ( ( ',' | Newline ) @@ )* Newline? )? '}'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DictEntry struct {
|
||||||
|
Key DictEntryKey `parser:"@@"`
|
||||||
|
Value Expression `parser:"'=' @@"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DictEntryKey interface{}
|
||||||
|
|
||||||
|
type Boolean bool
|
||||||
|
|
||||||
|
func (b *Boolean) Capture(values []string) error {
|
||||||
|
*b = values[0] == "true"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type LiteralBoolean struct {
|
||||||
|
Value Boolean `parser:"@('true' | 'false')"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LiteralString struct {
|
||||||
|
Value string `parser:"@String"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LiteralNumber struct {
|
||||||
|
Value float64 `parser:"@Number"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Identifier struct {
|
||||||
|
Value string `parser:"@Ident"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Quote struct {
|
||||||
|
Value Quotation `parser:"'#' @@"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Quotation interface{}
|
||||||
|
|
||||||
|
type ExpressionFactor interface{}
|
||||||
|
|
||||||
|
type BinaryOperation struct {
|
||||||
|
Lhs ExpressionFactor `parser:"@@"`
|
||||||
|
Operator string `parser:"( @Operator"`
|
||||||
|
Rhs Expression `parser:" @@ )?"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
scriptLexer = lexer.MustSimple([]lexer.SimpleRule{
|
||||||
|
{Name: "Comment", Pattern: `--[^\n]*\n?`},
|
||||||
|
{Name: "String", Pattern: `"(\\"|[^"])*"`},
|
||||||
|
{Name: "Number", Pattern: `[-+]?(?:\d*\.)?\d+`},
|
||||||
|
{Name: "Ident", Pattern: `[a-zA-Z]\w*`},
|
||||||
|
{Name: "Meta", Pattern: `\#`},
|
||||||
|
// an operator is any combination of [+-*/%<>=:;,&|^?#@] expect for the string "="
|
||||||
|
{Name: "Operator", Pattern: `(=[\+\-\*/%<>=:&|\^?#@]+)|([\+\-\*/%<>:&|\^?#@][\+\-\*/%<>=:&|\^?#@]*)`},
|
||||||
|
{Name: "Punct", Pattern: `[\(\)\{\}\[\]=,]`},
|
||||||
|
{Name: "Newline", Pattern: `[ \t]*\n\s*`},
|
||||||
|
{Name: "Whitespace", Pattern: `[ \t]+`},
|
||||||
|
})
|
||||||
|
// Parser
|
||||||
|
Parser = participle.MustBuild[Program](
|
||||||
|
participle.Lexer(scriptLexer),
|
||||||
|
participle.Elide("Comment", "Whitespace"),
|
||||||
|
participle.Unquote("String"),
|
||||||
|
participle.Union[Expression](
|
||||||
|
&FunctionCall{},
|
||||||
|
&BinaryOperation{},
|
||||||
|
),
|
||||||
|
participle.Union[ArgumentExpression](
|
||||||
|
&BinaryOperation{},
|
||||||
|
),
|
||||||
|
participle.Union[ExpressionFactor](
|
||||||
|
&ListExpression{},
|
||||||
|
&DictExpression{},
|
||||||
|
&LiteralNumber{},
|
||||||
|
&LiteralString{},
|
||||||
|
&LiteralBoolean{},
|
||||||
|
&ParenthesizedExpression{},
|
||||||
|
&Quote{},
|
||||||
|
&Identifier{},
|
||||||
|
),
|
||||||
|
participle.Union[Quotation](
|
||||||
|
&Identifier{},
|
||||||
|
&ParenthesizedExpression{},
|
||||||
|
),
|
||||||
|
participle.Union[DictEntryKey](
|
||||||
|
&Identifier{},
|
||||||
|
&ParenthesizedExpression{},
|
||||||
|
),
|
||||||
|
participle.Union[FunctionReceiver](
|
||||||
|
&Identifier{},
|
||||||
|
&ParenthesizedExpression{},
|
||||||
|
),
|
||||||
|
participle.UseLookahead(2),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func Parse(source string) (*Program, error) {
|
||||||
|
return Parser.ParseString("", source, participle.Trace(os.Stdout))
|
||||||
|
// return Parser.ParseString("", source)
|
||||||
|
}
|
||||||
@@ -0,0 +1,378 @@
|
|||||||
|
package script_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aziis98/cabret/script"
|
||||||
|
"github.com/bradleyjkemp/cupaloy"
|
||||||
|
"gotest.tools/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParsing(t *testing.T) {
|
||||||
|
t.Run("Number", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
-3.14
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, program,
|
||||||
|
&script.Program{
|
||||||
|
Statements: []script.Expression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralNumber{-3.14}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("String", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
"Hello, World!"
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, program,
|
||||||
|
&script.Program{
|
||||||
|
Statements: []script.Expression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralString{"Hello, World!"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Symbol", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
#symbol
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, program,
|
||||||
|
&script.Program{
|
||||||
|
Statements: []script.Expression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.Quote{Value: &script.Identifier{"symbol"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("BinaryOperation", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
1 < 2
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, program,
|
||||||
|
&script.Program{
|
||||||
|
Statements: []script.Expression{
|
||||||
|
&script.BinaryOperation{
|
||||||
|
Lhs: &script.LiteralNumber{1},
|
||||||
|
Operator: "<",
|
||||||
|
Rhs: &script.BinaryOperation{Lhs: &script.LiteralNumber{2}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Boolean/True", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
true
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, program,
|
||||||
|
&script.Program{
|
||||||
|
Statements: []script.Expression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralBoolean{true}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Boolean/False", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
false
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, program,
|
||||||
|
&script.Program{
|
||||||
|
Statements: []script.Expression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralBoolean{false}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("List", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
[1 2 3]
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, program,
|
||||||
|
&script.Program{
|
||||||
|
Statements: []script.Expression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.ListExpression{
|
||||||
|
Values: []script.Expression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralNumber{1}},
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralNumber{2}},
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralNumber{3}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("List", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
[1 2 3
|
||||||
|
|
||||||
|
"a" "b" "c"
|
||||||
|
|
||||||
|
false]
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, program,
|
||||||
|
&script.Program{
|
||||||
|
Statements: []script.Expression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.ListExpression{
|
||||||
|
Values: []script.Expression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralNumber{1}},
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralNumber{2}},
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralNumber{3}},
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralString{"a"}},
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralString{"b"}},
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralString{"c"}},
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralBoolean{false}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Dict", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
{}
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, program,
|
||||||
|
&script.Program{
|
||||||
|
Statements: []script.Expression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.DictExpression{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Dict", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
{ a = "1", b = "2" }
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, program,
|
||||||
|
&script.Program{
|
||||||
|
Statements: []script.Expression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.DictExpression{
|
||||||
|
Entries: []*script.DictEntry{
|
||||||
|
{
|
||||||
|
Key: &script.Identifier{"a"},
|
||||||
|
Value: &script.BinaryOperation{Lhs: &script.LiteralString{"1"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: &script.Identifier{"b"},
|
||||||
|
Value: &script.BinaryOperation{Lhs: &script.LiteralString{"2"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Dict", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
{
|
||||||
|
a = "1"
|
||||||
|
b = "2", c = "3"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, program,
|
||||||
|
&script.Program{
|
||||||
|
Statements: []script.Expression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.DictExpression{
|
||||||
|
Entries: []*script.DictEntry{
|
||||||
|
{
|
||||||
|
Key: &script.Identifier{"a"},
|
||||||
|
Value: &script.BinaryOperation{Lhs: &script.LiteralString{"1"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: &script.Identifier{"b"},
|
||||||
|
Value: &script.BinaryOperation{Lhs: &script.LiteralString{"2"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: &script.Identifier{"c"},
|
||||||
|
Value: &script.BinaryOperation{Lhs: &script.LiteralString{"3"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("FunctionCall", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
fn 1 2 3
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, program,
|
||||||
|
&script.Program{
|
||||||
|
Statements: []script.Expression{
|
||||||
|
&script.FunctionCall{
|
||||||
|
Receiver: &script.Identifier{"fn"},
|
||||||
|
Arguments: []script.ArgumentExpression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralNumber{1}},
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralNumber{2}},
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralNumber{3}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("FunctionCall", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
foo { a = 1, b = [1 2 3] } [
|
||||||
|
bar #test
|
||||||
|
2
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, program,
|
||||||
|
&script.Program{
|
||||||
|
Statements: []script.Expression{
|
||||||
|
&script.FunctionCall{
|
||||||
|
Receiver: &script.Identifier{"foo"},
|
||||||
|
Arguments: []script.ArgumentExpression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.DictExpression{
|
||||||
|
Entries: []*script.DictEntry{
|
||||||
|
{
|
||||||
|
Key: &script.Identifier{"a"},
|
||||||
|
Value: &script.BinaryOperation{Lhs: &script.LiteralNumber{1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: &script.Identifier{"b"},
|
||||||
|
Value: &script.BinaryOperation{Lhs: &script.ListExpression{
|
||||||
|
Values: []script.Expression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralNumber{1}},
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralNumber{2}},
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralNumber{3}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
&script.BinaryOperation{Lhs: &script.ListExpression{
|
||||||
|
Values: []script.Expression{
|
||||||
|
&script.FunctionCall{
|
||||||
|
Receiver: &script.Identifier{"bar"},
|
||||||
|
Arguments: []script.ArgumentExpression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.Quote{&script.Identifier{"test"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralNumber{2}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Operators", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
if 1 <= 2 [ println "Yes" ]
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, program,
|
||||||
|
&script.Program{
|
||||||
|
Statements: []script.Expression{
|
||||||
|
&script.FunctionCall{
|
||||||
|
Receiver: &script.Identifier{"if"},
|
||||||
|
Arguments: []script.ArgumentExpression{
|
||||||
|
&script.BinaryOperation{
|
||||||
|
Lhs: &script.LiteralNumber{1},
|
||||||
|
Operator: "<=",
|
||||||
|
Rhs: &script.BinaryOperation{
|
||||||
|
Lhs: &script.LiteralNumber{2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&script.BinaryOperation{Lhs: &script.ListExpression{
|
||||||
|
Values: []script.Expression{
|
||||||
|
&script.FunctionCall{
|
||||||
|
Receiver: &script.Identifier{"println"},
|
||||||
|
Arguments: []script.ArgumentExpression{
|
||||||
|
&script.BinaryOperation{Lhs: &script.LiteralString{"Yes"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ComplexProgram", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
match x [
|
||||||
|
#None -> 0
|
||||||
|
#(Some value) -> value
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
cupaloy.SnapshotT(t, program)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ComplexProgram", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
for n (range 1 10) [
|
||||||
|
m := n * n
|
||||||
|
printfln "n^2 = %v" m
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
cupaloy.SnapshotT(t, program)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ComplexProgram", func(t *testing.T) {
|
||||||
|
program, err := script.Parse(`
|
||||||
|
foo [ 1 2 3 ] {
|
||||||
|
a = 1
|
||||||
|
b = foo #other { bar = 456 }
|
||||||
|
} [
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
cupaloy.SnapshotT(t, program)
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user