From ade6a169bee3397e786db5419a0bfbaa9a5522a8 Mon Sep 17 00:00:00 2001 From: Robert Krimen Date: Tue, 9 Oct 2012 17:19:29 -0700 Subject: [PATCH] Partially fix ReferenceError origin reporting --- DESIGN.markdown | 1 + Makefile | 6 +- README.md => README.markdown | 147 ++++++++++++++++++----------------- environment.go | 4 +- error.go | 18 ++++- evaluate_expression.go | 6 +- otto_error_test.go | 17 ++++ parser.go | 20 +++-- type_reference.go | 6 +- 9 files changed, 138 insertions(+), 87 deletions(-) create mode 100644 DESIGN.markdown rename README.md => README.markdown (77%) diff --git a/DESIGN.markdown b/DESIGN.markdown new file mode 100644 index 0000000..2887529 --- /dev/null +++ b/DESIGN.markdown @@ -0,0 +1 @@ +* Designate the filename of "anonymous" source code by the hash (md5/sha1, etc.) diff --git a/Makefile b/Makefile index 2971be6..8af4efa 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: test assets todo fixme otto run test-all README +.PHONY: test assets todo fixme otto run test-all release export TERST_BASE=$(PWD) @@ -42,5 +42,5 @@ run: test-all: go test . -README: - godocdown > README.md +release: + godocdown > README.markdown diff --git a/README.md b/README.markdown similarity index 77% rename from README.md rename to README.markdown index e7259a8..10254f7 100644 --- a/README.md +++ b/README.markdown @@ -83,13 +83,13 @@ func (self Object) Call(this Value, argumentList ...interface{}) (Value, error) Call the object as a function with the given this value and argument list and return the result of invocation. It is essentially equivalent to: - self.apply(thisValue, argumentList) + self.apply(thisValue, argumentList) An undefined value and an error will result if: - 1. There is an error during conversion of the argument list - 2. The object is not actually a function - 3. An (uncaught) exception is thrown + 1. There is an error during conversion of the argument list + 2. The object is not actually a function + 3. An (uncaught) exception is thrown #### func (Object) Class @@ -100,14 +100,14 @@ Class will return the class string of the object. The return value will (generally) be one of: - Object - Function - Array - String - Number - Boolean - Date - RegExp + Object + Function + Array + String + Number + Boolean + Date + RegExp #### func (Object) Get @@ -123,8 +123,8 @@ func (self Object) Set(name string, value interface{}) error ``` Set the property of the given name to the given value. -An error will result if the setting the property triggers an exception (i.e. read-only), -or there is an error during conversion of the given value. +An error will result if the setting the property triggers an exception (i.e. +read-only), or there is an error during conversion of the given value. #### type Otto @@ -134,7 +134,8 @@ type Otto struct { } ``` -Otto is the representation of the JavaScript runtime. Each instance of Otto has a self-contained namespace. +Otto is the representation of the JavaScript runtime. Each instance of Otto has +a self-contained namespace. #### func New @@ -148,9 +149,8 @@ New will allocate a new JavaScript runtime ```go func Run(source string) (*Otto, Value, error) ``` -Run will allocate a new JavaScript runtime, run the given source -on the allocated runtime, and return the runtime, resulting value, and -error (if any). +Run will allocate a new JavaScript runtime, run the given source on the +allocated runtime, and return the runtime, resulting value, and error (if any). #### func (Otto) Get @@ -159,8 +159,8 @@ func (self Otto) Get(name string) (Value, error) ``` Get the value of the top-level binding of the given name. -If there is an error (like the binding not existing), then the value -will be undefined. +If there is an error (like the binding not existing), then the value will be +undefined. #### func (Otto) Object @@ -171,29 +171,30 @@ Object will run the given source and return the result as an object. For example, accessing an existing object: - object, _ := Otto.Object(`Number`) + object, _ := Otto.Object(`Number`) Or, creating a new object: - object, _ := Otto.Object(`{ xyzzy: "Nothing happens." }`) + object, _ := Otto.Object(`{ xyzzy: "Nothing happens." }`) Or, creating and assigning an object: - object, _ := Otto.Object(`xyzzy = {}`) - object.Set("volume", 11) + object, _ := Otto.Object(`xyzzy = {}`) + object.Set("volume", 11) -If there is an error (like the source does not result in an object), then -nil and an error is returned. +If there is an error (like the source does not result in an object), then nil +and an error is returned. #### func (Otto) Run ```go func (self Otto) Run(source string) (Value, error) ``` -Run will run the given source (parsing it first), returning the resulting value and error (if any) +Run will run the given source (parsing it first), returning the resulting value +and error (if any) -If the runtime is unable to parse the source, then this function will return undefined and the parse error (nothing -will be evaluated in this case). +If the runtime is unable to parse the source, then this function will return +undefined and the parse error (nothing will be evaluated in this case). #### func (Otto) Set @@ -202,11 +203,11 @@ func (self Otto) Set(name string, value interface{}) error ``` Set the top-level binding of the given name to the given value. -Set will automatically apply ToValue to the given value in order -to convert it to a JavaScript value (type Value). +Set will automatically apply ToValue to the given value in order to convert it +to a JavaScript value (type Value). -If there is an error (like the binding being read-only, or the ToValue conversion -failing), then an error is returned. +If there is an error (like the binding being read-only, or the ToValue +conversion failing), then an error is returned. If the top-level binding does not exist, it will be created. @@ -229,7 +230,7 @@ FalseValue will return a value represting false. It is equivalent to: - ToValue(false) + ToValue(false) #### func NaNValue @@ -240,7 +241,7 @@ NaNValue will return a value representing NaN. It is equivalent to: - ToValue(math.NaN()) + ToValue(math.NaN()) #### func NullValue @@ -264,7 +265,7 @@ TrueValue will return a value represting true. It is equivalent to: - ToValue(true) + ToValue(true) #### func UndefinedValue @@ -281,31 +282,32 @@ func (value Value) Call(this Value, argumentList ...interface{}) (Value, error) Call the value as a function with the given this value and argument list and return the result of invocation. It is essentially equivalent to: - value.apply(thisValue, argumentList) + value.apply(thisValue, argumentList) An undefined value and an error will result if: - 1. There is an error during conversion of the argument list - 2. The value is not actually a function - 3. An (uncaught) exception is thrown + 1. There is an error during conversion of the argument list + 2. The value is not actually a function + 3. An (uncaught) exception is thrown #### func (Value) Class ```go func (value Value) Class() string ``` -Class will return the class string of the value or the empty string if value is not an object. +Class will return the class string of the value or the empty string if value is +not an object. The return value will (generally) be one of: - Object - Function - Array - String - Number - Boolean - Date - RegExp + Object + Function + Array + String + Number + Boolean + Date + RegExp #### func (Value) IsBoolean @@ -384,7 +386,8 @@ func (value Value) Object() *Object ``` Object will return the object of the value, or nil if value is not an object. -This method will not do any implicit conversion. For example, calling this method on a string primitive value will not return a String object. +This method will not do any implicit conversion. For example, calling this +method on a string primitive value will not return a String object. #### func (Value) String @@ -402,13 +405,14 @@ func (value Value) ToBoolean() (bool, error) ``` ToBoolean will convert the value to a boolean (bool). - ToValue(0).ToBoolean() => false - ToValue("").ToBoolean() => false - ToValue(true).ToBoolean() => true - ToValue(1).ToBoolean() => true - ToValue("Nothing happens").ToBoolean() => true + ToValue(0).ToBoolean() => false + ToValue("").ToBoolean() => false + ToValue(true).ToBoolean() => true + ToValue(1).ToBoolean() => true + ToValue("Nothing happens").ToBoolean() => true -If there is an error during the conversion process (like an uncaught exception), then the result will be false and an error. +If there is an error during the conversion process (like an uncaught exception), +then the result will be false and an error. #### func (Value) ToFloat @@ -417,11 +421,12 @@ func (value Value) ToFloat() (float64, error) ``` ToFloat will convert the value to a number (float64). - ToValue(0).ToFloat() => 0. - ToValue(1.1).ToFloat() => 1.1 - ToValue("11").ToFloat() => 11. + ToValue(0).ToFloat() => 0. + ToValue(1.1).ToFloat() => 1.1 + ToValue("11").ToFloat() => 11. -If there is an error during the conversion process (like an uncaught exception), then the result will be 0 and an error. +If there is an error during the conversion process (like an uncaught exception), +then the result will be 0 and an error. #### func (Value) ToInteger @@ -430,11 +435,12 @@ func (value Value) ToInteger() (int64, error) ``` ToInteger will convert the value to a number (int64). - ToValue(0).ToInteger() => 0 - ToValue(1.1).ToInteger() => 1 - ToValue("11").ToInteger() => 11 + ToValue(0).ToInteger() => 0 + ToValue(1.1).ToInteger() => 1 + ToValue("11").ToInteger() => 11 -If there is an error during the conversion process (like an uncaught exception), then the result will be 0 and an error. +If there is an error during the conversion process (like an uncaught exception), +then the result will be 0 and an error. #### func (Value) ToString @@ -443,12 +449,13 @@ func (value Value) ToString() (string, error) ``` ToString will convert the value to a string (string). - ToValue(0).ToString() => "0" - ToValue(false).ToString() => "false" - ToValue(1.1).ToString() => "1.1" - ToValue("11").ToString() => "11" - ToValue('Nothing happens.').ToString() => "Nothing happens." + ToValue(0).ToString() => "0" + ToValue(false).ToString() => "false" + ToValue(1.1).ToString() => "1.1" + ToValue("11").ToString() => "11" + ToValue('Nothing happens.').ToString() => "Nothing happens." -If there is an error during the conversion process (like an uncaught exception), then the result will be the empty string ("") and an error. +If there is an error during the conversion process (like an uncaught exception), +then the result will be the empty string ("") and an error. diff --git a/environment.go b/environment.go index 9f4fc31..3d5c1e9 100644 --- a/environment.go +++ b/environment.go @@ -114,7 +114,7 @@ func (self *_objectEnvironment) ImplicitThisValue() *_object { func getIdentifierReference(environment _environment, name string, strict bool) _reference { if environment == nil { - return newObjectReference(nil, name, strict) + return newObjectReference(nil, name, strict, nil) } if environment.HasBinding(name) { return environment.newReference(name, strict) @@ -140,7 +140,7 @@ func (self *_objectEnvironment) newDeclarativeEnvironment() _environment { } func (self *_objectEnvironment) newReference(name string, strict bool) _reference { - return newObjectReference(self.Object, name, strict) + return newObjectReference(self.Object, name, strict, nil) } func (self *_objectEnvironment) GetReference(name string) _reference { diff --git a/error.go b/error.go index 3e07aeb..c2f40a9 100644 --- a/error.go +++ b/error.go @@ -44,14 +44,26 @@ func (self _error) String() string { func newError(name string, argumentList... interface{}) _error { description := "" - if len(argumentList) > 0 { - description, argumentList = argumentList[0].(string), argumentList[1:] + var node _node = nil + length := len(argumentList) + if length > 0 { + if node, _ = argumentList[length-1].(_node); node != nil || argumentList[length-1] == nil { + argumentList = argumentList[0:length-1] + length -= 1 + } + if length > 0 { + description, argumentList = argumentList[0].(string), argumentList[1:] + } } - return _error{ + error := _error{ Name: name, Message: messageFromDescription(description, argumentList...), Line: -1, } + if node != nil { + error.Line = node.position() + } + return error } func newReferenceError(argumentList... interface{}) _error { diff --git a/evaluate_expression.go b/evaluate_expression.go index 3e17f1b..36b15fc 100644 --- a/evaluate_expression.go +++ b/evaluate_expression.go @@ -507,7 +507,7 @@ func (self *_runtime) evaluateFunction(node *_functionNode) Value { func (self *_runtime) evaluateDotMember(node *_dotMemberNode) Value { target := self.evaluate(node.Target) targetValue := self.GetValue(target) - return toValue(newObjectReference(self.toObject(targetValue), node.Member, false)) + return toValue(newObjectReference(self.toObject(targetValue), node.Member, false, node)) } func (self *_runtime) evaluateBracketMember(node *_bracketMemberNode) Value { @@ -516,12 +516,14 @@ func (self *_runtime) evaluateBracketMember(node *_bracketMemberNode) Value { member := self.evaluate(node.Member) memberValue := self.GetValue(member) - return toValue(newObjectReference(self.toObject(targetValue), toString(memberValue), false)) + return toValue(newObjectReference(self.toObject(targetValue), toString(memberValue), false, node)) } func (self *_runtime) evaluateIdentifier(node *_identifierNode) Value { name := node.Value // TODO Should be true or false (strictness) depending on context + // TODO Associate the node with reference... how? + // TODO Can/Will getIdentifierReference ever return nil? reference := getIdentifierReference(self.LexicalEnvironment(), name, false) if reference == nil { panic("referenceError: " + name) diff --git a/otto_error_test.go b/otto_error_test.go index 0b099bd..1615c03 100644 --- a/otto_error_test.go +++ b/otto_error_test.go @@ -29,4 +29,21 @@ func TestOttoError(t *testing.T) { `) Is(err, "ReferenceError: abcdef is not defined (line 2)") + _, err = Otto.Run(` + function start() { + } + + start() + + xyzzy() + `) + Is(err, "ReferenceError: xyzzy is not defined (line 6)") + + _, err = Otto.Run(` + // Just a comment + + xyzzy + `) + Is(err, "ReferenceError: xyzzy is not defined (line 3)") + } diff --git a/parser.go b/parser.go index fe36cc3..d47776e 100644 --- a/parser.go +++ b/parser.go @@ -162,7 +162,9 @@ func (self *_parser) matchAssignment() bool { } func (self *_parser) ConsumeNull() *_valueNode { - return newNullNode(self.Next().Text) + node := newNullNode(self.Next().Text) + self.markNode(node) + return node } func (self *_parser) ConsumeIdentifier() *_identifierNode { @@ -170,19 +172,27 @@ func (self *_parser) ConsumeIdentifier() *_identifierNode { if token.Kind != "identifier" { panic(token.newSyntaxError("Unexpected token %s", token.Kind)) } - return newIdentifierNode(token.Text) + node := newIdentifierNode(token.Text) + self.markNode(node) + return node } func (self *_parser) ConsumeString() *_valueNode { - return newStringNode(self.Next().Text) + node := newStringNode(self.Next().Text) + self.markNode(node) + return node } func (self *_parser) ConsumeBoolean() *_valueNode { - return newBooleanNode(self.Next().Text) + node := newBooleanNode(self.Next().Text) + self.markNode(node) + return node } func (self *_parser) ConsumeNumber() *_valueNode { - return newNumberNode(self.Next().Text) + node := newNumberNode(self.Next().Text) + self.markNode(node) + return node } func (self *_parser) ConsumeSemicolon() { diff --git a/type_reference.go b/type_reference.go index 09ed860..e5755fc 100644 --- a/type_reference.go +++ b/type_reference.go @@ -64,15 +64,17 @@ func (self *_argumentReference) PutValue(value Value) bool { type _objectReference struct { _referenceBase Base *_object + node _node } -func newObjectReference(base *_object, name string, strict bool) *_objectReference { +func newObjectReference(base *_object, name string, strict bool, node _node) *_objectReference { return &_objectReference{ Base: base, _referenceBase: _referenceBase{ name: name, strict: strict, }, + node: node, } } @@ -82,7 +84,7 @@ func (self *_objectReference) GetBase() *_object { func (self *_objectReference) GetValue() Value { if self.Base == nil { - panic(newReferenceError("notDefined", self.name)) + panic(newReferenceError("notDefined", self.name, self.node)) } return self.Base.GetValue(self.name) }