Compare commits
4 Commits
main
...
feat/updat
| Author | SHA1 | Date | |
|---|---|---|---|
|
31924237b2
|
|||
|
68cc1624c7
|
|||
|
0cdce0e42c
|
|||
|
0ec52008bb
|
@@ -48,7 +48,7 @@ linters:
|
|||||||
# More information: https://golangci-lint.run/usage/false-positives/#comments
|
# More information: https://golangci-lint.run/usage/false-positives/#comments
|
||||||
#
|
#
|
||||||
# Please uncomment the following line if your code is not using the godoc format
|
# Please uncomment the following line if your code is not using the godoc format
|
||||||
- comments
|
# - comments
|
||||||
|
|
||||||
# Common false positives
|
# Common false positives
|
||||||
# feel free to remove this if you don't have any false positives
|
# feel free to remove this if you don't have any false positives
|
||||||
@@ -126,6 +126,9 @@ linters:
|
|||||||
# Blank import should be only in a main or test package, or have a comment justifying it.
|
# Blank import should be only in a main or test package, or have a comment justifying it.
|
||||||
- name: blank-imports
|
- name: blank-imports
|
||||||
|
|
||||||
|
# Packages should have comments of the form "Package x ...".
|
||||||
|
- name: package-comments
|
||||||
|
|
||||||
# context.Context() should be the first parameter of a function when provided as argument.
|
# context.Context() should be the first parameter of a function when provided as argument.
|
||||||
- name: context-as-argument
|
- name: context-as-argument
|
||||||
arguments:
|
arguments:
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -1,7 +1,7 @@
|
|||||||
BINARY_NAME=lambda
|
BINARY_NAME=lambda
|
||||||
TEST=simple
|
TEST=simple
|
||||||
|
|
||||||
.PHONY: help build run profile explain graph docs test bench clean
|
.PHONY: help build run profile explain graph docs test bench lint clean
|
||||||
.DEFAULT_GOAL := help
|
.DEFAULT_GOAL := help
|
||||||
.SILENT:
|
.SILENT:
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ help:
|
|||||||
echo " docs - Start local godoc server on port 6060"
|
echo " docs - Start local godoc server on port 6060"
|
||||||
echo " test - Run tests for all samples"
|
echo " test - Run tests for all samples"
|
||||||
echo " bench - Run benchmarks for all samples"
|
echo " bench - Run benchmarks for all samples"
|
||||||
|
echo " lint - Run golangci-lint on all packages"
|
||||||
echo " clean - Remove all build artifacts"
|
echo " clean - Remove all build artifacts"
|
||||||
|
|
||||||
build:
|
build:
|
||||||
@@ -45,6 +46,9 @@ test:
|
|||||||
bench:
|
bench:
|
||||||
go test -bench=. -benchtime=10x -cpu=4 ./cmd/lambda
|
go test -bench=. -benchtime=10x -cpu=4 ./cmd/lambda
|
||||||
|
|
||||||
|
lint:
|
||||||
|
go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest run ./...
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f ${BINARY_NAME}
|
rm -f ${BINARY_NAME}
|
||||||
rm -f program.out
|
rm -f program.out
|
||||||
|
|||||||
26
cmd/lambda/registry.go
Normal file
26
cmd/lambda/registry.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.maximhutz.com/max/lambda/internal/cli"
|
||||||
|
"git.maximhutz.com/max/lambda/internal/registry"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/convert"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/engine/normalorder"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/lambda"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/saccharine"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MakeRegistry() *registry.Registry {
|
||||||
|
r := registry.New()
|
||||||
|
|
||||||
|
// Codecs
|
||||||
|
r.AddCodec(cli.ConvertCodec(convert.Saccharine2Lambda{}, "saccharine", "lambda"))
|
||||||
|
|
||||||
|
// Engines
|
||||||
|
r.AddEngine(cli.ConvertEngine(normalorder.Engine{}, "normalorder", "lambda"))
|
||||||
|
|
||||||
|
// Marshalers
|
||||||
|
r.AddMarshaler(cli.ConvertMarshaler(lambda.Marshaler{}, "lambda"))
|
||||||
|
r.AddMarshaler(cli.ConvertMarshaler(saccharine.Marshaler{}, "saccharine"))
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
55
internal/cli/codec.go
Normal file
55
internal/cli/codec.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Codec interface {
|
||||||
|
codec.Codec[Repr, Repr]
|
||||||
|
|
||||||
|
InType() string
|
||||||
|
OutType() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type convertedCodec[T, U any] struct {
|
||||||
|
codec codec.Codec[T, U]
|
||||||
|
inType, outType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c convertedCodec[T, U]) Decode(r Repr) (Repr, error) {
|
||||||
|
u, ok := r.Data().(U)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("could not parse '%v' as '%s'", r, c.inType)
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := c.codec.Decode(u)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewRepr(c.outType, t), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c convertedCodec[T, U]) Encode(r Repr) (Repr, error) {
|
||||||
|
t, ok := r.Data().(T)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("could not parse '%v' as '%s'", t, c.outType)
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := c.codec.Encode(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewRepr(c.inType, u), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c convertedCodec[T, U]) InType() string { return c.inType }
|
||||||
|
|
||||||
|
func (c convertedCodec[T, U]) OutType() string { return c.outType }
|
||||||
|
|
||||||
|
func ConvertCodec[T, U any](e codec.Codec[T, U], inType, outType string) Codec {
|
||||||
|
return convertedCodec[T, U]{e, inType, outType}
|
||||||
|
}
|
||||||
49
internal/cli/engine.go
Normal file
49
internal/cli/engine.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/engine"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Engine interface {
|
||||||
|
engine.Engine[Repr]
|
||||||
|
|
||||||
|
Name() string
|
||||||
|
InType() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type convertedEngine[T any] struct {
|
||||||
|
engine engine.Engine[T]
|
||||||
|
name string
|
||||||
|
inType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b convertedEngine[T]) InType() string { return b.inType }
|
||||||
|
|
||||||
|
func (b convertedEngine[T]) Name() string { return b.name }
|
||||||
|
|
||||||
|
func (b convertedEngine[T]) Get() (Repr, error) {
|
||||||
|
s, err := b.engine.Get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewRepr(b.inType, s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b convertedEngine[T]) Set(r Repr) error {
|
||||||
|
if t, ok := r.Data().(T); ok {
|
||||||
|
return b.engine.Set(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Incorrent format '%s' for engine '%s'.", r.Id(), b.inType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b convertedEngine[T]) Step(i int) bool {
|
||||||
|
return b.engine.Step(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertEngine[T any](e engine.Engine[T], name string, inType string) Engine {
|
||||||
|
return convertedEngine[T]{e, name, inType}
|
||||||
|
}
|
||||||
42
internal/cli/marshaler.go
Normal file
42
internal/cli/marshaler.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Marshaler interface {
|
||||||
|
codec.Marshaler[Repr]
|
||||||
|
|
||||||
|
InType() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type convertedMarshaler[T any] struct {
|
||||||
|
codec codec.Marshaler[T]
|
||||||
|
inType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c convertedMarshaler[T]) Decode(s string) (Repr, error) {
|
||||||
|
t, err := c.codec.Decode(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewRepr(c.inType, t), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c convertedMarshaler[T]) Encode(r Repr) (string, error) {
|
||||||
|
t, ok := r.Data().(T)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("could not parse '%v' as 'string'", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.codec.Encode(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c convertedMarshaler[T]) InType() string { return c.inType }
|
||||||
|
|
||||||
|
func ConvertMarshaler[T any](e codec.Marshaler[T], inType string) Marshaler {
|
||||||
|
return convertedMarshaler[T]{e, inType}
|
||||||
|
}
|
||||||
21
internal/cli/repr.go
Normal file
21
internal/cli/repr.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
type Repr interface {
|
||||||
|
// Id returns to name of the objects underlying representation. If is
|
||||||
|
// assumed that if two Repr objects have the same Id(), they share the same
|
||||||
|
// representation.
|
||||||
|
Id() string
|
||||||
|
|
||||||
|
Data() any
|
||||||
|
}
|
||||||
|
|
||||||
|
type baseRepr struct {
|
||||||
|
id string
|
||||||
|
data any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r baseRepr) Id() string { return r.id }
|
||||||
|
|
||||||
|
func (r baseRepr) Data() any { return r.data }
|
||||||
|
|
||||||
|
func NewRepr(id string, data any) Repr { return baseRepr{id, data} }
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package plugins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log/slog"
|
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Logs struct {
|
|
||||||
logger *slog.Logger
|
|
||||||
reducer runtime.Runtime
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLogs(logger *slog.Logger, r runtime.Runtime) *Logs {
|
|
||||||
plugin := &Logs{logger, r}
|
|
||||||
r.On(runtime.StepEvent, plugin.Step)
|
|
||||||
|
|
||||||
return plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Logs) Step() {
|
|
||||||
t.logger.Info("reduction", "tree", t.reducer.Expression().String())
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// Package "explanation" provides an observer to gather the reasoning during the
|
|
||||||
// reduction, and present a thorough explanation to the user for each step.
|
|
||||||
package plugins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Track the reductions made by a reduction process.
|
|
||||||
type Explanation struct {
|
|
||||||
reducer runtime.Runtime
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attaches a new explanation tracker to a reducer.
|
|
||||||
func NewExplanation(r runtime.Runtime) *Explanation {
|
|
||||||
plugin := &Explanation{reducer: r}
|
|
||||||
r.On(runtime.StartEvent, plugin.Start)
|
|
||||||
r.On(runtime.StepEvent, plugin.Step)
|
|
||||||
|
|
||||||
return plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Explanation) Start() {
|
|
||||||
fmt.Println(t.reducer.Expression().String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Explanation) Step() {
|
|
||||||
fmt.Println(" =", t.reducer.Expression().String())
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
// Package "performance" provides a tracker to observer CPU performance during
|
|
||||||
// execution.
|
|
||||||
package plugins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime/pprof"
|
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Observes a reduction process, and publishes a CPU performance profile on
|
|
||||||
// completion.
|
|
||||||
type Performance struct {
|
|
||||||
File string
|
|
||||||
filePointer *os.File
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a performance tracker that outputs a profile to "file".
|
|
||||||
func NewPerformance(file string, process runtime.Runtime) *Performance {
|
|
||||||
plugin := &Performance{File: file}
|
|
||||||
process.On(runtime.StartEvent, plugin.Start)
|
|
||||||
process.On(runtime.StopEvent, plugin.Stop)
|
|
||||||
|
|
||||||
return plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
// Begin profiling.
|
|
||||||
func (t *Performance) Start() {
|
|
||||||
var absPath string
|
|
||||||
|
|
||||||
absPath, t.Error = filepath.Abs(t.File)
|
|
||||||
if t.Error != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Error = os.MkdirAll(filepath.Dir(absPath), 0777)
|
|
||||||
if t.Error != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.filePointer, t.Error = os.Create(absPath)
|
|
||||||
if t.Error != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Error = pprof.StartCPUProfile(t.filePointer)
|
|
||||||
if t.Error != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop profiling.
|
|
||||||
func (t *Performance) Stop() {
|
|
||||||
pprof.StopCPUProfile()
|
|
||||||
t.filePointer.Close()
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package plugins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/internal/statistics"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An observer, to track reduction performance.
|
|
||||||
type Statistics struct {
|
|
||||||
start time.Time
|
|
||||||
steps uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new reduction performance Statistics.
|
|
||||||
func NewStatistics(r runtime.Runtime) *Statistics {
|
|
||||||
plugin := &Statistics{}
|
|
||||||
r.On(runtime.StartEvent, plugin.Start)
|
|
||||||
r.On(runtime.StepEvent, plugin.Step)
|
|
||||||
r.On(runtime.StopEvent, plugin.Stop)
|
|
||||||
|
|
||||||
return plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Statistics) Start() {
|
|
||||||
t.start = time.Now()
|
|
||||||
t.steps = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Statistics) Step() {
|
|
||||||
t.steps++
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Statistics) Stop() {
|
|
||||||
results := statistics.Results{
|
|
||||||
StepsTaken: t.steps,
|
|
||||||
TimeElapsed: uint64(time.Since(t.start).Milliseconds()),
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprint(os.Stderr, results.String())
|
|
||||||
}
|
|
||||||
75
internal/registry/registry.go
Normal file
75
internal/registry/registry.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.maximhutz.com/max/lambda/internal/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
marshalers map[string]cli.Marshaler
|
||||||
|
codecs []cli.Codec
|
||||||
|
engines map[string]cli.Engine
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
marshalers: map[string]cli.Marshaler{},
|
||||||
|
codecs: []cli.Codec{},
|
||||||
|
engines: map[string]cli.Engine{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) AddCodec(c cli.Codec) error {
|
||||||
|
r.codecs = append(r.codecs, c)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) AddMarshaler(c cli.Marshaler) error {
|
||||||
|
if _, ok := r.marshalers[c.InType()]; ok {
|
||||||
|
return fmt.Errorf("marshaler for '%s' already registered", c.InType())
|
||||||
|
}
|
||||||
|
|
||||||
|
r.marshalers[c.InType()] = c
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) AddEngine(e cli.Engine) error {
|
||||||
|
if _, ok := r.engines[e.Name()]; ok {
|
||||||
|
return fmt.Errorf("engine '%s' already registered", e.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
r.engines[e.Name()] = e
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) GetEngine(name string) (cli.Engine, error) {
|
||||||
|
e, ok := r.engines[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("engine '%s' not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) ConvertTo(repr cli.Repr, outType string) (cli.Repr, error) {
|
||||||
|
panic("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) Marshal(repr cli.Repr) (string, error) {
|
||||||
|
m, ok := r.marshalers[repr.Id()]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("no marshaler for '%s'", repr.Id())
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.Encode(repr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) Unmarshal(s string, outType string) (cli.Repr, error) {
|
||||||
|
m, ok := r.marshalers[outType]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("no marshaler for '%s'", outType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.Decode(s)
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// Package "statistics" provides a way to observer reduction speed during
|
|
||||||
// execution.
|
|
||||||
package statistics
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Statistics for a specific reduction.
|
|
||||||
type Results struct {
|
|
||||||
StepsTaken uint64 // Number of steps taken during execution.
|
|
||||||
TimeElapsed uint64 // The time (ms) taken for execution to complete.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the average number of operations per second of the execution.
|
|
||||||
func (r Results) OpsPerSecond() float32 {
|
|
||||||
return float32(r.StepsTaken) / (float32(r.TimeElapsed) / 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format the results as a string.
|
|
||||||
func (r Results) String() string {
|
|
||||||
builder := strings.Builder{}
|
|
||||||
fmt.Fprintln(&builder, "Time Spent:", r.TimeElapsed, "ms")
|
|
||||||
fmt.Fprintln(&builder, "Steps:", r.StepsTaken)
|
|
||||||
fmt.Fprintln(&builder, "Speed:", r.OpsPerSecond(), "ops")
|
|
||||||
return builder.String()
|
|
||||||
}
|
|
||||||
8
pkg/codec/codec.go
Normal file
8
pkg/codec/codec.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package codec
|
||||||
|
|
||||||
|
type Codec[T, U any] interface {
|
||||||
|
Encode(T) (U, error)
|
||||||
|
Decode(U) (T, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Marshaler[T any] = Codec[T, string]
|
||||||
@@ -3,16 +3,17 @@ package convert
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/codec"
|
||||||
"git.maximhutz.com/max/lambda/pkg/lambda"
|
"git.maximhutz.com/max/lambda/pkg/lambda"
|
||||||
"git.maximhutz.com/max/lambda/pkg/saccharine"
|
"git.maximhutz.com/max/lambda/pkg/saccharine"
|
||||||
)
|
)
|
||||||
|
|
||||||
func convertAtom(n *saccharine.Atom) lambda.Expression {
|
func encodeAtom(n *saccharine.Atom) lambda.Expression {
|
||||||
return lambda.NewVariable(n.Name)
|
return lambda.NewVariable(n.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertAbstraction(n *saccharine.Abstraction) lambda.Expression {
|
func encodeAbstraction(n *saccharine.Abstraction) lambda.Expression {
|
||||||
result := SaccharineToLambda(n.Body)
|
result := encodeExpression(n.Body)
|
||||||
|
|
||||||
parameters := n.Parameters
|
parameters := n.Parameters
|
||||||
|
|
||||||
@@ -31,13 +32,13 @@ func convertAbstraction(n *saccharine.Abstraction) lambda.Expression {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertApplication(n *saccharine.Application) lambda.Expression {
|
func encodeApplication(n *saccharine.Application) lambda.Expression {
|
||||||
result := SaccharineToLambda(n.Abstraction)
|
result := encodeExpression(n.Abstraction)
|
||||||
|
|
||||||
arguments := []lambda.Expression{}
|
arguments := []lambda.Expression{}
|
||||||
for _, argument := range n.Arguments {
|
for _, argument := range n.Arguments {
|
||||||
convertedArgument := SaccharineToLambda(argument)
|
encodeedArgument := encodeExpression(argument)
|
||||||
arguments = append(arguments, convertedArgument)
|
arguments = append(arguments, encodeedArgument)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, argument := range arguments {
|
for _, argument := range arguments {
|
||||||
@@ -51,9 +52,9 @@ func reduceLet(s *saccharine.LetStatement, e lambda.Expression) lambda.Expressio
|
|||||||
var value lambda.Expression
|
var value lambda.Expression
|
||||||
|
|
||||||
if len(s.Parameters) == 0 {
|
if len(s.Parameters) == 0 {
|
||||||
value = SaccharineToLambda(s.Body)
|
value = encodeExpression(s.Body)
|
||||||
} else {
|
} else {
|
||||||
value = convertAbstraction(saccharine.NewAbstraction(s.Parameters, s.Body))
|
value = encodeAbstraction(saccharine.NewAbstraction(s.Parameters, s.Body))
|
||||||
}
|
}
|
||||||
|
|
||||||
return lambda.NewApplication(
|
return lambda.NewApplication(
|
||||||
@@ -67,7 +68,7 @@ func reduceDeclare(s *saccharine.DeclareStatement, e lambda.Expression) lambda.E
|
|||||||
|
|
||||||
return lambda.NewApplication(
|
return lambda.NewApplication(
|
||||||
lambda.NewAbstraction(freshVar, e),
|
lambda.NewAbstraction(freshVar, e),
|
||||||
SaccharineToLambda(s.Value),
|
encodeExpression(s.Value),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,8 +83,8 @@ func reduceStatement(s saccharine.Statement, e lambda.Expression) lambda.Express
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertClause(n *saccharine.Clause) lambda.Expression {
|
func encodeClause(n *saccharine.Clause) lambda.Expression {
|
||||||
result := SaccharineToLambda(n.Returns)
|
result := encodeExpression(n.Returns)
|
||||||
|
|
||||||
for i := len(n.Statements) - 1; i >= 0; i-- {
|
for i := len(n.Statements) - 1; i >= 0; i-- {
|
||||||
result = reduceStatement(n.Statements[i], result)
|
result = reduceStatement(n.Statements[i], result)
|
||||||
@@ -92,17 +93,46 @@ func convertClause(n *saccharine.Clause) lambda.Expression {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaccharineToLambda(n saccharine.Expression) lambda.Expression {
|
func encodeExpression(s saccharine.Expression) lambda.Expression {
|
||||||
switch n := n.(type) {
|
switch s := s.(type) {
|
||||||
case *saccharine.Atom:
|
case *saccharine.Atom:
|
||||||
return convertAtom(n)
|
return encodeAtom(s)
|
||||||
case *saccharine.Abstraction:
|
case *saccharine.Abstraction:
|
||||||
return convertAbstraction(n)
|
return encodeAbstraction(s)
|
||||||
case *saccharine.Application:
|
case *saccharine.Application:
|
||||||
return convertApplication(n)
|
return encodeApplication(s)
|
||||||
case *saccharine.Clause:
|
case *saccharine.Clause:
|
||||||
return convertClause(n)
|
return encodeClause(s)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("unknown expression type: %T", n))
|
panic(fmt.Errorf("unknown expression type: %T", s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeExression(l lambda.Expression) saccharine.Expression {
|
||||||
|
switch l := l.(type) {
|
||||||
|
case lambda.Variable:
|
||||||
|
return saccharine.NewAtom(l.Name())
|
||||||
|
case lambda.Abstraction:
|
||||||
|
return saccharine.NewAbstraction(
|
||||||
|
[]string{l.Parameter()},
|
||||||
|
decodeExression(l.Body()))
|
||||||
|
case lambda.Application:
|
||||||
|
return saccharine.NewApplication(
|
||||||
|
decodeExression(l.Abstraction()),
|
||||||
|
[]saccharine.Expression{decodeExression(l.Argument())})
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unknown expression type: %T", l))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Saccharine2Lambda struct{}
|
||||||
|
|
||||||
|
func (c Saccharine2Lambda) Decode(l lambda.Expression) (saccharine.Expression, error) {
|
||||||
|
return decodeExression(l), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Saccharine2Lambda) Encode(s saccharine.Expression) (lambda.Expression, error) {
|
||||||
|
return encodeExpression(s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ codec.Codec[saccharine.Expression, lambda.Expression] = (*Saccharine2Lambda)(nil)
|
||||||
|
|||||||
7
pkg/engine/engine.go
Normal file
7
pkg/engine/engine.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package engine
|
||||||
|
|
||||||
|
type Engine[T any] interface {
|
||||||
|
Get() (T, error)
|
||||||
|
Set(T) error
|
||||||
|
Step(int) bool
|
||||||
|
}
|
||||||
34
pkg/engine/normalorder/engine.go
Normal file
34
pkg/engine/normalorder/engine.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package normalorder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/engine"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/lambda"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Engine struct {
|
||||||
|
expr lambda.Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Engine) Get() (lambda.Expression, error) {
|
||||||
|
return e.expr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Engine) Set(l lambda.Expression) error {
|
||||||
|
e.expr = l
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Engine) Step(i int) bool {
|
||||||
|
var reduced bool
|
||||||
|
|
||||||
|
for range i {
|
||||||
|
e.expr, reduced = ReduceOnce(e.expr)
|
||||||
|
if !reduced {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ engine.Engine[lambda.Expression] = (*Engine)(nil)
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// Package expr provides the abstract Expression interface for all evaluatable
|
|
||||||
// expression types in the lambda runtime.
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Expression is the base interface for all evaluatable expression types.
|
|
||||||
// Different evaluation modes (lambda calculus, SKI combinators, typed lambda
|
|
||||||
// calculus, etc.) implement this interface with their own concrete types.
|
|
||||||
type Expression interface {
|
|
||||||
// The expression should have a human-readable representation.
|
|
||||||
fmt.Stringer
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
package lambda
|
package lambda
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.maximhutz.com/max/lambda/pkg/expr"
|
"fmt"
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/set"
|
"git.maximhutz.com/max/lambda/pkg/set"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Expression is the interface for all lambda calculus expression types.
|
// Expression is the interface for all lambda calculus expression types.
|
||||||
// It embeds the general expr.Expression interface for cross-mode compatibility.
|
// It embeds the general expr.Expression interface for cross-mode compatibility.
|
||||||
type Expression interface {
|
type Expression interface {
|
||||||
expr.Expression
|
fmt.Stringer
|
||||||
|
|
||||||
// Substitute replaces all free occurrences of the target variable with the
|
// Substitute replaces all free occurrences of the target variable with the
|
||||||
// replacement expression. Alpha-renaming is performed automatically to
|
// replacement expression. Alpha-renaming is performed automatically to
|
||||||
|
|||||||
19
pkg/lambda/marshaler.go
Normal file
19
pkg/lambda/marshaler.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package lambda
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Marshaler struct{}
|
||||||
|
|
||||||
|
func (m Marshaler) Decode(string) (Expression, error) {
|
||||||
|
return nil, fmt.Errorf("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Marshaler) Encode(e Expression) (string, error) {
|
||||||
|
return e.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ codec.Marshaler[Expression] = (*Marshaler)(nil)
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package normalorder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/emitter"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/expr"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/lambda"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NormalOrderReducer implements normal order (leftmost-outermost) reduction
|
|
||||||
// for lambda calculus expressions.
|
|
||||||
type Runtime struct {
|
|
||||||
emitter.BaseEmitter[runtime.Event]
|
|
||||||
expression lambda.Expression
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNormalOrderReducer creates a new normal order reducer.
|
|
||||||
func NewRuntime(expression lambda.Expression) *Runtime {
|
|
||||||
return &Runtime{
|
|
||||||
BaseEmitter: *emitter.New[runtime.Event](),
|
|
||||||
expression: expression,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expression returns the current expression state.
|
|
||||||
func (r *Runtime) Expression() expr.Expression {
|
|
||||||
return r.expression
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Runtime) Step() bool {
|
|
||||||
result, done := ReduceOnce(r.expression)
|
|
||||||
r.expression = result
|
|
||||||
return !done
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reduce performs normal order reduction on a lambda expression.
|
|
||||||
// The expression must be a lambda.Expression; other types are returned unchanged.
|
|
||||||
func (r *Runtime) Run() {
|
|
||||||
r.Emit(runtime.StartEvent)
|
|
||||||
|
|
||||||
for !r.Step() {
|
|
||||||
r.Emit(runtime.StepEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Emit(runtime.StopEvent)
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package runtime
|
|
||||||
|
|
||||||
// Event represents lifecycle events during interpretation.
|
|
||||||
type Event int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// StartEvent is emitted before interpretation begins.
|
|
||||||
StartEvent Event = iota
|
|
||||||
// StepEvent is emitted after each interpretation step.
|
|
||||||
StepEvent
|
|
||||||
// StopEvent is emitted after interpretation completes.
|
|
||||||
StopEvent
|
|
||||||
)
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// Package runtime provides the abstract Reducer interface for all expression
|
|
||||||
// reduction strategies.
|
|
||||||
package runtime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/emitter"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/expr"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Runtime defines the interface for expression reduction strategies.
|
|
||||||
// Different evaluation modes (normal order, applicative order, SKI combinators,
|
|
||||||
// etc.) implement this interface with their own reduction logic.
|
|
||||||
//
|
|
||||||
// Runtimes also implement the Emitter interface to allow plugins to observe
|
|
||||||
// reduction lifecycle events (Start, Step, Stop).
|
|
||||||
type Runtime interface {
|
|
||||||
emitter.Emitter[Event]
|
|
||||||
|
|
||||||
// Run a single step. Returns whether the runtime is complete or not.
|
|
||||||
Step() bool
|
|
||||||
|
|
||||||
// Run until completion.
|
|
||||||
Run()
|
|
||||||
|
|
||||||
// Copy the state of the runtime.
|
|
||||||
Expression() expr.Expression
|
|
||||||
}
|
|
||||||
24
pkg/saccharine/marshaler.go
Normal file
24
pkg/saccharine/marshaler.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Package "saccharine" provides a simple language built on top of λ-calculus,
|
||||||
|
// to facilitate productive coding using it.
|
||||||
|
package saccharine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Marshaler struct{}
|
||||||
|
|
||||||
|
func (m Marshaler) Decode(s string) (Expression, error) {
|
||||||
|
tokens, err := scan(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return parse(tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Marshaler) Encode(e Expression) (string, error) {
|
||||||
|
return stringifyExpression(e), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ codec.Marshaler[Expression] = (*Marshaler)(nil)
|
||||||
@@ -5,18 +5,17 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/iterator"
|
"git.maximhutz.com/max/lambda/pkg/iterator"
|
||||||
"git.maximhutz.com/max/lambda/pkg/saccharine/token"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/trace"
|
"git.maximhutz.com/max/lambda/pkg/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TokenIterator = iterator.Iterator[token.Token]
|
type TokenIterator = iterator.Iterator[Token]
|
||||||
|
|
||||||
func parseRawToken(i *TokenIterator, expected token.Type) (*token.Token, error) {
|
func parseRawToken(i *TokenIterator, expected TokenType) (*Token, error) {
|
||||||
return iterator.Do(i, func(i *TokenIterator) (*token.Token, error) {
|
return iterator.Do(i, func(i *TokenIterator) (*Token, error) {
|
||||||
if tok, err := i.Next(); err != nil {
|
if tok, err := i.Next(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if tok.Type != expected {
|
} else if tok.Type != expected {
|
||||||
return nil, fmt.Errorf("expected token %v, got %v'", token.Name(expected), tok.Value)
|
return nil, fmt.Errorf("expected token %v, got %v'", expected.Name(), tok.Value)
|
||||||
} else {
|
} else {
|
||||||
return &tok, nil
|
return &tok, nil
|
||||||
}
|
}
|
||||||
@@ -25,14 +24,14 @@ func parseRawToken(i *TokenIterator, expected token.Type) (*token.Token, error)
|
|||||||
|
|
||||||
func passSoftBreaks(i *TokenIterator) {
|
func passSoftBreaks(i *TokenIterator) {
|
||||||
for {
|
for {
|
||||||
if _, err := parseRawToken(i, token.SoftBreak); err != nil {
|
if _, err := parseRawToken(i, TokenSoftBreak); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseToken(i *TokenIterator, expected token.Type, ignoreSoftBreaks bool) (*token.Token, error) {
|
func parseToken(i *TokenIterator, expected TokenType, ignoreSoftBreaks bool) (*Token, error) {
|
||||||
return iterator.Do(i, func(i *TokenIterator) (*token.Token, error) {
|
return iterator.Do(i, func(i *TokenIterator) (*Token, error) {
|
||||||
if ignoreSoftBreaks {
|
if ignoreSoftBreaks {
|
||||||
passSoftBreaks(i)
|
passSoftBreaks(i)
|
||||||
}
|
}
|
||||||
@@ -42,17 +41,17 @@ func parseToken(i *TokenIterator, expected token.Type, ignoreSoftBreaks bool) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseString(i *TokenIterator) (string, error) {
|
func parseString(i *TokenIterator) (string, error) {
|
||||||
if tok, err := parseToken(i, token.Atom, true); err != nil {
|
if tok, err := parseToken(i, TokenAtom, true); err != nil {
|
||||||
return "", trace.Wrap(err, "no variable (col %d)", i.Index())
|
return "", trace.Wrap(err, "no variable (col %d)", i.Index())
|
||||||
} else {
|
} else {
|
||||||
return tok.Value, nil
|
return tok.Value, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBreak(i *TokenIterator) (*token.Token, error) {
|
func parseBreak(i *TokenIterator) (*Token, error) {
|
||||||
if tok, softErr := parseRawToken(i, token.SoftBreak); softErr == nil {
|
if tok, softErr := parseRawToken(i, TokenSoftBreak); softErr == nil {
|
||||||
return tok, nil
|
return tok, nil
|
||||||
} else if tok, hardErr := parseRawToken(i, token.HardBreak); hardErr == nil {
|
} else if tok, hardErr := parseRawToken(i, TokenHardBreak); hardErr == nil {
|
||||||
return tok, nil
|
return tok, nil
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.Join(softErr, hardErr)
|
return nil, errors.Join(softErr, hardErr)
|
||||||
@@ -76,11 +75,11 @@ func parseList[U any](i *TokenIterator, fn func(*TokenIterator) (U, error), mini
|
|||||||
|
|
||||||
func parseAbstraction(i *TokenIterator) (*Abstraction, error) {
|
func parseAbstraction(i *TokenIterator) (*Abstraction, error) {
|
||||||
return iterator.Do(i, func(i *TokenIterator) (*Abstraction, error) {
|
return iterator.Do(i, func(i *TokenIterator) (*Abstraction, error) {
|
||||||
if _, err := parseToken(i, token.Slash, true); err != nil {
|
if _, err := parseToken(i, TokenSlash, true); err != nil {
|
||||||
return nil, trace.Wrap(err, "no function slash (col %d)", i.MustGet().Column)
|
return nil, trace.Wrap(err, "no function slash (col %d)", i.MustGet().Column)
|
||||||
} else if parameters, err := parseList(i, parseString, 0); err != nil {
|
} else if parameters, err := parseList(i, parseString, 0); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if _, err = parseToken(i, token.Dot, true); err != nil {
|
} else if _, err = parseToken(i, TokenDot, true); err != nil {
|
||||||
return nil, trace.Wrap(err, "no function dot (col %d)", i.MustGet().Column)
|
return nil, trace.Wrap(err, "no function dot (col %d)", i.MustGet().Column)
|
||||||
} else if body, err := parseExpression(i); err != nil {
|
} else if body, err := parseExpression(i); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -92,11 +91,11 @@ func parseAbstraction(i *TokenIterator) (*Abstraction, error) {
|
|||||||
|
|
||||||
func parseApplication(i *TokenIterator) (*Application, error) {
|
func parseApplication(i *TokenIterator) (*Application, error) {
|
||||||
return iterator.Do(i, func(i *TokenIterator) (*Application, error) {
|
return iterator.Do(i, func(i *TokenIterator) (*Application, error) {
|
||||||
if _, err := parseToken(i, token.OpenParen, true); err != nil {
|
if _, err := parseToken(i, TokenOpenParen, true); err != nil {
|
||||||
return nil, trace.Wrap(err, "no openning brackets (col %d)", i.MustGet().Column)
|
return nil, trace.Wrap(err, "no openning brackets (col %d)", i.MustGet().Column)
|
||||||
} else if expressions, err := parseList(i, parseExpression, 1); err != nil {
|
} else if expressions, err := parseList(i, parseExpression, 1); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if _, err := parseToken(i, token.CloseParen, true); err != nil {
|
} else if _, err := parseToken(i, TokenCloseParen, true); err != nil {
|
||||||
return nil, trace.Wrap(err, "no closing brackets (col %d)", i.MustGet().Column)
|
return nil, trace.Wrap(err, "no closing brackets (col %d)", i.MustGet().Column)
|
||||||
} else {
|
} else {
|
||||||
return NewApplication(expressions[0], expressions[1:]), nil
|
return NewApplication(expressions[0], expressions[1:]), nil
|
||||||
@@ -105,7 +104,7 @@ func parseApplication(i *TokenIterator) (*Application, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseAtom(i *TokenIterator) (*Atom, error) {
|
func parseAtom(i *TokenIterator) (*Atom, error) {
|
||||||
if tok, err := parseToken(i, token.Atom, true); err != nil {
|
if tok, err := parseToken(i, TokenAtom, true); err != nil {
|
||||||
return nil, trace.Wrap(err, "no variable (col %d)", i.Index())
|
return nil, trace.Wrap(err, "no variable (col %d)", i.Index())
|
||||||
} else {
|
} else {
|
||||||
return NewAtom(tok.Value), nil
|
return NewAtom(tok.Value), nil
|
||||||
@@ -133,7 +132,7 @@ func parseStatements(i *TokenIterator) ([]Statement, error) {
|
|||||||
|
|
||||||
func parseClause(i *TokenIterator, braces bool) (*Clause, error) {
|
func parseClause(i *TokenIterator, braces bool) (*Clause, error) {
|
||||||
if braces {
|
if braces {
|
||||||
if _, err := parseToken(i, token.OpenBrace, true); err != nil {
|
if _, err := parseToken(i, TokenOpenBrace, true); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,7 +151,7 @@ func parseClause(i *TokenIterator, braces bool) (*Clause, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if braces {
|
if braces {
|
||||||
if _, err := parseToken(i, token.CloseBrace, true); err != nil {
|
if _, err := parseToken(i, TokenCloseBrace, true); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,13 +164,13 @@ func parseExpression(i *TokenIterator) (Expression, error) {
|
|||||||
passSoftBreaks(i)
|
passSoftBreaks(i)
|
||||||
|
|
||||||
switch peek := i.MustGet(); peek.Type {
|
switch peek := i.MustGet(); peek.Type {
|
||||||
case token.OpenParen:
|
case TokenOpenParen:
|
||||||
return parseApplication(i)
|
return parseApplication(i)
|
||||||
case token.Slash:
|
case TokenSlash:
|
||||||
return parseAbstraction(i)
|
return parseAbstraction(i)
|
||||||
case token.Atom:
|
case TokenAtom:
|
||||||
return parseAtom(i)
|
return parseAtom(i)
|
||||||
case token.OpenBrace:
|
case TokenOpenBrace:
|
||||||
return parseClause(i, true)
|
return parseClause(i, true)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("expected expression, got '%v' (col %d)", peek.Value, peek.Column)
|
return nil, fmt.Errorf("expected expression, got '%v' (col %d)", peek.Value, peek.Column)
|
||||||
@@ -183,7 +182,7 @@ func parseLet(i *TokenIterator) (*LetStatement, error) {
|
|||||||
return iterator.Do(i, func(i *TokenIterator) (*LetStatement, error) {
|
return iterator.Do(i, func(i *TokenIterator) (*LetStatement, error) {
|
||||||
if parameters, err := parseList(i, parseString, 1); err != nil {
|
if parameters, err := parseList(i, parseString, 1); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if _, err := parseToken(i, token.Assign, true); err != nil {
|
} else if _, err := parseToken(i, TokenAssign, true); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if body, err := parseExpression(i); err != nil {
|
} else if body, err := parseExpression(i); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -212,7 +211,7 @@ func parseStatement(i *TokenIterator) (Statement, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Given a list of tokens, attempt to parse it into an syntax tree.
|
// Given a list of tokens, attempt to parse it into an syntax tree.
|
||||||
func parse(tokens []token.Token) (Expression, error) {
|
func parse(tokens []Token) (Expression, error) {
|
||||||
i := iterator.Of(tokens)
|
i := iterator.Of(tokens)
|
||||||
|
|
||||||
exp, err := parseClause(i, false)
|
exp, err := parseClause(i, false)
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
// Package "saccharine" provides a simple language built on top of λ-calculus,
|
|
||||||
// to facilitate productive coding using it.
|
|
||||||
package saccharine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/saccharine/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Convert a piece of valid saccharine code into an expression.
|
|
||||||
func Parse(code string) (Expression, error) {
|
|
||||||
tokens, err := token.Parse(code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return parse(tokens)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a parsed saccharine expression back into source code.
|
|
||||||
func Stringify(expression Expression) string {
|
|
||||||
return stringifyExpression(expression)
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package token
|
package saccharine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -14,7 +14,7 @@ func isVariable(r rune) bool {
|
|||||||
return unicode.IsLetter(r) || unicode.IsNumber(r)
|
return unicode.IsLetter(r) || unicode.IsNumber(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRune(i *iterator.Iterator[rune], expected func(rune) bool) (rune, error) {
|
func scanRune(i *iterator.Iterator[rune], expected func(rune) bool) (rune, error) {
|
||||||
i2 := i.Copy()
|
i2 := i.Copy()
|
||||||
|
|
||||||
if r, err := i2.Next(); err != nil {
|
if r, err := i2.Next(); err != nil {
|
||||||
@@ -27,7 +27,7 @@ func parseRune(i *iterator.Iterator[rune], expected func(rune) bool) (rune, erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCharacter(i *iterator.Iterator[rune], expected rune) (rune, error) {
|
func scanCharacter(i *iterator.Iterator[rune], expected rune) (rune, error) {
|
||||||
i2 := i.Copy()
|
i2 := i.Copy()
|
||||||
|
|
||||||
if r, err := i2.Next(); err != nil {
|
if r, err := i2.Next(); err != nil {
|
||||||
@@ -42,7 +42,7 @@ func parseCharacter(i *iterator.Iterator[rune], expected rune) (rune, error) {
|
|||||||
|
|
||||||
// Pulls the next token from an iterator over runes. If it cannot, it will
|
// Pulls the next token from an iterator over runes. If it cannot, it will
|
||||||
// return nil. If an error occurs, it will return that.
|
// return nil. If an error occurs, it will return that.
|
||||||
func getToken(i *iterator.Iterator[rune]) (*Token, error) {
|
func scanToken(i *iterator.Iterator[rune]) (*Token, error) {
|
||||||
index := i.Index()
|
index := i.Index()
|
||||||
|
|
||||||
if i.Done() {
|
if i.Done() {
|
||||||
@@ -56,27 +56,27 @@ func getToken(i *iterator.Iterator[rune]) (*Token, error) {
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case letter == '(':
|
case letter == '(':
|
||||||
return NewOpenParen(index), nil
|
return NewTokenOpenParen(index), nil
|
||||||
case letter == ')':
|
case letter == ')':
|
||||||
return NewCloseParen(index), nil
|
return NewTokenCloseParen(index), nil
|
||||||
case letter == '.':
|
case letter == '.':
|
||||||
return NewDot(index), nil
|
return NewTokenDot(index), nil
|
||||||
case letter == '\\':
|
case letter == '\\':
|
||||||
return NewSlash(index), nil
|
return NewTokenSlash(index), nil
|
||||||
case letter == '\n':
|
case letter == '\n':
|
||||||
return NewSoftBreak(index), nil
|
return NewTokenSoftBreak(index), nil
|
||||||
case letter == '{':
|
case letter == '{':
|
||||||
return NewOpenBrace(index), nil
|
return NewTokenOpenBrace(index), nil
|
||||||
case letter == '}':
|
case letter == '}':
|
||||||
return NewCloseBrace(index), nil
|
return NewTokenCloseBrace(index), nil
|
||||||
case letter == ':':
|
case letter == ':':
|
||||||
if _, err := parseCharacter(i, '='); err != nil {
|
if _, err := scanCharacter(i, '='); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return NewAssign(index), nil
|
return NewTokenAssign(index), nil
|
||||||
}
|
}
|
||||||
case letter == ';':
|
case letter == ';':
|
||||||
return NewHardBreak(index), nil
|
return NewTokenHardBreak(index), nil
|
||||||
case letter == '#':
|
case letter == '#':
|
||||||
// Skip everything until the next newline or EOF.
|
// Skip everything until the next newline or EOF.
|
||||||
for !i.Done() {
|
for !i.Done() {
|
||||||
@@ -98,27 +98,27 @@ func getToken(i *iterator.Iterator[rune]) (*Token, error) {
|
|||||||
atom := []rune{letter}
|
atom := []rune{letter}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if r, err := parseRune(i, isVariable); err != nil {
|
if r, err := scanRune(i, isVariable); err != nil {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
atom = append(atom, r)
|
atom = append(atom, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewAtom(string(atom), index), nil
|
return NewTokenAtom(string(atom), index), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("unknown character '%v'", string(letter))
|
return nil, fmt.Errorf("unknown character '%v'", string(letter))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a string into tokens.
|
// scan a string into tokens.
|
||||||
func Parse(input string) ([]Token, error) {
|
func scan(input string) ([]Token, error) {
|
||||||
i := iterator.Of([]rune(input))
|
i := iterator.Of([]rune(input))
|
||||||
tokens := []Token{}
|
tokens := []Token{}
|
||||||
errorList := []error{}
|
errorList := []error{}
|
||||||
|
|
||||||
for !i.Done() {
|
for !i.Done() {
|
||||||
token, err := getToken(i)
|
token, err := scanToken(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorList = append(errorList, err)
|
errorList = append(errorList, err)
|
||||||
} else if token != nil {
|
} else if token != nil {
|
||||||
91
pkg/saccharine/token.go
Normal file
91
pkg/saccharine/token.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package saccharine
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// All tokens in the pseudo-lambda language.
|
||||||
|
type TokenType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TokenOpenParen TokenType = iota // Denotes the '(' token.
|
||||||
|
TokenCloseParen // Denotes the ')' token.
|
||||||
|
TokenOpenBrace // Denotes the '{' token.
|
||||||
|
TokenCloseBrace // Denotes the '}' token.
|
||||||
|
TokenHardBreak // Denotes the ';' token.
|
||||||
|
TokenAssign // Denotes the ':=' token.
|
||||||
|
TokenAtom // Denotes an alpha-numeric variable.
|
||||||
|
TokenSlash // Denotes the '/' token.
|
||||||
|
TokenDot // Denotes the '.' token.
|
||||||
|
TokenSoftBreak // Denotes a new-line.
|
||||||
|
)
|
||||||
|
|
||||||
|
// A representation of a token in source code.
|
||||||
|
type Token struct {
|
||||||
|
Column int // Where the token begins in the source text.
|
||||||
|
Type TokenType // What type the token is.
|
||||||
|
Value string // The value of the token.
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenOpenParen(column int) *Token {
|
||||||
|
return &Token{Type: TokenOpenParen, Column: column, Value: "("}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenCloseParen(column int) *Token {
|
||||||
|
return &Token{Type: TokenCloseParen, Column: column, Value: ")"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenOpenBrace(column int) *Token {
|
||||||
|
return &Token{Type: TokenOpenBrace, Column: column, Value: "{"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenCloseBrace(column int) *Token {
|
||||||
|
return &Token{Type: TokenCloseBrace, Column: column, Value: "}"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenDot(column int) *Token {
|
||||||
|
return &Token{Type: TokenDot, Column: column, Value: "."}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenHardBreak(column int) *Token {
|
||||||
|
return &Token{Type: TokenHardBreak, Column: column, Value: ";"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenAssign(column int) *Token {
|
||||||
|
return &Token{Type: TokenAssign, Column: column, Value: ":="}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenSlash(column int) *Token {
|
||||||
|
return &Token{Type: TokenSlash, Column: column, Value: "\\"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenAtom(name string, column int) *Token {
|
||||||
|
return &Token{Type: TokenAtom, Column: column, Value: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenSoftBreak(column int) *Token {
|
||||||
|
return &Token{Type: TokenSoftBreak, Column: column, Value: "\\n"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TokenType) Name() string {
|
||||||
|
switch t {
|
||||||
|
case TokenOpenParen:
|
||||||
|
return "("
|
||||||
|
case TokenCloseParen:
|
||||||
|
return ")"
|
||||||
|
case TokenSlash:
|
||||||
|
return "\\"
|
||||||
|
case TokenDot:
|
||||||
|
return "."
|
||||||
|
case TokenAtom:
|
||||||
|
return "ATOM"
|
||||||
|
case TokenSoftBreak:
|
||||||
|
return "\\n"
|
||||||
|
case TokenHardBreak:
|
||||||
|
return ";"
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unknown token type %v", t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Token) Name() string {
|
||||||
|
return t.Type.Name()
|
||||||
|
}
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package token
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// All tokens in the pseudo-lambda language.
|
|
||||||
type Type int
|
|
||||||
|
|
||||||
const (
|
|
||||||
OpenParen Type = iota // Denotes the '(' token.
|
|
||||||
CloseParen // Denotes the ')' token.
|
|
||||||
OpenBrace // Denotes the '{' token.
|
|
||||||
CloseBrace // Denotes the '}' token.
|
|
||||||
HardBreak // Denotes the ';' token.
|
|
||||||
Assign // Denotes the ':=' token.
|
|
||||||
Atom // Denotes an alpha-numeric variable.
|
|
||||||
Slash // Denotes the '/' token.
|
|
||||||
Dot // Denotes the '.' token.
|
|
||||||
SoftBreak // Denotes a new-line.
|
|
||||||
)
|
|
||||||
|
|
||||||
// A representation of a token in source code.
|
|
||||||
type Token struct {
|
|
||||||
Column int // Where the token begins in the source text.
|
|
||||||
Type Type // What type the token is.
|
|
||||||
Value string // The value of the token.
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOpenParen(column int) *Token {
|
|
||||||
return &Token{Type: OpenParen, Column: column, Value: "("}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCloseParen(column int) *Token {
|
|
||||||
return &Token{Type: CloseParen, Column: column, Value: ")"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOpenBrace(column int) *Token {
|
|
||||||
return &Token{Type: OpenBrace, Column: column, Value: "{"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCloseBrace(column int) *Token {
|
|
||||||
return &Token{Type: CloseBrace, Column: column, Value: "}"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDot(column int) *Token {
|
|
||||||
return &Token{Type: Dot, Column: column, Value: "."}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHardBreak(column int) *Token {
|
|
||||||
return &Token{Type: HardBreak, Column: column, Value: ";"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAssign(column int) *Token {
|
|
||||||
return &Token{Type: Assign, Column: column, Value: ":="}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSlash(column int) *Token {
|
|
||||||
return &Token{Type: Slash, Column: column, Value: "\\"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAtom(name string, column int) *Token {
|
|
||||||
return &Token{Type: Atom, Column: column, Value: name}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSoftBreak(column int) *Token {
|
|
||||||
return &Token{Type: SoftBreak, Column: column, Value: "\\n"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Name(typ Type) string {
|
|
||||||
switch typ {
|
|
||||||
case OpenParen:
|
|
||||||
return "("
|
|
||||||
case CloseParen:
|
|
||||||
return ")"
|
|
||||||
case Slash:
|
|
||||||
return "\\"
|
|
||||||
case Dot:
|
|
||||||
return "."
|
|
||||||
case Atom:
|
|
||||||
return "ATOM"
|
|
||||||
case SoftBreak:
|
|
||||||
return "\\n"
|
|
||||||
case HardBreak:
|
|
||||||
return ";"
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("unknown token type %v", typ))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Token) Name() string {
|
|
||||||
return Name(t.Type)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user