commit e3bd2658cd032c9d386fa580eebc5ca3e9ddc8a4 Author: silva guimaraes Date: Sun May 25 02:35:11 2025 -0300 Commit inicial diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d114169 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module sexp + +go 1.24.2 diff --git a/input.go b/input.go new file mode 100644 index 0000000..0676d21 --- /dev/null +++ b/input.go @@ -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 + } +} diff --git a/lex.go b/lex.go new file mode 100644 index 0000000..b81c9b1 --- /dev/null +++ b/lex.go @@ -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 +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..712d034 --- /dev/null +++ b/main.go @@ -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) +} diff --git a/parse.go b/parse.go new file mode 100644 index 0000000..c099a59 --- /dev/null +++ b/parse.go @@ -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) +}