Skip to content

Commit

Permalink
Replace panic("TODO: ...") with NotImplemented("...")
Browse files Browse the repository at this point in the history
This produces easier to read stack traces and nicer/more-thought-out
error messages.  In general, it makes the output of the program seem
more professional because it doesn't appear to crash "randomly".
  • Loading branch information
Kestred committed Jul 8, 2016
1 parent 6d8d87a commit 03f16a6
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 37 deletions.
10 changes: 10 additions & 0 deletions code/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ var (
BuiltinUint32 = BaseTyp("u32")
BuiltinUint64 = BaseTyp("u64")

// Undefined opererator
UndefinedOperator = Operator("<undefined>", "<undefined>", "_undefined_", Nullary, NonAssociative, InvalidPrec)

// Logical operators
BuiltinLogicalOr = Operator("Logical Or", "or", "_or_", BinaryInfix, LeftAssociative, LogicalOrPrec)
BuiltinLogicalAnd = Operator("Logical And", "and", "_and_", BinaryInfix, LeftAssociative, LogicalAndPrec)
Expand Down Expand Up @@ -188,6 +191,7 @@ func (l *TextLiteral) GetValue() Value { return l.Value }
type Type interface {
Node
ImplementsType()
Print() string
}

func (t *ArrayType) ImplementsType() {}
Expand All @@ -196,6 +200,12 @@ func (t *NamedType) ImplementsType() {}
func (t *PointerType) ImplementsType() {}
func (t *BaseType) ImplementsType() {}

func (t *ArrayType) Print() string { return "[]" + t.Element.Print() }
func (t *ProcedureType) Print() string { return "()" /* TODO */ }
func (t *NamedType) Print() string { return t.Name.Literal }
func (t *PointerType) Print() string { return "^" + t.PointerTo.Print() }
func (t *BaseType) Print() string { return t.Name }

type EnumItem interface {
Node
ImplementsEnumItem()
Expand Down
1 change: 1 addition & 0 deletions code/ast/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func (asc OpAssociation) String() string {
}

const (
InvalidPrec OpPrecedence = -1
AssignmentPrec OpPrecedence = 0
LogicalOrPrec OpPrecedence = 10
LogicalAndPrec OpPrecedence = 15
Expand Down
1 change: 1 addition & 0 deletions code/ast/section.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func flattenTree(node Node, parent Node) []Node {
break // nothing to add

default:
utils.Errorf("Unhandled node type '%s' during AST flattening", utils.Typeof(n))
utils.InvalidCodePath()
}

Expand Down
21 changes: 14 additions & 7 deletions code/bytecode/bytecode.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ func (p *Procedure) Extend(node ast.Node) {
p.Registers[n] = p.PrevResult
} else {
p.Registers[n] = Rg(p.AssignLocation(), typeFromAst(n.Expr.GetType()))
utils.NotImplemented("Bytecode generation for declaration without initialization")
utils.NotImplemented("bytecode generation for declaration without initialization")
}

case *ast.EvalStmt:
Expand Down Expand Up @@ -373,7 +373,7 @@ func (p *Procedure) Extend(node ast.Node) {
p.insertCast(rhs, rightType, expr.Type)
p.Instructions = append(p.Instructions, Inst(COPY, Unary(p.PrevResult, lhs)))
} else {
panic("TODO: Handle non-identifier expressions as the assignee in assignment")
utils.NotImplemented("bytecode generation for assignment to a non-identifier expression")
}

return
Expand All @@ -399,7 +399,7 @@ func (p *Procedure) Extend(node ast.Node) {
p.insertCast(tmps[i], n.Right[i].GetType(), e.Type)
p.Instructions = append(p.Instructions, Inst(COPY, Unary(p.PrevResult, lhs)))
} else {
panic("TODO: Handle non-identifier expressions as the assignee in assignment")
utils.NotImplemented("bytecode generation for assignment to a non-identifier expression")
}
}

Expand Down Expand Up @@ -470,7 +470,11 @@ func (p *Procedure) Extend(node ast.Node) {
op = DIVIDE
}
default:
panic("TODO: Unhandle expression type in bytecode generator")
utils.NotImplemented(
fmt.Sprintf(`Bytecode generation for infix "%s %s %s"`,
n.Left.GetType().Print(),
n.Operator.Literal,
n.Right.GetType().Print()))
}

out := Rg(p.AssignLocation(), typeFromAst(n.Type))
Expand Down Expand Up @@ -523,7 +527,8 @@ func (p *Procedure) Extend(node ast.Node) {
endRegister = out

default:
panic("TODO: Unhandled node type in bytecode.Generate")
utils.Errorf("Unhandled node type '%s' in bytecode generation", utils.Typeof(n))
utils.InvalidCodePath()
}

// set the result of this expression
Expand All @@ -548,7 +553,7 @@ func typeFromAst(t ast.Type) Type {
case ast.BuiltinText:
return Pointer // TODO: eventually text should be a pointer/length struct
default:
utils.NotImplemented("bytecode generation for for non-numeric/non-builtin types")
utils.NotImplemented("Bytecode generation for for non-numeric/non-builtin types")
return None
}
}
Expand Down Expand Up @@ -580,6 +585,8 @@ func (p *Procedure) insertCast(in Register, from ast.Type, to ast.Type) {
p.Instructions = append(p.Instructions, cast)
p.PrevResult = out
default:
panic("TODO: unhandled type in insertCast")
utils.NotImplemented(
fmt.Sprintf(`Inserting implicit cast of %s to %s during bytecode generation`,
from.Print(), to.Print()))
}
}
5 changes: 3 additions & 2 deletions code/interpreter/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ InstructionLoop:
registers[args.Out.Loc] = proc.Program.Data[args.Name]
}
case bc.UnaryArgs:
panic("TODO: Load data from pointer")
utils.NotImplemented("Loading data from a non-constant pointer during interpretation")
// registers[args.Out] = proc.Program.Constants[args.Left]
}
case bc.CALL:
Expand Down Expand Up @@ -196,7 +196,8 @@ InstructionLoop:
}

default:
panic("TODO: Unhandled opcode")
utils.Errorf("Unhandled opcode '%s' in interpreter", inst.Op)
utils.InvalidCodePath()
}
}

Expand Down
39 changes: 26 additions & 13 deletions code/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/kestred/philomath/code/ast"
"github.com/kestred/philomath/code/scanner"
"github.com/kestred/philomath/code/token"
"github.com/kestred/philomath/code/utils"
)

// FIXME: The grammar feels like a hack because of the way functions are parsed
Expand Down Expand Up @@ -295,7 +296,8 @@ func (p *Parser) parseDeclaration() ast.Decl {
// parse mutable decl
p.next() // eat ":"
if p.tok != token.EQUALS {
panic("TODO: Handle typed declarations")
utils.NotImplemented("Parsing mutable declarations with explicit types")
return nil
}
p.expect(token.EQUALS)
expr := p.parseExpression()
Expand All @@ -307,9 +309,11 @@ func (p *Parser) parseDeclaration() ast.Decl {
p.expect(token.CONS)
switch p.tok {
case token.STRUCT:
panic("TODO: Handle structs")
utils.NotImplemented("Parsing struct declarations")
return nil
case token.MODULE:
panic("TODO: Handle modules")
utils.NotImplemented("Parsing module declarations")
return nil
default:
expr := p.parseExpression()
if _, isFunc := expr.(*ast.ProcedureExpr); !isFunc {
Expand All @@ -323,9 +327,9 @@ func (p *Parser) parseStatement() ast.Stmt {
if p.tok.IsKeyword() {
switch p.tok {
case token.FOR:
panic("TODO: Handle if conditions (in parser)")
utils.NotImplemented("Parsing for loops")
case token.IF:
panic("TODO: Handle for loops (in parser)")
utils.NotImplemented("Parsing if statements")
case token.RETURN:
p.next() // eat 'return'
var expr ast.Expr
Expand All @@ -335,7 +339,8 @@ func (p *Parser) parseStatement() ast.Stmt {
p.expect(token.SEMICOLON)
return ast.Return(expr)
default:
panic("TODO: Unhandled keyword: " + p.lit)
utils.Errorf("Unhandled keyword '%s' in parse statement", p.lit)
utils.InvalidCodePath()
}
}

Expand All @@ -351,7 +356,9 @@ func (p *Parser) parseStatement() ast.Stmt {
p.expect(token.SEMICOLON)
return ast.Eval(exprs[0])
} else {
panic("TODO: Produce error for expression list w/o assignment, then recover")
p.error(p.scanner.Pos(), `An expression list (eg. "a, b, c") is only valid as part of an assignment`)
p.expect(token.SEMICOLON)
return ast.Eval(exprs[0]) // TODO: return some sort of ast.NoOp
}
}

Expand All @@ -371,7 +378,8 @@ func (p *Parser) parseExpression() ast.Expr {
func (p *Parser) parseOperators(precedence ast.OpPrecedence) ast.Expr {
lhs := p.parseBaseExpression()
if p.tok == token.LEFT_BRACKET {
panic("TODO: Hande array subscript")
utils.NotImplemented("Parsing array subscripts")
return nil
} else if p.tok == token.LEFT_PAREN {
p.next() // eat '('
var args []ast.Expr
Expand Down Expand Up @@ -411,7 +419,8 @@ func (p *Parser) parseOperators(precedence ast.OpPrecedence) ast.Expr {
func (p *Parser) parseBinaryOperator() *ast.OperatorDefn {
options, defined := p.operators.Lookup(p.lit)
if !defined {
panic("TODO: Handle undefined operators")
p.error(p.scanner.Pos(), "The operator '"+p.lit+"' has not been defined")
return ast.UndefinedOperator
}

var op *ast.OperatorDefn
Expand All @@ -423,7 +432,8 @@ func (p *Parser) parseBinaryOperator() *ast.OperatorDefn {
}

if op == nil {
panic("TODO: Handle operator is not an infix/postfix operator")
p.error(p.scanner.Pos(), "The operator '"+p.lit+"' cannot be used as an infix operator")
return ast.UndefinedOperator
}

return op
Expand All @@ -450,7 +460,8 @@ func (p *Parser) parseBaseExpression() ast.Expr {
if p.tok.IsOperator() {
options, defined := p.operators.Lookup(p.lit)
if !defined {
panic("TODO: Handle undefined operators")
p.error(p.scanner.Pos(), "The operator '"+p.lit+"' has not been defined")
// it is ok to fallthrough here, we'll just loop over an empty list
}

var op *ast.OperatorDefn
Expand All @@ -462,7 +473,8 @@ func (p *Parser) parseBaseExpression() ast.Expr {
}

if op == nil {
panic("TODO: Handle operator is not a prefix operator")
p.error(p.scanner.Pos(), "The operator '"+p.lit+"' cannot be used as an prefix operator")
op = ast.UndefinedOperator
}

p.next() // eat operator
Expand Down Expand Up @@ -545,7 +557,8 @@ func (p *Parser) parseType() ast.Type {
return ast.ArrTyp(typ, len)

case token.LEFT_PAREN:
panic("TODO: parse function types")
utils.NotImplemented("Parsing procedure types")
return nil

case token.IDENT:
var typ ast.Type
Expand Down
30 changes: 21 additions & 9 deletions code/semantics/inference.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ func inferTypesRecursive(node ast.Node) ast.Type {
inferTypesRecursive(n.Expr)
case *ast.AssignStmt:
if len(n.Left) != len(n.Right) {
panic("TODO: Handle unbalanced assignment")
utils.Errorf("Found unbalanced assignment during type inference")
utils.NotImplemented(`Printing "file:line: message"-style error messages after parsing`)
}
for i := range n.Left {
inferTypesRecursive(n.Left[i])
Expand Down Expand Up @@ -129,10 +130,13 @@ func inferPrefixType(op *ast.OperatorDefn, typ ast.Type) ast.Type {
case ast.BuiltinUint64:
return ast.BuiltinInt64
default:
panic("TODO: Implement operator overload resolution for prefix -/+")
utils.NotImplemented("Operator overloading for prefix +/- (unary negation)")
return nil
}
default:
panic("TODO: Unhandled prefix operator in type inference")
utils.Errorf("Unhandled prefix operator '%s' in type inference", op.Literal)
utils.InvalidCodePath()
return nil
}
}

Expand All @@ -143,7 +147,9 @@ func inferPostfixType(op *ast.OperatorDefn, typ ast.Type) ast.Type {

switch op {
default:
panic("TODO: Unhandled postfix operator in type inference")
utils.Errorf("Unhandled postfix operator '%s' in type inference", op.Literal)
utils.InvalidCodePath()
return nil
}
}

Expand All @@ -159,7 +165,9 @@ func inferInfixType(op *ast.OperatorDefn, left ast.Type, right ast.Type) ast.Typ
// TODO: Implement operator overload resolution
return castNumbers(left, right)
default:
panic("TODO: Unhandled infix operator in type inference")
utils.Errorf("Unhandled infix operator '%s' in type inference", op.Literal)
utils.InvalidCodePath()
return nil
}
}

Expand All @@ -182,28 +190,32 @@ func parseNumber(num string) (ast.Type, interface{}) {
if len(num) > 2 && num[0:2] == "0x" {
val, err := strconv.ParseUint(num, 16, 0)
if err == strconv.ErrRange {
panic("TODO: Handle hexadecimal literal can't be represented by uint64")
utils.Errorf("Hexadecimal literal can't be represented by a uint64")
utils.NotImplemented(`Printing "file:line: message"-style error messages after parsing`)
}
utils.AssertNil(err, "Failed parsing hexadecimal literal")
return ast.InferredUnsigned, val
} else if strings.Contains(num, ".") || strings.Contains(num, "e") {
val, err := strconv.ParseFloat(num, 0)
if err == strconv.ErrRange {
panic("TODO: Handle floating point literal can't be represented by float64")
utils.Errorf("Floating point literal can't be represented by a float64")
utils.NotImplemented(`Printing "file:line: message"-style error messages after parsing`)
}
utils.AssertNil(err, "Failed parsing float literal")
return ast.InferredFloat, val
} else if num[0] == '0' {
val, err := strconv.ParseUint(num, 8, 0)
if err == strconv.ErrRange {
panic("TODO: Handle octal literal can't be represented by uint64")
utils.Errorf("Octal literal can't be represented by a uint64")
utils.NotImplemented(`Printing "file:line: message"-style error messages after parsing`)
}
utils.AssertNil(err, "Failed parsing octal literal")
return ast.InferredUnsigned, val
} else {
val, err := strconv.ParseUint(num, 10, 0)
if err == strconv.ErrRange {
panic("TODO: Handle decimal literal can't be represented by uint64")
utils.Errorf("Decimal literal can't be represented by a uint64")
utils.NotImplemented(`Printing "file:line: message"-style error messages after parsing`)
}
utils.AssertNil(err, "Failed parsing decimal literal")
return ast.InferredNumber, val
Expand Down
2 changes: 1 addition & 1 deletion code/semantics/resolution.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func ResolveNames(cs *ast.Section) {
n.Decl = decl
break
} else if search == nil {
utils.NotImplemented("out-of-order declaration lookup")
utils.NotImplemented("name resolution for out-of-order declarations")
} else {
search = FindParentScope(search)
}
Expand Down
18 changes: 13 additions & 5 deletions code/utils/assert.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ func Puts(value interface{}) {
fmt.Printf("%v:%v\n", reflect.ValueOf(value).Type(), value)
}

func Errorf(msg string, args ...interface{}) {
fmt.Fprintf(os.Stderr, "Error: "+msg+"\n", args...)
}

func Assert(truthy bool, msg string, args ...interface{}) {
if !truthy {
AssertionFailed(msg, args...)
Expand Down Expand Up @@ -42,11 +46,11 @@ func InvalidCodePath() {
}

func AssertionFailed(msg string, args ...interface{}) {
fmt.Printf("Assertion: "+msg+"\n", args...)
fmt.Fprintf(os.Stderr, "Assertion: "+msg+"\n", args...)

// print call stack
stackEnded := false
fmt.Println()
fmt.Fprintf(os.Stderr, "\n")
for i := 2; i < 30; i++ {
pc, file, line, ok := runtime.Caller(i)
if !ok {
Expand All @@ -62,12 +66,16 @@ func AssertionFailed(msg string, args ...interface{}) {

callerParts := strings.Split(runtime.FuncForPC(pc).Name(), "/")
caller := callerParts[len(callerParts)-1]
fmt.Printf(" %16s %s:%d\n", path, caller, line)
fmt.Fprintf(os.Stderr, " %16s %s:%d\n", path, caller, line)
}
fmt.Println()
fmt.Fprintf(os.Stderr, "\n")

if !stackEnded {
fmt.Println("\nWhy things fail: your stack is to deep for sanity...")
fmt.Fprintf(os.Stderr, "\nWhy things fail: your stack is to deep for sanity...\n")
}
os.Exit(1)
}

func Typeof(v interface{}) string {
return fmt.Sprintf("%T", v)
}

0 comments on commit 03f16a6

Please sign in to comment.