diff --git a/LICENSE b/LICENSE index b940823..aeedab1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014 Branden J Brown +Copyright (c) 2014, 2018 Branden J Brown This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/control.go b/control.go index 2fa6d6a..e388802 100644 --- a/control.go +++ b/control.go @@ -22,7 +22,6 @@ type StopStatus int const ( // I have resisted the urge to name this DontStopBelieving. - // Unfortunately, I have just realized it is now a rape joke. NoStop StopStatus = iota ReturnStop BreakStop diff --git a/io/main.go b/io/main.go index 9b14c7b..dae6525 100644 --- a/io/main.go +++ b/io/main.go @@ -10,7 +10,7 @@ import ( func main() { vm := iolang.NewVM() - // iolang.Debugvm = vm + iolang.Debugvm = vm iolang.SetSlot(vm.Lobby, "ps1", vm.NewString("io> ")) iolang.SetSlot(vm.Lobby, "ps2", vm.NewString("... ")) iolang.SetSlot(vm.Lobby, "isRunning", vm.True) diff --git a/message.go b/message.go index ec8b5bc..d8d3106 100644 --- a/message.go +++ b/message.go @@ -31,6 +31,34 @@ const ( StringSym ) +// IdentMessage creates a message of a given identifier. Additional messages +// may be passed as arguments. +func (vm *VM) IdentMessage(s string, args ...*Message) *Message { + return &Message{ + Object: Object{Slots: vm.DefaultSlots["Message"], Protos: []Interface{vm.BaseObject}}, + Symbol: Symbol{Kind: IdentSym, Text: s}, + Args: args, + } +} + +// StringMessage creates message carrying a string value. +func (vm *VM) StringMessage(s string) *Message { + return &Message{ + Object: Object{Slots: vm.DefaultSlots["Message"], Protos: []Interface{vm.BaseObject}}, + Symbol: Symbol{Kind: StringSym, String: s}, + Memo: vm.NewString(s), + } +} + +// NumberMessage creates a message carrying a numeric value. +func (vm *VM) NumberMessage(v float64) *Message { + return &Message{ + Object: Object{Slots: vm.DefaultSlots["Message"], Protos: []Interface{vm.BaseObject}}, + Symbol: Symbol{Kind: NumSym, Num: v}, + Memo: vm.NewNumber(v), + } +} + func (m *Message) AssertArgCount(name string, n int) error { if len(m.Args) != n { return fmt.Errorf("%s must have %d arguments", name, n) @@ -39,7 +67,7 @@ func (m *Message) AssertArgCount(name string, n int) error { } func (m *Message) ArgAt(n int) *Message { - if n >= len(m.Args) { + if n >= len(m.Args) || n < 0 { return nil } return m.Args[n] @@ -88,11 +116,20 @@ func (m *Message) EvalArgAt(vm *VM, locals Interface, n int) Interface { return m.ArgAt(n).Eval(vm, locals) } -// Evaluate a message in the context of the given VM. A nil message evaluates -// to vm.Nil. +// Evaluate a message in the context of the given VM. This is a proxy to Send +// using locals as the target. func (m *Message) Eval(vm *VM, locals Interface) (result Interface) { + return m.Send(vm, locals, locals) +} + +// Send evaluates a message in the context of the given VM, targeting an +// object. If target is nil, it becomes the locals. A nil Message evaluates to +// vm.Nil. +func (m *Message) Send(vm *VM, locals, target Interface) (result Interface) { result = vm.Nil - target := locals + if target == nil { + target = locals + } for m != nil { if m.Memo != nil { result = m.Memo @@ -103,6 +140,7 @@ func (m *Message) Eval(vm *VM, locals Interface) (result Interface) { // fmt.Println("ident:", m.Symbol.Text) if newtarget, proto := GetSlot(target, m.Symbol.Text); proto != nil { // We have the slot. + // fmt.Println("we have the slot") switch a := newtarget.(type) { case Stop: a.Result = result @@ -112,6 +150,7 @@ func (m *Message) Eval(vm *VM, locals Interface) (result Interface) { default: result = newtarget } + // fmt.Println("target goes from", vm.AsString(target), "to", vm.AsString(newtarget)) target = newtarget } else if forward, fp := GetSlot(target, "forward"); fp != nil { // fmt.Println("forwarding", m.Symbol.Text) @@ -135,6 +174,7 @@ func (m *Message) Eval(vm *VM, locals Interface) (result Interface) { panic(fmt.Sprintf("iolang: invalid Symbol: %#v", m.Symbol)) } } + // fmt.Println("evaluated") if result == nil { // No message should evaluate to something that is not an Io // object, so we want to convert nil to vm.Nil. @@ -143,17 +183,33 @@ func (m *Message) Eval(vm *VM, locals Interface) (result Interface) { if m.Symbol.Kind != SemiSym { target = result } + // fmt.Println("m goes from", m.Name(), "to", m.Next.Name()) m = m.Next } return result } +// InsertAfter links another message to follow this one. +func (m *Message) InsertAfter(other *Message) { + if m.Next != nil { + m.Next.Prev = other + } + if other != nil { + other.Next = m.Next + other.Prev = m + } + m.Next = other +} + func (m *Message) Name() string { + if m == nil { + return "" + } switch m.Symbol.Kind { case SemiSym, IdentSym: return m.Symbol.Text case NumSym: - return fmt.Sprintf("%d", m.Symbol.Num) + return fmt.Sprintf("%g", m.Symbol.Num) case StringSym: return m.Symbol.String default: @@ -162,7 +218,7 @@ func (m *Message) Name() string { } func (m *Message) String() string { - return "message" + return "message-" + m.Name() } func (m *Message) stringRecurse(vm *VM, b *bytes.Buffer) { @@ -170,40 +226,42 @@ func (m *Message) stringRecurse(vm *VM, b *bytes.Buffer) { b.WriteString("") return } - if m.Memo != nil { - if msg, ok := m.Memo.(*Message); ok { - b.WriteString("") + for m != nil { + if m.Memo != nil { + if msg, ok := m.Memo.(*Message); ok { + b.WriteString("") + } else { + b.WriteString(vm.AsString(m.Memo)) + } } else { - b.WriteString(vm.AsString(m.Memo)) - } - } else { - switch m.Symbol.Kind { - case SemiSym: - b.WriteString("; ") - case IdentSym: - b.WriteString(m.Symbol.Text) - if len(m.Args) > 0 { - b.WriteByte('(') - m.Args[0].stringRecurse(vm, b) - for _, arg := range m.Args[1:] { - b.WriteString(", ") - arg.stringRecurse(vm, b) + switch m.Symbol.Kind { + case SemiSym: + b.WriteString("; ") + case IdentSym: + b.WriteString(m.Symbol.Text) + if len(m.Args) > 0 { + b.WriteByte('(') + m.Args[0].stringRecurse(vm, b) + for _, arg := range m.Args[1:] { + b.WriteString(", ") + arg.stringRecurse(vm, b) + } + b.WriteByte(')') } - b.WriteByte(')') + case NumSym: + fmt.Fprint(b, m.Symbol.Num) + case StringSym: + fmt.Fprintf(b, "%q", m.Symbol.String) + default: + panic("iolang: unknown symbol kind") } - case NumSym: - fmt.Fprint(b, m.Symbol.Num) - case StringSym: - fmt.Fprintf(b, "%q", m.Symbol.String) - default: - panic("iolang: unknown symbol kind") } - } - if m.Next != nil { - b.WriteByte(' ') - m.Next.stringRecurse(vm, b) + if m.Next != nil { + b.WriteByte(' ') + } + m = m.Next } } diff --git a/number.go b/number.go index bfa5fa6..7575563 100644 --- a/number.go +++ b/number.go @@ -38,9 +38,11 @@ func (n *Number) String() string { func (vm *VM) initNumber() { slots := Slots{ + "*": vm.NewCFunction(NumberMul, "NumberMul(v)"), + "+": vm.NewCFunction(NumberAdd, "NumberAdd(v)"), + "/": vm.NewCFunction(NumberDiv, "NumberDiv(v)"), "abs": vm.NewCFunction(NumberAbs, "NumberAbs()"), "acos": vm.NewCFunction(NumberAcos, "NumberAcos()"), - "add": vm.NewCFunction(NumberAdd, "NumberAdd(v)"), "asBinary": vm.NewCFunction(NumberAsBinary, "NumberAsBinary()"), "asCharacter": vm.NewCFunction(NumberAsCharacter, "NumberAsCharacter()"), "asHex": vm.NewCFunction(NumberAsHex, "NumberAsHex()"), @@ -136,7 +138,6 @@ func (vm *VM) initNumber() { vm.MemoizeNumber(math.MaxInt64) vm.MemoizeNumber(math.Inf(1)) vm.MemoizeNumber(math.Inf(-1)) - slots["+"] = slots["add"] slots["%"] = slots["mod"] slots["&"] = slots["bitwiseAnd"] slots["|"] = slots["bitwiseOr"] @@ -306,6 +307,14 @@ func NumberCubed(vm *VM, target, locals Interface, msg *Message) Interface { return vm.NewNumber(x * x * x) } +func NumberDiv(vm *VM, target, locals Interface, msg *Message) Interface { + arg, err := msg.NumberArgAt(vm, locals, 0) + if err != nil { + return vm.IoError(err) + } + return vm.NewNumber(target.(*Number).Value / arg.Value) +} + func NumberExp(vm *VM, target, locals Interface, msg *Message) Interface { return vm.NewNumber(math.Exp(target.(*Number).Value)) } @@ -431,6 +440,14 @@ func NumberNegate(vm *VM, target, locals Interface, msg *Message) Interface { return vm.NewNumber(-target.(*Number).Value) } +func NumberMul(vm *VM, target, locals Interface, msg *Message) Interface { + arg, err := msg.NumberArgAt(vm, locals, 0) + if err != nil { + return vm.IoError(err) + } + return vm.NewNumber(target.(*Number).Value * arg.Value) +} + func NumberPow(vm *VM, target, locals Interface, msg *Message) Interface { arg, err := msg.NumberArgAt(vm, locals, 0) if err != nil { @@ -482,6 +499,14 @@ func NumberSquared(vm *VM, target, locals Interface, msg *Message) Interface { return vm.NewNumber(x * x) } +func NumberSub(vm *VM, target, locals Interface, msg *Message) Interface { + arg, err := msg.NumberArgAt(vm, locals, 0) + if err != nil { + return vm.IoError(err) + } + return vm.NewNumber(target.(*Number).Value - arg.Value) +} + func NumberTan(vm *VM, target, locals Interface, msg *Message) Interface { return vm.NewNumber(math.Tan(target.(*Number).Value)) } diff --git a/object.go b/object.go index e405743..959baf4 100644 --- a/object.go +++ b/object.go @@ -95,7 +95,8 @@ func GetSlot(o Interface, slot string) (value, proto Interface) { return getSlotRecurse(o, slot, make(map[*Object]struct{}, len(o.SP().Protos)+1)) } -// var Debugvm *VM +var Debugvm *VM +var _ = Debugvm func getSlotRecurse(o Interface, slot string, checked map[*Object]struct{}) (Interface, Interface) { obj := o.SP() diff --git a/optable.go b/optable.go index 6ee80e7..b4c67c7 100644 --- a/optable.go +++ b/optable.go @@ -1,169 +1,525 @@ package iolang -// import "fmt" +import ( + "bytes" + "fmt" + "sort" + "text/tabwriter" +) +// OpTable is an Object which manages message-shuffling operators in Io. type OpTable struct { Object Operators map[string]Operator - Assigns map[string]Operator } +// Clone generates a shallow copy of the OpTable. +func (o *OpTable) Clone() Interface { + return &OpTable{ + Object: Object{Slots: Slots{}, Protos: []Interface{o}}, + Operators: o.Operators, + } +} + +// String generates a string representation of the operators in the table. +func (o *OpTable) String() string { + ops := o.Operators + s := make(opSorter, 0, len(ops)) + a := make(opSorter, 0) + for k, v := range ops { + if v.Calls == "" { + s = append(s, opToSort{k, v}) + } else { + a = append(a, opToSort{k, v}) + } + } + sort.Sort(s) + sort.Sort(a) + var b bytes.Buffer + b.WriteString("Operators\n") + w := tabwriter.NewWriter(&b, 4, 0, 1, ' ', 0) + if len(s) > 0 { + prev := s[0] + r := "left" + if prev.op.Right { + r = "right" + } + fmt.Fprintf(w, "\t%d/%s\t%s", prev.op.Prec, r, prev.name) + for _, v := range s[1:] { + if prev.op != v.op { + r = "left" + if v.op.Right { + r = "right" + } + fmt.Fprintf(w, "\n\t%d/%s", v.op.Prec, r) + } + fmt.Fprintf(w, "\t%s", v.name) + prev = v + } + } + w.Flush() + b.WriteString("\n\nAssign Operators\n") + w.Init(&b, 3, 0, 1, ' ', 0) + if len(a) > 0 { + for _, v := range a { + fmt.Fprintf(w, "\t%s\t%s\n", v.name, v.op.Calls) + } + } + w.Flush() + return b.String() +} + +// Operator defines an Io operator. type Operator struct { - // The slot the operator accesses; this is always the same as the name - // except for assign operators. + // For assign operators, the slot the operator calls. This must be the + // empty string for operators that are not assign operators. Calls string // Precedence. Lower is more binding. Prec int // Associativity. Right-associativity is more binding than // left-associativity for operators of equal precedence. + // This is an iolang extension. Right bool } +// leastBindingOp is the least binding operator, used internally to manage the +// operator shuffling stack. +var leastBindingOp = Operator{Prec: int((^uint(0)) >> 1)} + +// assignPrecedence is the precedence of assignment operators. +// In the original, assignment operators are handled separately from others and +// live in a different place in the OperatorTable. The behavior here won't be +// identical, especially with regard to return, which has lower precedence than +// assignment operators. +const assignPrecedence = 13 + +// MoreBinding determines whether this Operator is at least as binding as +// another. func (op Operator) MoreBinding(than Operator) bool { - return op.Prec < than.Prec || op.Prec == than.Prec && than.Right + return op.Prec < than.Prec || op.Prec == than.Prec && op.Right } func (vm *VM) initOpTable() { + slots := Slots{ + "addAssignOperator": vm.NewCFunction(OperatorTableAddAssignOperator, "OperatorTableAddAssignOperator(op, calls)"), + "addOperator": vm.NewCFunction(OperatorTableAddOperator, "OperatorTableAddOperator(op, precedence, [associativity])"), + "asString": vm.NewCFunction(OperatorTableAsString, "OperatorTableAsString()"), + "precedenceLevelCount": vm.NewNumber(32), // not really + "type": vm.NewString("OperatorTable"), + } + vm.Operators.Object = Object{Slots: slots, Protos: []Interface{vm.BaseObject}} vm.Operators.Operators = map[string]Operator{ - "?": Operator{"?", 0, false}, - "@": Operator{"@", 0, false}, - "@@": Operator{"@@", 0, false}, - "**": Operator{"**", 1, true}, - "%": Operator{"%", 2, false}, - "*": Operator{"*", 2, false}, - "/": Operator{"/", 2, false}, - "+": Operator{"+", 3, false}, - "-": Operator{"-", 3, false}, - "<<": Operator{"<<", 4, false}, - ">>": Operator{">>", 4, false}, - "<": Operator{"<", 5, false}, - "<=": Operator{"<=", 5, false}, - ">": Operator{">", 5, false}, - ">=": Operator{">=", 5, false}, - "!=": Operator{"!=", 6, false}, - "==": Operator{"==", 6, false}, - "&": Operator{"&", 7, false}, - "^": Operator{"^", 8, false}, - "|": Operator{"|", 9, false}, - "&&": Operator{"&&", 10, false}, - "and": Operator{"and", 10, false}, - "or": Operator{"or", 11, false}, - "||": Operator{"||", 11, false}, - "..": Operator{"..", 12, false}, - "%=": Operator{"%=", 13, true}, - "&=": Operator{"&=", 13, true}, - "*=": Operator{"*=", 13, true}, - "+=": Operator{"+=", 13, true}, - "-=": Operator{"-=", 13, true}, - "/=": Operator{"/=", 13, true}, - "<<=": Operator{"<<=", 13, true}, - ">>=": Operator{">>=", 13, true}, - "^=": Operator{"^=", 13, true}, - "|=": Operator{"|=", 13, true}, - "return": Operator{"return", 14, false}, - } - vm.Operators.Assigns = map[string]Operator{ - "::=": Operator{"newSlot", 13, true}, - ":=": Operator{"setSlot", 13, true}, - "=": Operator{"updateSlot", 13, true}, - } -} - -// Perform operator-precedence reordering of a message according to the -// OpTable in the VM. -func (vm *VM) OpShuffle(m *Message) { - vm.shuffleRecurse(m, Operator{Prec: int(^uint(0) >> 1)}) -} - -/* -1. if the current token is not an operator then it's unaffected -2. if its precedence is lower than the last operator then it is more binding - and the argument is part of the current message -3. if its precedence is the same then it falls to associativity; left is higher -4. if it's higher then it is less binding and the argument is part of the last - operator with precedence < current or == current and right-assoc -5. 1 + (1+x) * 2 = 1 +((1 +(x)) *(2)) --> - whenever an operator has arguments and the next operator is more binding, - add it to the end of the argument -recursively add the following messages until an operator that is less binding - than the current is reached to the argument of the current operator -*/ -func (vm *VM) shuffleRecurse(m *Message, current Operator) (last *Message) { - if m == nil { - return nil + "?": Operator{"", 0, false}, + "@": Operator{"", 0, false}, + "@@": Operator{"", 0, false}, + "**": Operator{"", 1, true}, + "%": Operator{"", 2, false}, + "*": Operator{"", 2, false}, + "/": Operator{"", 2, false}, + "+": Operator{"", 3, false}, + "-": Operator{"", 3, false}, + "<<": Operator{"", 4, false}, + ">>": Operator{"", 4, false}, + "<": Operator{"", 5, false}, + "<=": Operator{"", 5, false}, + ">": Operator{"", 5, false}, + ">=": Operator{"", 5, false}, + "!=": Operator{"", 6, false}, + "==": Operator{"", 6, false}, + "&": Operator{"", 7, false}, + "^": Operator{"", 8, false}, + "|": Operator{"", 9, false}, + "&&": Operator{"", 10, false}, + "and": Operator{"", 10, false}, + "or": Operator{"", 11, false}, + "||": Operator{"", 11, false}, + "..": Operator{"", 12, false}, + "%=": Operator{"", 13, true}, + "&=": Operator{"", 13, true}, + "*=": Operator{"", 13, true}, + "+=": Operator{"", 13, true}, + "-=": Operator{"", 13, true}, + "/=": Operator{"", 13, true}, + "<<=": Operator{"", 13, true}, + ">>=": Operator{"", 13, true}, + "^=": Operator{"", 13, true}, + "|=": Operator{"", 13, true}, + "return": Operator{"", 14, false}, + + // Assign operators. + "::=": Operator{"newSlot", assignPrecedence, true}, + ":=": Operator{"setSlot", assignPrecedence, true}, + "=": Operator{"updateSlot", assignPrecedence, true}, + } +} + +// shufLevel is a linked stack item to manage the messages to which to attach. +type shufLevel struct { + op Operator + m *Message + up *shufLevel + typ int +} + +func (ll *shufLevel) String() string { + k := 0 + for nl := ll; nl != nil; nl = nl.up { + k++ + } + var typ string + switch ll.typ { + case levArg: + typ = "levArg" + case levAttach: + typ = "levAttach" + case levNew: + typ = "levNew" + } + if ll.op == leastBindingOp { + return fmt.Sprintf("shufLevel{leastBindingOp m=%s depth=%d typ=%s}", ll.m.Name(), k, typ) + } + if ll.op.Calls == "" { + return fmt.Sprintf("shufLevel{asgn=%s m=%s depth=%d typ=%s}", ll.op.Calls, ll.m.Name(), k, typ) + } + return fmt.Sprintf("shufLevel{prec=%d m=%s depth=%d typ=%s}", ll.op.Prec, ll.m.Name(), k, typ) +} + +// Level types, indicating the meaning of attaching a message to this level. +const ( + levAttach = iota // Attach to this message. + levArg // Add an argument. + levNew // New level without a message. +) + +// pop unlinks the stack until a level at least as binding as op is found, +// returning the new top of the stack. +func (ll *shufLevel) pop(op Operator) *shufLevel { + for ll != nil && ll.op.MoreBinding(op) && ll.typ != levArg { + ll.finish() + ll, ll.up, ll.m = ll.up, nil, nil + } + return ll +} + +// clear unlinks the stack down to the bottom level, as if calling +// ll.pop(opOnlyMoreBindingThanLeastBindingOp). +func (ll *shufLevel) clear() *shufLevel { + for ll != nil && ll.up != nil && ll.typ != levArg { + ll.finish() + ll, ll.up, ll.m = ll.up, nil, nil + } + return ll +} + +// fullClear fully clears the stack to prepare for the next top-level message. +func (ll *shufLevel) fullClear() *shufLevel { + for ll != nil && ll.up != nil { + ll.finish() + ll, ll.up, ll.m = ll.up, nil, nil + } + // ll is now the top of the stack, which we need to reset. + ll.m = nil + ll.typ = levNew + return ll +} + +// attach attaches a message to this level in the correct way for its type. +func (ll *shufLevel) attach(m *Message) { + switch ll.typ { + case levAttach: + ll.m.Next = m + case levArg: + ll.m.Args = append(ll.m.Args, m) + case levNew: + ll.m = m + } +} + +// attachReplace attaches a message to this level, then makes the message the +// new level target. +func (ll *shufLevel) attachReplace(m *Message) { + ll.attach(m) + ll.m, ll.typ = m, levAttach +} + +// push attaches a new level to the top of the stack, returning the new top. +func (ll *shufLevel) push(m *Message, op Operator) *shufLevel { + ll.attachReplace(m) + return &shufLevel{ + op: op, + m: m, + up: ll, + typ: levArg, + } +} + +// finish declares a stack level to be done processing. +func (ll *shufLevel) finish() { + if m := ll.m; m != nil { + m.InsertAfter(nil) + if len(m.Args) == 1 { + a := m.Args[0] + if a.Symbol.Kind == IdentSym && a.Symbol.Text == "" && len(a.Args) == 1 && a.Next == nil { + // We added a () for grouping, but we don't need it anymore. + m.Args[0] = a.Args[0] + a.Args = nil + } + } } - for x, op := vm.nextOp(m); x != nil; x, op = vm.nextOp(m) { - if len(x.Args) > 0 { - // If the current operator has an argument already and the next - // operator is more binding, then the next is part of the argument - // of the current. - for next, op2 := vm.nextOp(x); next != nil && op2.MoreBinding(op); next, op2 = vm.nextOp(x) { - last = vm.shuffleRecurse(next, op2) - x.Args[0] = &Message{ - Object: Object{Slots: vm.DefaultSlots["Message"], Protos: []Interface{vm.BaseObject}}, - Symbol: Symbol{Kind: IdentSym, Text: ""}, - Args: []*Message{x.Args[0]}, - Next: next, +} + +// doLevel shuffles one level. The new stack top, extra messages to be +// shuffled, and any syntax error are returned. +func (ll *shufLevel) doLevel(vm *VM, ops *OpTable, m *Message) (nl *shufLevel, next []*Message, err *Exception) { + fmt.Println("doLevel on", vm.AsString(m), "shufLevel is", ll, "ll.m", MessageAsString(vm, ll.m, nil, nil)) + switch m.Symbol.Kind { + case IdentSym: + if op, ok := ops.Operators[m.Symbol.Text]; ok { + if op.Calls != "" { + // Assignment operator. + lhs := ll.m + fmt.Println("assign lhs:", lhs.Name(), " next:", lhs.Next.Name(), "==m", lhs.Next == m) + if lhs == nil { + // Assigning to nothing is illegal. + err = vm.NewExceptionf("%s assigning to nothing", m.Symbol.Text) + return ll, nil, err } - next.Prev = x.Args[0] - x.Next = last - if last != nil && last.Prev != nil { - last.Prev.Next = nil + if len(m.Args) > 1 { + // Assignment operators are allowed to have only zero or + // one argument. + err = vm.NewExceptionf("too many arguments to %s", m.Symbol.Text) + return ll, nil, err } - } - } else { - // Recursively add to the argument of the current operator the - // messages between the current and next operators until an - // operator that is less binding than the current is reached. - if op.MoreBinding(current) { - last = vm.shuffleRecurse(x, op) - m.Args = []*Message{m.Next} - m.Next.Prev = nil - m.Next = last - if last != nil && last.Prev != nil { - last.Prev.Next = nil + if m.Next.IsTerminator() && len(m.Args) == 0 { + // Assigning nothing to something is illegal. + err = vm.NewExceptionf("%s requires a value to assign", m.Symbol.Text) + return ll, nil, err + } + if len(lhs.Args) > 0 { + // Assigning to a call used to be illegal, but a recent + // change allows expressions like `a(b, c) := d`, + // transforming into `setSlot(a(b, c), d)`. This was to + // enable a Python-style multiple assignment syntax like + // `target [a, b, c] <- list(x, y, z)` to accomplish + // `target do(a = x; b = y; c = z)`. + // + // I'm not implementing this for a few reasons. First, the + // meaning of lhs in this form is different in a + // non-obvious way, as it is normally converted by name to + // a string; using an existing assignment operator and + // accidentally or unknowingly triggering this syntax will + // produce unexpected results. Second, the implmentation of + // this technique involves creating a deep copy of the + // entire message chain forward, meaning if a file begins + // with this type of assignment, the runtime will allocate + // (actually three) copies of *every message in the file*, + // recursively, causing essentially unbounded memory and + // stack usage. Third, the current syntax assumes only a + // single message follows the assignment operator, so + // `data(i,j) = Number constants pi` will transform to + // `assignOp(data(i,j), Number) constants pi`. + err = vm.NewExceptionf("message preceding %s must have no args", m.Symbol.Text) + return ll, nil, err + } + + // Handle `a := (b c) d ; e` as follows: + // 1. Move op arg to a separate message: a :=() (b c) d ; e + // 2. Give lhs arguments: a("a", (...)) :=() (b c) d ; e + // 3. Change lhs name: setSlot("a", (...)) :=() (b c) d ; e + // 4. Move msgs up to terminator: setSlot("a", (b c) d) := ; e + // 5. Remove operator message: setSlot("a", (b c) d) ; e + + // fmt.Println(vm.AsString(lhs)) + // 1. Move the operator argument, if it exists, to a separate + // message. + if len(m.Args) > 0 { + // fmt.Println("move", m.Name(), "arg") + m.InsertAfter(vm.IdentMessage("", m.Args...)) + m.Args = nil + } + // fmt.Println(vm.AsString(lhs)) + // 2. Give lhs its arguments. The first is the name of the + // slot to which we're assigning (assuming a built-in + // assignment operator), and the second is the value to give + // it. We'll also need to shuffle that value later. + // fmt.Println("lhs args before:", lhs.Args) + lhs.Args = []*Message{vm.StringMessage(lhs.Name()), m.Next} + next = append(next, m.Next) + // fmt.Println("lhs args after:", lhs.Args) + // fmt.Println(vm.AsString(lhs)) + // 3. Change lhs's name to the assign operator's call. + lhs.Symbol = Symbol{Kind: IdentSym, Text: op.Calls} + // fmt.Println("new lhs:", lhs.Name()) + // fmt.Println(vm.AsString(lhs)) + // 4. Move messages up to but not including the next terminator + // into the assignment's second argument. Really, we already + // moved it there; we're finding the message to be the next + // after lhs. + last := m.Next + for !last.Next.IsTerminator() { + last = last.Next + } + // fmt.Println("last:", last.Name()) + if last.Next != nil { + last.Next.Prev = lhs } - // Some extra work is done here because we already have the - // next operator but discard it and find it again in the - // post-loop, but I want to get this working before I care. - // Besides, the next operator will always be the next message - // because we just linked it thus. + lhs.Next = last.Next + last.Next = nil + // fmt.Println("lhs.Next:", lhs.Next) + fmt.Println(vm.AsString(lhs)) + + // 5. Remove the operator message. + m.Next = lhs.Next + + // It's legal to do something like `1 := x`, so we need to make + // sure that x will be evaluated when that happens. + lhs.Memo = nil } else { - return x + // Non-assignment operator. + if len(m.Args) > 0 { + // `a + (b - c) * d` is initially parsed as `b - c` being + // the argument to +. In order to have order of operations + // make sense, we need to move that argument to a separate + // message, so we have `a +() (b - c) * d`, which we can + // then shuffle into `a +((b - c) *(d))`. + m.InsertAfter(vm.IdentMessage("", m.Args...)) + m.Args = nil + } + ll = ll.pop(op).push(m, op) } + } else { + // Non-operator identifier. + ll.attachReplace(m) } + case SemiSym: + // Terminator. + ll = ll.clear() + ll.attachReplace(m) + case NumSym, StringSym: + // Literal. The handling is the same as for a non-operator identifier. + ll.attachReplace(m) } - if m.Next != nil { - m.Args = []*Message{m.Next} - m.Next.Prev = nil - m.Next = nil - } - return nil + return ll, next, nil } -func (vm *VM) nextOp(m *Message) (*Message, Operator) { +// OpShuffle performs operator-precedence reordering of a message. If the +// message (or one of its protos) has an OperatorTable slot that contains an +// *OpTable, it is used for operators; otherwise, the VM's default OpTable is +// used. +func (vm *VM) OpShuffle(m *Message) (err *Exception) { if m == nil { - return nil, Operator{} + return nil } - for _, arg := range m.Args { - vm.OpShuffle(arg) + if m.Symbol.Kind == IdentSym && m.Symbol.Text == "__noShuffling__" { + // We could make __noShuffling__ just an Object with an OperatorTable + // that is empty, but doing it this way allows us to skip shuffling + // entirely, speeding up parsing. Also, Io's treatment of + // __noShuffling__ is interesting: a message named __noShuffling__ + // prevents operator shuffling as expected, but there is no object so + // named, so you have to create it yourself using setSlot, because you + // can't assign to __noShuffling__ using an operator, because assign + // operator transformation is handled during operator shuffling, which + // doesn't happen because the message begins with __noShuffling__. :) + return nil } - m = m.Next - for m != nil { - for _, arg := range m.Args { - vm.OpShuffle(arg) - } - switch m.Symbol.Kind { - case SemiSym: - return m, Operator{} - case IdentSym: - if op, ok := vm.Operators.Operators[m.Symbol.Text]; ok { - return m, op - } else if op, ok = vm.Operators.Assigns[m.Symbol.Text]; ok { - return m, op + opsx, _ := GetSlot(m, "OperatorTable") + var ops *OpTable + if ops, _ = opsx.(*OpTable); ops == nil { + ops = vm.Operators + } + ll := &shufLevel{ + op: leastBindingOp, + typ: levNew, + } + exprs := []*Message{m} + var next []*Message + for len(exprs) > 0 { + expr := exprs[len(exprs)-1] + exprs = exprs[:len(exprs)-1] + for { + ll, next, err = ll.doLevel(vm, ops, expr) + if err != nil { + return err + } + exprs = append(exprs, next...) + exprs = append(exprs, expr.Args...) + if expr.Next == nil { + break } + expr = expr.Next } - m = m.Next + ll.fullClear() } - return nil, Operator{} + return nil +} + +func OperatorTableAddAssignOperator(vm *VM, target, locals Interface, msg *Message) Interface { + name, err := msg.StringArgAt(vm, locals, 0) + if err != nil { + return vm.IoError(err) + } + calls, err := msg.StringArgAt(vm, locals, 1) + if err != nil { + return vm.IoError(err) + } + op := Operator{ + Calls: calls.Value, + Prec: assignPrecedence, + Right: true, + } + target.(*OpTable).Operators[name.Value] = op + return target +} + +func OperatorTableAddOperator(vm *VM, target, locals Interface, msg *Message) Interface { + name, err := msg.StringArgAt(vm, locals, 0) + if err != nil { + return vm.IoError(err) + } + prec, err := msg.NumberArgAt(vm, locals, 1) + if err != nil { + return vm.IoError(err) + } + right := vm.AsBool(msg.EvalArgAt(vm, locals, 2)) + op := Operator{ + Calls: "", + Prec: int(prec.Value), + Right: right, + } + target.(*OpTable).Operators[name.Value] = op + return target +} + +func OperatorTableAsString(vm *VM, target, locals Interface, msg *Message) Interface { + return vm.NewString(target.(*OpTable).String()) +} + +// opToSort is a type for sorting operators by precedence. +type opToSort struct { + name string + op Operator +} + +// opSorter is a type for sorting operators by precedence. +type opSorter []opToSort + +func (o opSorter) Len() int { + return len(o) +} + +func (o opSorter) Less(i, j int) bool { + if o[i].op.MoreBinding(o[j].op) { + // Strictly less. + return true + } + if o[j].op.MoreBinding(o[i].op) { + // Strictly greater. + return false + } + // Equal precedence, so sort them by name. + return o[i].name < o[j].name +} + +func (o opSorter) Swap(i, j int) { + o[i], o[j] = o[j], o[i] } diff --git a/parse.go b/parse.go index 3b62ae5..1078b5e 100644 --- a/parse.go +++ b/parse.go @@ -25,7 +25,13 @@ func (vm *VM) parseRecurse(open rune, src *bufio.Reader, tokens chan token) (tok // We didn't parse any messages. msg = nil } else { - m.Prev.Next = nil + // If the final message wasn't already one, add a SemiSym to + // conclude the expression. + if open == -1 && m.Prev.Symbol.Kind != SemiSym { + m.Symbol.Kind = SemiSym + } else { + m.Prev.Next = nil + } } }() for tok = range tokens { @@ -172,7 +178,9 @@ func (vm *VM) DoReader(src io.Reader) Interface { if err != nil { return vm.IoError(err) } - vm.OpShuffle(msg) + if err := vm.OpShuffle(msg); err != nil { + return err + } return vm.DoMessage(msg, vm.BaseObject) } @@ -187,5 +195,11 @@ func (vm *VM) DoMessage(msg *Message, locals Interface) Interface { // Determine whether this message is the start of a "statement." This is true // if it has no previous link or if the previous link is a SemiSym. func (m *Message) IsStart() bool { - return m.Prev == nil || m.Prev.Symbol.Kind == SemiSym + return m.Prev.IsTerminator() +} + +// IsTerminator determines whether this message is the end of an expression. +// This is true if it is nil or its symbol is a SemiSym. +func (m *Message) IsTerminator() bool { + return m == nil || m.Symbol.Kind == SemiSym } diff --git a/vm.go b/vm.go index 3f8c2ac..d5caeb8 100644 --- a/vm.go +++ b/vm.go @@ -126,7 +126,7 @@ func (vm *VM) AsString(obj Interface) string { if s, ok := obj.(fmt.Stringer); ok { return s.String() } - return fmt.Sprintf("%T_%p", obj) + return fmt.Sprintf("%T_%p", obj, obj) } // Define extras in Io once the VM is capable of executing code.