Commit inicial
This commit is contained in:
commit
e3bd2658cd
5 changed files with 512 additions and 0 deletions
3
go.mod
Normal file
3
go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
module sexp
|
||||||
|
|
||||||
|
go 1.24.2
|
||||||
69
input.go
Normal file
69
input.go
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Input struct {
|
||||||
|
index int
|
||||||
|
input []Lexeme
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in Input) Index() int {
|
||||||
|
return in.index
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in Input) String() string {
|
||||||
|
_ = in.input[in.index]
|
||||||
|
return fmt.Sprintf("%v", in.input[in.index:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *Input) Len() int {
|
||||||
|
// _ = in.input[in.index]
|
||||||
|
return len(in.input[in.index:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *Input) Peek(i int) Lexeme {
|
||||||
|
return in.input[in.index+i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *Input) Pop() Lexeme {
|
||||||
|
v := in.input[in.index]
|
||||||
|
in.index++
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *Input) Seek(i int) {
|
||||||
|
_ = in.input[i]
|
||||||
|
in.index = i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *Input) Take(amount int) *Input {
|
||||||
|
var out = &Input{
|
||||||
|
index: 0,
|
||||||
|
input: in.input[in.index : in.index+amount],
|
||||||
|
}
|
||||||
|
in.index += amount
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *Input) Find(open, close *Token) (int, bool) {
|
||||||
|
level := 1
|
||||||
|
_ = in.input[in.index]
|
||||||
|
idx := slices.IndexFunc(in.input[in.index:], func(a Lexeme) bool {
|
||||||
|
switch {
|
||||||
|
case a.Equals(open):
|
||||||
|
level++
|
||||||
|
case a.Equals(close):
|
||||||
|
level--
|
||||||
|
return level == 0
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
if idx > -1 {
|
||||||
|
return idx, true
|
||||||
|
} else {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
159
lex.go
Normal file
159
lex.go
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type position struct {
|
||||||
|
pos int
|
||||||
|
filepath string
|
||||||
|
line, column int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p position) String() string {
|
||||||
|
return fmt.Sprintf("%s:%d:%d", p.filepath, p.line, p.column)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *position) Position() position {
|
||||||
|
return *p
|
||||||
|
}
|
||||||
|
|
||||||
|
type Range [2]position
|
||||||
|
|
||||||
|
type token[T comparable] struct {
|
||||||
|
position
|
||||||
|
value T
|
||||||
|
}
|
||||||
|
|
||||||
|
type Lexeme interface {
|
||||||
|
Position() position
|
||||||
|
Equals(Lexeme) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Id string
|
||||||
|
|
||||||
|
type Token token[string]
|
||||||
|
|
||||||
|
type Int token[int]
|
||||||
|
|
||||||
|
type Identifier token[Id]
|
||||||
|
|
||||||
|
var (
|
||||||
|
Lparen = &Token{value: "("}
|
||||||
|
Rparen = &Token{value: ")"}
|
||||||
|
Space = &Token{value: " "}
|
||||||
|
Tab = &Token{value: "\t"}
|
||||||
|
Newline = &Token{value: "\n"}
|
||||||
|
SingleQuote = &Token{value: "'"}
|
||||||
|
Let = &Token{value: "let"}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t Token) String() string {
|
||||||
|
return fmt.Sprint(t.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Int) String() string {
|
||||||
|
return fmt.Sprint(t.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Identifier) String() string {
|
||||||
|
return fmt.Sprint(t.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var knownTokens = []*Token{
|
||||||
|
Lparen,
|
||||||
|
Rparen,
|
||||||
|
Space,
|
||||||
|
Tab,
|
||||||
|
Newline,
|
||||||
|
SingleQuote,
|
||||||
|
Let,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) Equals(a Lexeme) bool {
|
||||||
|
switch x := a.(type) {
|
||||||
|
case *Token:
|
||||||
|
return t.value == x.value
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Int) Equals(a Lexeme) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Identifier) Equals(a Lexeme) bool {
|
||||||
|
switch x := a.(type) {
|
||||||
|
case *Identifier:
|
||||||
|
return t.value == x.value
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchNumbers = regexp.MustCompile(`-?\d+`)
|
||||||
|
var matchIdentifier = regexp.MustCompile(`[^\'() ]+`)
|
||||||
|
|
||||||
|
func lex(source string) (*Input, error) {
|
||||||
|
var (
|
||||||
|
pos, line, column int
|
||||||
|
tokens []Lexeme
|
||||||
|
)
|
||||||
|
outer:
|
||||||
|
for len(source) > 0 {
|
||||||
|
p := position{pos: pos, line: line, column: column, filepath: "sourceinput"}
|
||||||
|
for _, try := range knownTokens {
|
||||||
|
if !strings.HasPrefix(source, try.value) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
l = len(try.value)
|
||||||
|
t = &Token{position: p, value: try.value}
|
||||||
|
)
|
||||||
|
source = source[l:]
|
||||||
|
pos += l
|
||||||
|
switch try {
|
||||||
|
case Newline:
|
||||||
|
line += 1
|
||||||
|
column = 0
|
||||||
|
case Tab, Space:
|
||||||
|
column += l
|
||||||
|
default:
|
||||||
|
column += l
|
||||||
|
tokens = append(tokens, t)
|
||||||
|
}
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
index := matchNumbers.FindStringSubmatchIndex(source)
|
||||||
|
if index != nil && index[0] == 0 {
|
||||||
|
a := source[index[0]:index[1]]
|
||||||
|
conv, err := strconv.Atoi(a)
|
||||||
|
if err == nil {
|
||||||
|
var i = &Int{position: p, value: conv}
|
||||||
|
tokens = append(tokens, i)
|
||||||
|
l := len(a)
|
||||||
|
source = source[l:]
|
||||||
|
column += l
|
||||||
|
pos += l
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index = matchIdentifier.FindStringIndex(source)
|
||||||
|
if index != nil && index[0] == 0 {
|
||||||
|
a := source[index[0]:index[1]]
|
||||||
|
var id = &Identifier{position: p, value: Id(a)}
|
||||||
|
tokens = append(tokens, id)
|
||||||
|
l := len(a)
|
||||||
|
source = source[l:]
|
||||||
|
column += l
|
||||||
|
pos += l
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unrecognized token '%c' at %s", source[0], p)
|
||||||
|
}
|
||||||
|
return &Input{input: tokens}, nil
|
||||||
|
}
|
||||||
117
main.go
Normal file
117
main.go
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var source = `
|
||||||
|
(+ 1 2 3 4)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (v *Value[T]) Step() (Expression, error) {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Quoted) Step() (Expression, error) {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Declaration) Step() (Expression, error) {
|
||||||
|
return &Value[int]{0}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Identifier) Step() (Expression, error) {
|
||||||
|
switch v.value {
|
||||||
|
case "+":
|
||||||
|
f := func(args []Expression) (Expression, error) {
|
||||||
|
sum := 0
|
||||||
|
for _, arg := range args {
|
||||||
|
if !isValue(arg) {
|
||||||
|
panic("!")
|
||||||
|
}
|
||||||
|
switch x := arg.(type) {
|
||||||
|
case *Value[int]:
|
||||||
|
sum += x.value
|
||||||
|
case *Value[*Int]:
|
||||||
|
sum += x.value.value
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid type")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Value[int]{sum}, nil
|
||||||
|
}
|
||||||
|
return &Value[Function]{f}, nil
|
||||||
|
default:
|
||||||
|
panic("!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *List) Step() (Expression, error) {
|
||||||
|
for i, expr := range v.els {
|
||||||
|
if !isValue(expr) {
|
||||||
|
e, err := expr.Step()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v.els[i] = e
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(v.els) == 0 {
|
||||||
|
return nil, fmt.Errorf("empty list")
|
||||||
|
}
|
||||||
|
f, ok := v.els[0].(*Value[Function])
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("not a function")
|
||||||
|
}
|
||||||
|
r, err := f.value(v.els[1:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValue(e Expression) bool {
|
||||||
|
switch e.(type) {
|
||||||
|
case *Value[int], *Value[*Int], *Value[Function], *Quoted:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fullStep(e Expression) (Expression, error) {
|
||||||
|
for !isValue(e) {
|
||||||
|
n, err := e.Step()
|
||||||
|
if err != nil {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
e = n
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
source = os.Args[1]
|
||||||
|
}
|
||||||
|
tk, err := lex(source)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
p, err := consume(tk)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
s, err := p.Step()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
result, err := fullStep(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(result)
|
||||||
|
}
|
||||||
164
parse.go
Normal file
164
parse.go
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var noMatch = errors.New("no match")
|
||||||
|
|
||||||
|
type Function func([]Expression) (Expression, error)
|
||||||
|
|
||||||
|
type Expression interface {
|
||||||
|
Step() (Expression, error)
|
||||||
|
// Visit(func(Expression) error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type List struct {
|
||||||
|
els []Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
type Value[T interface{ *Int | int | Function }] struct {
|
||||||
|
value T
|
||||||
|
}
|
||||||
|
|
||||||
|
type Quoted struct {
|
||||||
|
expr Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
type Declaration struct {
|
||||||
|
id *Identifier
|
||||||
|
expr Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Value[T]) String() string {
|
||||||
|
return fmt.Sprint(v.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Quoted) String() string {
|
||||||
|
return fmt.Sprintf("'%s", v.expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v List) String() string {
|
||||||
|
var s []string
|
||||||
|
for _, e := range v.els {
|
||||||
|
s = append(s, fmt.Sprint(e))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("(%s)", strings.Join(s, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseQuotedObject(in *Input) (Expression, error) {
|
||||||
|
start := in.Index()
|
||||||
|
l := in.Pop()
|
||||||
|
switch {
|
||||||
|
case l.Equals(SingleQuote):
|
||||||
|
n, err := consume(in)
|
||||||
|
if err != nil {
|
||||||
|
in.Seek(start)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Quoted{n}, nil
|
||||||
|
default:
|
||||||
|
in.Seek(start)
|
||||||
|
return nil, noMatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSymbol(in *Input) (Expression, error) {
|
||||||
|
start := in.Index()
|
||||||
|
l := in.Pop()
|
||||||
|
switch x := l.(type) {
|
||||||
|
case *Identifier:
|
||||||
|
return x, nil
|
||||||
|
default:
|
||||||
|
in.Seek(start)
|
||||||
|
return nil, noMatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseValue(in *Input) (Expression, error) {
|
||||||
|
start := in.Index()
|
||||||
|
l := in.Pop()
|
||||||
|
switch x := l.(type) {
|
||||||
|
case *Int:
|
||||||
|
return &Value[*Int]{x}, nil
|
||||||
|
default:
|
||||||
|
in.Seek(start)
|
||||||
|
return nil, noMatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseList(in *Input) (Expression, error) {
|
||||||
|
start := in.Index()
|
||||||
|
openingParens := in.Pop()
|
||||||
|
if !openingParens.Equals(Lparen) {
|
||||||
|
in.Seek(start)
|
||||||
|
return nil, noMatch
|
||||||
|
}
|
||||||
|
idx, ok := in.Find(Lparen, Rparen)
|
||||||
|
if !ok {
|
||||||
|
in.Seek(start)
|
||||||
|
return nil, fmt.Errorf("unmatched parenthesis starting at %s", openingParens.Position())
|
||||||
|
}
|
||||||
|
if idx == 0 {
|
||||||
|
in.Seek(start)
|
||||||
|
return nil, fmt.Errorf("no function declared at %s", openingParens.Position())
|
||||||
|
}
|
||||||
|
list := in.Take(idx)
|
||||||
|
var args = new(List)
|
||||||
|
for list.Len() > 0 {
|
||||||
|
arg, err := consume(list)
|
||||||
|
if err != nil {
|
||||||
|
in.Seek(start)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
args.els = append(args.els, arg)
|
||||||
|
}
|
||||||
|
_ = in.Pop()
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLet(in *Input) (Expression, error) {
|
||||||
|
start := in.Index()
|
||||||
|
token := in.Pop()
|
||||||
|
if !token.Equals(Let) {
|
||||||
|
in.Seek(start)
|
||||||
|
return nil, noMatch
|
||||||
|
}
|
||||||
|
id, ok := in.Pop().(*Identifier)
|
||||||
|
if !ok {
|
||||||
|
in.Seek(start)
|
||||||
|
return nil, noMatch
|
||||||
|
}
|
||||||
|
expr, err := consume(in)
|
||||||
|
if err != nil {
|
||||||
|
in.Seek(start)
|
||||||
|
return nil, noMatch
|
||||||
|
}
|
||||||
|
return &Declaration{id: id, expr: expr}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParserFunc func(*Input) (Expression, error)
|
||||||
|
|
||||||
|
func consume(in *Input) (n Expression, err error) {
|
||||||
|
var parseFunctions = []ParserFunc{
|
||||||
|
parseQuotedObject,
|
||||||
|
parseList,
|
||||||
|
parseSymbol,
|
||||||
|
parseValue,
|
||||||
|
parseLet,
|
||||||
|
}
|
||||||
|
for _, f := range parseFunctions {
|
||||||
|
n, err := f(in)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, noMatch) {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unrecognized construction: %s", in)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue