diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/desugarer.go b/desugarer.go new file mode 100644 index 0000000..3b51a5c --- /dev/null +++ b/desugarer.go @@ -0,0 +1,30 @@ +/* +Copyright 2016 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package jsonnet + +func desugar(ast astNode, objLevel int) (astNode, error) { + return ast, nil +} + +func desugarFile(ast astNode) (astNode, error) { + ast, err := desugar(ast, 0) + if err != nil { + return nil, err + } + // TODO(dcunnin): wrap in std local + return ast, nil +} diff --git a/desugarer_test.go b/desugarer_test.go new file mode 100644 index 0000000..deffeea --- /dev/null +++ b/desugarer_test.go @@ -0,0 +1,17 @@ +/* +Copyright 2016 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package jsonnet diff --git a/interpreter.go b/interpreter.go new file mode 100644 index 0000000..847a72f --- /dev/null +++ b/interpreter.go @@ -0,0 +1,234 @@ +/* +Copyright 2016 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package jsonnet + +import ( + "bytes" + "fmt" + "math" +) + +// Misc top-level stuff + +type vmExt struct { + value string + isCode bool +} + +type vmExtMap map[string]vmExt + +type RuntimeError struct { + StackTrace []traceFrame + Msg string +} + +func makeRuntimeError(msg string) RuntimeError { + return RuntimeError{ + Msg: msg, + } +} + +func (err RuntimeError) Error() string { + // TODO(dcunnin): Include stacktrace. + return err.Msg +} + +// Values and state + +type bindingFrame map[*identifier]thunk + +type value interface { +} + +type valueString struct { + value string +} + +func makeValueString(v string) *valueString { + return &valueString{value: v} +} + +type valueBoolean struct { + value bool +} + +func makeValueBoolean(v bool) *valueBoolean { + return &valueBoolean{value: v} +} + +type valueNumber struct { + value float64 +} + +func makeValueNumber(v float64) *valueNumber { + return &valueNumber{value: v} +} + +// TODO(dcunnin): Maybe intern values null, true, and false? +type valueNull struct { +} + +func makeValueNull() *valueNull { + return &valueNull{} +} + +type thunk struct { + Content value // nil if not filled + Name *identifier + UpValues bindingFrame + Self value + Offset int + Body astNode +} + +func makeThunk(name *identifier, self *value, offset int, body astNode) *thunk { + return &thunk{ + Name: name, + Self: self, + Offset: offset, + Body: body, + } +} + +func (t *thunk) fill(v value) { + t.Content = v + t.Self = nil + t.UpValues = make(bindingFrame) // clear the map +} + +type valueArray struct { + Elements []thunk +} + +func makeValueArray(elements []thunk) *valueArray { + return &valueArray{ + Elements: elements, + } +} + +// TODO(dcunnin): SimpleObject +// TODO(dcunnin): ExtendedObject +// TODO(dcunnin): ComprehensionObject +// TODO(dcunnin): Closure + +// The stack + +type traceFrame struct { + Loc LocationRange + Name string +} + +type callFrame struct { + bindings bindingFrame +} + +type callStack struct { + Calls int + Limit int + Stack []callFrame +} + +func makeCallStack(limit int) callStack { + return callStack{ + Calls: 0, + Limit: limit, + } +} + +// TODO(dcunnin): Add import callbacks. +// TODO(dcunnin): Add string output. +// TODO(dcunnin): Add multi output. + +type interpreter struct { + Stack callStack + ExternalVars vmExtMap +} + +func (this interpreter) execute(ast_ astNode) (value, error) { + // TODO(dcunnin): All the other cases... + switch ast := ast_.(type) { + case *astBinary: + // TODO(dcunnin): Assume it's + on numbers for now + leftVal, err := this.execute(ast.left) + if err != nil { + return nil, err + } + leftNum := leftVal.(*valueNumber).value + rightVal, err := this.execute(ast.right) + if err != nil { + return nil, err + } + rightNum := rightVal.(*valueNumber).value + return makeValueNumber(leftNum + rightNum), nil + case *astLiteralNull: + return makeValueNull(), nil + case *astLiteralBoolean: + return makeValueBoolean(ast.value), nil + case *astLiteralNumber: + return makeValueNumber(ast.value), nil + default: + return nil, makeRuntimeError("Executing this AST type not implemented yet.") + } +} + +func unparseNumber(v float64) string { + if v == math.Floor(v) { + return fmt.Sprintf("%.0f", v) + } else { + // See "What Every Computer Scientist Should Know About Floating-Point Arithmetic" + // Theorem 15 + // http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html + return fmt.Sprintf("%.17g", v) + } +} + +func (this interpreter) manifestJson( + v_ value, multiline bool, indent string, buf *bytes.Buffer) error { + // TODO(dcunnin): All the other types... + switch v := v_.(type) { + case *valueBoolean: + if v.value { + buf.WriteString("true") + } else { + buf.WriteString("false") + } + case *valueNull: + buf.WriteString("null") + case *valueNumber: + buf.WriteString(unparseNumber(v.value)) + default: + return makeRuntimeError("Manifesting this value not implemented yet.") + } + return nil +} + +func execute(ast astNode, ext vmExtMap, maxStack int) (string, error) { + theInterpreter := interpreter{ + Stack: makeCallStack(maxStack), + ExternalVars: ext, + } + result, err := theInterpreter.execute(ast) + if err != nil { + return "", err + } + var buffer bytes.Buffer + err = theInterpreter.manifestJson(result, true, "", &buffer) + if err != nil { + return "", err + } + return buffer.String(), nil +} diff --git a/interpreter_test.go b/interpreter_test.go new file mode 100644 index 0000000..900d04a --- /dev/null +++ b/interpreter_test.go @@ -0,0 +1,17 @@ +/* +Copyright 2016 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package jsonnet diff --git a/main.go b/main.go new file mode 100644 index 0000000..443f65c --- /dev/null +++ b/main.go @@ -0,0 +1,56 @@ +/* +Copyright 2016 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package jsonnet + +// Note: There are no garbage collection params because we're using the native Go garbage collector. +type Vm struct { + maxStack int + maxTrace int + ext vmExtMap +} + +func MakeVm() *Vm { + return &Vm{ + maxStack: 500, + maxTrace: 20, + } +} + +func (vm *Vm) ExtVar(key string, val string) { + vm.ext[key] = vmExt{value: val, isCode: false} +} + +func (vm *Vm) ExtCode(key string, val string) { + vm.ext[key] = vmExt{value: val, isCode: true} +} + +func (vm *Vm) EvaluateSnippet(filename string, snippet string) (string, error) { + tokens, err := lex(filename, snippet) + if err != nil { + return "", err + } + ast, err := parse(tokens) + if err != nil { + return "", err + } + ast, err = desugarFile(ast) + output, err := execute(ast, vm.ext, vm.maxStack) + if err != nil { + return "", err + } + return output, nil +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..6f5f7dc --- /dev/null +++ b/main_test.go @@ -0,0 +1,57 @@ +/* +Copyright 2016 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package jsonnet + +import ( + "testing" +) + +// Just a few simple sanity tests for now. Eventually we'll share end-to-end tests with the C++ +// implementation but unsure if that should be done here or via some external framework. + +type mainTest struct { + name string + input string + golden string + errString string +} + +var mainTests = []mainTest{ + {"numeric_literal", "100", "100", ""}, + {"boolean_literal", "true", "true", ""}, + {"simple_arith1", "3 + 3", "6", ""}, + {"simple_arith2", "3 + 3 + 3", "9", ""}, + {"simple_arith3", "(3 + 3) + (3 + 3)", "12", ""}, +} + +func TestMain(t *testing.T) { + for _, test := range mainTests { + vm := MakeVm() + output, err := vm.EvaluateSnippet(test.name, test.input) + var errString string + if err != nil { + errString = err.Error() + } + if errString != test.errString { + t.Errorf("%s: error result does not match. got\n\t%+v\nexpected\n\t%+v", + test.input, errString, test.errString) + } + if err == nil && output != test.golden { + t.Errorf("%s: got\n\t%+v\nexpected\n\t%+v", test.name, output, test.golden) + } + } +} diff --git a/static_analyzer.go b/static_analyzer.go new file mode 100644 index 0000000..ff98355 --- /dev/null +++ b/static_analyzer.go @@ -0,0 +1,29 @@ +/* +Copyright 2016 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package jsonnet + +// TODO(dcunnin): Check for invalid use of self, super, and bound variables. +// TODO(dcunnin): Compute free variables at each AST. +func analyseVisit(ast astNode, inObject bool, vars identifierSet) (identifierSet, error) { + var r identifierSet + return r, nil +} + +func analyse(ast astNode) error { + _, err := analyseVisit(ast, false, NewidentifierSet()) + return err +} diff --git a/static_analyzer_test.go b/static_analyzer_test.go new file mode 100644 index 0000000..900d04a --- /dev/null +++ b/static_analyzer_test.go @@ -0,0 +1,17 @@ +/* +Copyright 2016 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package jsonnet