Commit inicial

This commit is contained in:
silva guimaraes 2025-05-25 02:35:11 -03:00
commit e3bd2658cd
5 changed files with 512 additions and 0 deletions

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module sexp
go 1.24.2

69
input.go Normal file
View 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
View 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
View 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
View 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)
}