ginger/expr/expr.go

566 lines
13 KiB
Go

package expr
import (
"fmt"
"io"
"strconv"
"strings"
"llvm.org/llvm/bindings/go/llvm"
"github.com/mediocregopher/ginger/lexer"
)
// TODO empty blocks
// TODO empty parenthesis
// TODO need to figure out how to test LLVMVal stuff
// Actual represents the actual expression in question, and has certain
// properties. It is wrapped by Expr which also holds onto contextual
// information, like the token to which Actual was originally parsed from
type Actual interface {
// Initializes an llvm.Value and returns it.
LLVMVal(llvm.Builder) llvm.Value
}
// equaler is used to compare two expressions. The comparison should not take
// into account Token values, only the actual value being represented
type equaler interface {
equal(equaler) bool
}
// Expr contains the actual expression as well as some contextual information
// wrapping it. Most interactions will be with this and not with the Actual
// directly.
type Expr struct {
Actual Actual
// Token is a nice-to-have, nothing will break if it's not there
Token lexer.Token
val *llvm.Value
}
// LLVMVal passes its arguments to the underlying Actual instance. It caches the
// result, so if this is called multiple times the underlying one is only called
// the first time.
func (e Expr) LLVMVal(builder llvm.Builder) llvm.Value {
if e.val == nil {
v := e.Actual.LLVMVal(builder)
e.val = &v
}
return *e.val
}
// will panic if either Expr's Actual doesn't implement equaler
func (e Expr) equal(e2 Expr) bool {
eq1, ok1 := e.Actual.(equaler)
eq2, ok2 := e2.Actual.(equaler)
if !ok1 || !ok2 {
panic(fmt.Sprintf("can't compare %T and %T", e.Actual, e2.Actual))
}
return eq1.equal(eq2)
}
////////////////////////////////////////////////////////////////////////////////
// Bool represents a true or false value
type Bool bool
// LLVMVal implements the Actual interface method
func (b Bool) LLVMVal(builder llvm.Builder) llvm.Value {
return llvm.Value{}
}
func (b Bool) equal(e equaler) bool {
bb, ok := e.(Bool)
if !ok {
return false
}
return bb == b
}
////////////////////////////////////////////////////////////////////////////////
// Int represents an integer value
type Int int64
// LLVMVal implements the Actual interface method
func (i Int) LLVMVal(builder llvm.Builder) llvm.Value {
v := builder.CreateAlloca(llvm.Int64Type(), "")
builder.CreateStore(llvm.ConstInt(llvm.Int64Type(), uint64(i), false), v)
return v
}
func (i Int) equal(e equaler) bool {
ii, ok := e.(Int)
if !ok {
return false
}
return ii == i
}
////////////////////////////////////////////////////////////////////////////////
// String represents a string value
type String string
// LLVMVal implements the Actual interface method
func (s String) LLVMVal(builder llvm.Builder) llvm.Value {
return llvm.Value{}
}
func (s String) equal(e equaler) bool {
ss, ok := e.(String)
if !ok {
return false
}
return ss == s
}
////////////////////////////////////////////////////////////////////////////////
// Identifier represents a binding to some other value which has been given a
// name
type Identifier string
// LLVMVal implements the Actual interface method
func (id Identifier) LLVMVal(builder llvm.Builder) llvm.Value {
return llvm.Value{}
}
func (id Identifier) equal(e equaler) bool {
idid, ok := e.(Identifier)
if !ok {
return false
}
return idid == id
}
////////////////////////////////////////////////////////////////////////////////
// Macro is an identifier for a macro which can be used to transform
// expressions. The tokens for macros start with a '%', but the Macro identifier
// itself has that stripped off
type Macro string
// String returns the Macro with a '%' prepended to it
func (m Macro) String() string {
return "%" + string(m)
}
// LLVMVal implements the Actual interface method
func (m Macro) LLVMVal(builder llvm.Builder) llvm.Value {
panic("Macros have no inherent LLVMVal")
}
func (m Macro) equal(e equaler) bool {
mm, ok := e.(Macro)
if !ok {
return false
}
return m == mm
}
////////////////////////////////////////////////////////////////////////////////
// Tuple represents a fixed set of expressions which are interacted with as if
// they were a single value
type Tuple []Expr
func (tup Tuple) String() string {
strs := make([]string, len(tup))
for i := range tup {
strs[i] = fmt.Sprint(tup[i].Actual)
}
return "(" + strings.Join(strs, ", ") + ")"
}
// LLVMVal implements the Actual interface method
func (tup Tuple) LLVMVal(builder llvm.Builder) llvm.Value {
return llvm.Value{}
}
func (tup Tuple) equal(e equaler) bool {
tuptup, ok := e.(Tuple)
if !ok || len(tuptup) != len(tup) {
return false
}
for i := range tup {
if !tup[i].equal(tuptup[i]) {
return false
}
}
return true
}
////////////////////////////////////////////////////////////////////////////////
// Statement represents an actual action which will be taken. The input value is
// used as the input to the pipe, and the output of the pipe is the output of
// the statement
type Statement struct {
In Expr
To Expr
}
func (s Statement) String() string {
return fmt.Sprintf("(%v > %s)", s.In.Actual, s.To.Actual)
}
// LLVMVal implements the Actual interface method
func (s Statement) LLVMVal(builder llvm.Builder) llvm.Value {
m, ok := s.To.Actual.(Macro)
if !ok {
// TODO proper error
panic("statement To is not a macro")
}
fn, ok := macros[m]
if !ok {
// TODO proper error
panic(fmt.Sprintf("unknown macro %q", m))
}
newe, err := fn(s.In)
if err != nil {
// TODO proper error
panic(err)
}
return newe.LLVMVal(builder)
}
func (s Statement) equal(e equaler) bool {
ss, ok := e.(Statement)
return ok && s.In.equal(ss.In) && s.To.equal(ss.To)
}
////////////////////////////////////////////////////////////////////////////////
// Block represents a set of statements which share a scope, i.e. If one
// statement binds a variable the rest of the statements in the block can use
// that variable, including sub-blocks within this one.
type Block []Statement
func (b Block) String() string {
strs := make([]string, len(b))
for i := range b {
strs[i] = b[i].String()
}
return fmt.Sprintf("{ %s }", strings.Join(strs, " "))
}
// LLVMVal implements the Actual interface method
func (b Block) LLVMVal(builder llvm.Builder) llvm.Value {
return llvm.Value{}
}
func (b Block) equal(e equaler) bool {
bb, ok := e.(Block)
if !ok {
return false
}
for i := range b {
if !b[i].equal(bb[i]) {
return false
}
}
return true
}
////////////////////////////////////////////////////////////////////////////////
type exprErr struct {
reason string
err error
tok lexer.Token
tokCtx string // e.g. "block starting at" or "open paren at"
}
func (e exprErr) Error() string {
var msg string
if e.err != nil {
msg = e.err.Error()
} else {
msg = e.reason
}
if err := e.tok.Err(); err != nil {
msg += " - token error: " + err.Error()
} else if (e.tok != lexer.Token{}) {
msg += " - "
if e.tokCtx != "" {
msg += e.tokCtx + ": "
}
msg = fmt.Sprintf("%s [line:%d col:%d]", msg, e.tok.Row, e.tok.Col)
}
return msg
}
////////////////////////////////////////////////////////////////////////////////
// toks[0] must be start
func sliceEnclosedToks(toks []lexer.Token, start, end lexer.Token) ([]lexer.Token, []lexer.Token, error) {
c := 1
ret := []lexer.Token{}
first := toks[0]
for i, tok := range toks[1:] {
if tok.Err() != nil {
return nil, nil, exprErr{
reason: fmt.Sprintf("missing closing %v", end),
tok: tok,
}
}
if tok.Equal(start) {
c++
} else if tok.Equal(end) {
c--
}
if c == 0 {
return ret, toks[2+i:], nil
}
ret = append(ret, tok)
}
return nil, nil, exprErr{
reason: fmt.Sprintf("missing closing %v", end),
tok: first,
tokCtx: "starting at",
}
}
// Parse reads in all expressions it can from the given io.Reader and returns
// them
func Parse(r io.Reader) ([]Expr, error) {
toks := readAllToks(r)
var ret []Expr
var expr Expr
var err error
for len(toks) > 0 {
if toks[0].TokenType == lexer.EOF {
return ret, nil
}
expr, toks, err = parse(toks)
if err != nil {
return nil, err
}
ret = append(ret, expr)
}
return ret, nil
}
// ParseAsBlock reads the given io.Reader as if it was implicitly surrounded by
// curly braces, making it into a Block. This means all expressions from the
// io.Reader *must* be statements. The returned Expr's Actual will always be a
// Block.
func ParseAsBlock(r io.Reader) (Expr, error) {
return parseBlock(readAllToks(r))
}
func readAllToks(r io.Reader) []lexer.Token {
l := lexer.New(r)
var toks []lexer.Token
for l.HasNext() {
toks = append(toks, l.Next())
}
return toks
}
// For all parse methods it is assumed that toks is not empty
var (
openParen = lexer.Token{TokenType: lexer.Wrapper, Val: "("}
closeParen = lexer.Token{TokenType: lexer.Wrapper, Val: ")"}
openCurly = lexer.Token{TokenType: lexer.Wrapper, Val: "{"}
closeCurly = lexer.Token{TokenType: lexer.Wrapper, Val: "}"}
comma = lexer.Token{TokenType: lexer.Punctuation, Val: ","}
arrow = lexer.Token{TokenType: lexer.Punctuation, Val: ">"}
)
func parse(toks []lexer.Token) (Expr, []lexer.Token, error) {
expr, toks, err := parseSingle(toks)
if err != nil {
return Expr{}, nil, err
}
if len(toks) > 0 && toks[0].TokenType == lexer.Punctuation {
return parseConnectingPunct(toks, expr)
}
return expr, toks, nil
}
func parseSingle(toks []lexer.Token) (Expr, []lexer.Token, error) {
var expr Expr
var err error
if toks[0].Err() != nil {
return Expr{}, nil, exprErr{
reason: "could not parse token",
tok: toks[0],
}
}
if toks[0].Equal(openParen) {
starter := toks[0]
var ptoks []lexer.Token
ptoks, toks, err = sliceEnclosedToks(toks, openParen, closeParen)
if err != nil {
return Expr{}, nil, err
}
if expr, ptoks, err = parse(ptoks); err != nil {
return Expr{}, nil, err
} else if len(ptoks) > 0 {
return Expr{}, nil, exprErr{
reason: "multiple expressions inside parenthesis",
tok: starter,
tokCtx: "starting at",
}
}
return expr, toks, nil
} else if toks[0].Equal(openCurly) {
var btoks []lexer.Token
btoks, toks, err = sliceEnclosedToks(toks, openCurly, closeCurly)
if err != nil {
return Expr{}, nil, err
}
if expr, err = parseBlock(btoks); err != nil {
return Expr{}, nil, err
}
return expr, toks, nil
}
if expr, err = parseNonPunct(toks[0]); err != nil {
return Expr{}, nil, err
}
return expr, toks[1:], nil
}
func parseNonPunct(tok lexer.Token) (Expr, error) {
if tok.TokenType == lexer.Identifier {
return parseIdentifier(tok)
} else if tok.TokenType == lexer.String {
return parseString(tok)
}
return Expr{}, exprErr{
reason: "unexpected non-punctuation token",
tok: tok,
}
}
func parseIdentifier(t lexer.Token) (Expr, error) {
e := Expr{Token: t}
if t.Val[0] == '-' || (t.Val[0] >= '0' && t.Val[0] <= '9') {
n, err := strconv.ParseInt(t.Val, 10, 64)
if err != nil {
return Expr{}, exprErr{
err: err,
tok: t,
}
}
e.Actual = Int(n)
} else if t.Val == "%true" {
e.Actual = Bool(true)
} else if t.Val == "%false" {
e.Actual = Bool(false)
} else if t.Val[0] == '%' {
e.Actual = Macro(t.Val[1:])
} else {
e.Actual = Identifier(t.Val)
}
return e, nil
}
func parseString(t lexer.Token) (Expr, error) {
str, err := strconv.Unquote(t.Val)
if err != nil {
return Expr{}, exprErr{
err: err,
tok: t,
}
}
return Expr{Token: t, Actual: String(str)}, nil
}
func parseConnectingPunct(toks []lexer.Token, root Expr) (Expr, []lexer.Token, error) {
if toks[0].Equal(comma) {
return parseTuple(toks, root)
} else if toks[0].Equal(arrow) {
expr, toks, err := parse(toks[1:])
if err != nil {
return Expr{}, nil, err
}
return Expr{Token: root.Token, Actual: Statement{In: root, To: expr}}, toks, nil
}
return root, toks, nil
}
func parseTuple(toks []lexer.Token, root Expr) (Expr, []lexer.Token, error) {
rootTup, ok := root.Actual.(Tuple)
if !ok {
rootTup = Tuple{root}
}
// rootTup is modified throughout, be we need to make it into an Expr for
// every return, which is annoying. so make a function to do it on the fly
mkRoot := func() Expr {
return Expr{Token: rootTup[0].Token, Actual: rootTup}
}
if len(toks) < 2 {
return mkRoot(), toks, nil
} else if !toks[0].Equal(comma) {
if toks[0].TokenType == lexer.Punctuation {
return parseConnectingPunct(toks, mkRoot())
}
return mkRoot(), toks, nil
}
var expr Expr
var err error
if expr, toks, err = parseSingle(toks[1:]); err != nil {
return Expr{}, nil, err
}
rootTup = append(rootTup, expr)
return parseTuple(toks, mkRoot())
}
// parseBlock assumes that the given token list is the entire block, already
// pulled from outer curly braces by sliceEnclosedToks, or determined to be the
// entire block in some other way.
func parseBlock(toks []lexer.Token) (Expr, error) {
b := Block{}
first := toks[0]
var expr Expr
var err error
for {
if len(toks) == 0 {
return Expr{Token: first, Actual: b}, nil
}
if expr, toks, err = parse(toks); err != nil {
return Expr{}, err
}
stmt, ok := expr.Actual.(Statement)
if !ok {
return Expr{}, exprErr{
reason: "blocks may only contain full statements",
tok: expr.Token,
tokCtx: "non-statement here",
}
}
b = append(b, stmt)
}
}