First end-to-end test (addition of numbers)

This commit is contained in:
Dave Cunningham 2016-03-08 00:48:00 -05:00
parent c0060affd2
commit 563bbe12f4
9 changed files with 458 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*~

30
desugarer.go Normal file
View File

@ -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
}

17
desugarer_test.go Normal file
View File

@ -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

234
interpreter.go Normal file
View File

@ -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
}

17
interpreter_test.go Normal file
View File

@ -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

56
main.go Normal file
View File

@ -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
}

57
main_test.go Normal file
View File

@ -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)
}
}
}

29
static_analyzer.go Normal file
View File

@ -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
}

17
static_analyzer_test.go Normal file
View File

@ -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