├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── ast ├── array.go ├── assert.go ├── assign.go ├── asttest │ ├── array.go │ ├── array_test.go │ ├── assert.go │ ├── binary.go │ ├── binary_test.go │ ├── call.go │ ├── call_test.go │ ├── literal.go │ ├── literal_test.go │ └── map.go ├── binary.go ├── break.go ├── call.go ├── comment.go ├── continue.go ├── doc.go ├── error_scope.go ├── for.go ├── func.go ├── group.go ├── identifier.go ├── if.go ├── import.go ├── interpolate.go ├── key.go ├── key_value.go ├── literal.go ├── map.go ├── node.go ├── raise.go ├── return.go ├── switch.go ├── test.go ├── type.go ├── type_test.go └── unary.go ├── cmd ├── asm │ └── main.go ├── build │ └── main.go ├── doc │ └── main.go ├── lib-gen │ └── main.go ├── run │ └── main.go ├── test │ └── main.go └── version │ └── main.go ├── codecov.yml ├── compiler ├── array.go ├── array_test.go ├── assert.go ├── assert_test.go ├── assign.go ├── assign_test.go ├── binary.go ├── binary_test.go ├── block.go ├── call.go ├── call_test.go ├── compile.go ├── doc.go ├── error_scope.go ├── error_scope_test.go ├── expr.go ├── expr_test.go ├── for.go ├── for_test.go ├── func.go ├── func_test.go ├── identifier.go ├── if.go ├── if_test.go ├── interpolate.go ├── interpolate_test.go ├── key.go ├── key_test.go ├── literal.go ├── map.go ├── map_test.go ├── object_test.go ├── raise.go ├── return.go ├── return_test.go ├── statement.go ├── switch.go ├── switch_test.go ├── test.go ├── test_test.go ├── unary.go └── unary_test.go ├── fs ├── lib.go └── vfs.go ├── go.mod ├── go.sum ├── lexer ├── doc.go ├── options.go ├── pos.go ├── token.go ├── token_test.go ├── tokenize.go └── tokenize_test.go ├── lib ├── README.md ├── error │ ├── README.md │ ├── error.ok │ └── package.md ├── lang │ ├── README.md │ ├── add-assign.okt │ ├── add.okt │ ├── assert-raise.okt │ ├── char.okt │ ├── compare-any.okt │ ├── equal.okt │ ├── escape.okt │ ├── is.okt │ ├── len.okt │ ├── not-equal.okt │ ├── number.okt │ ├── string.okt │ ├── try.okt │ └── unary.okt ├── log │ ├── README.md │ ├── log.ok │ ├── log.okt │ └── package.md ├── math │ ├── README.md │ ├── abs.ok │ ├── abs.okt │ ├── constants.ok │ ├── constants.okt │ ├── log.ok │ ├── log.okt │ ├── package.md │ ├── powers.ok │ ├── powers.okt │ ├── rand.ok │ ├── rand.okt │ ├── rounding.ok │ └── rounding.okt ├── os │ ├── README.md │ ├── file.ok │ ├── file.okt │ ├── filesystem.ok │ ├── filesystem.okt │ ├── info.ok │ ├── info.okt │ ├── package.md │ ├── temp.ok │ └── temp.okt ├── reflect │ ├── README.md │ ├── call.ok │ ├── call.okt │ ├── get.ok │ ├── get.okt │ ├── interface.ok │ ├── interface.okt │ ├── kind.ok │ ├── kind.okt │ ├── len.ok │ ├── len.okt │ ├── package.md │ ├── props.ok │ ├── props.okt │ ├── set.ok │ ├── set.okt │ ├── type.ok │ └── type.okt ├── runtime │ ├── README.md │ ├── env.ok │ ├── env.okt │ ├── exit.ok │ ├── package.md │ ├── stack.ok │ └── stack.okt ├── strings │ ├── README.md │ ├── case.ok │ ├── case.okt │ ├── contains.ok │ ├── contains.okt │ ├── index.ok │ ├── index.okt │ ├── join.ok │ ├── join.okt │ ├── package.md │ ├── pad.ok │ ├── pad.okt │ ├── repeat.ok │ ├── repeat.okt │ ├── replace.ok │ ├── replace.okt │ ├── reverse.ok │ ├── reverse.okt │ ├── split.ok │ ├── split.okt │ ├── substr.ok │ ├── substr.okt │ ├── trim.ok │ └── trim.okt ├── time │ ├── README.md │ ├── add.ok │ ├── add.okt │ ├── compare.ok │ ├── compare.okt │ ├── duration.ok │ ├── duration.okt │ ├── package.md │ ├── sleep.ok │ ├── sleep.okt │ ├── time.ok │ ├── time.okt │ ├── unix.ok │ ├── unix.okt │ └── util.ok └── unicode │ ├── README.md │ ├── is.ok │ ├── is.okt │ ├── to.ok │ └── to.okt ├── main.go ├── number ├── arithmetic.go ├── arithmetic_test.go ├── cmp.go ├── cmp_test.go ├── doc.go ├── format.go ├── format_test.go ├── int.go ├── int_test.go ├── log.go ├── log_test.go ├── number.go ├── number_test.go ├── pow.go └── pow_test.go ├── parser ├── array.go ├── array_test.go ├── assert.go ├── assert_test.go ├── assign.go ├── assign_test.go ├── assignable.go ├── block.go ├── call.go ├── call_test.go ├── consume.go ├── doc.go ├── error.go ├── error_scope.go ├── error_scope_test.go ├── errors.go ├── expr.go ├── expr_test.go ├── for.go ├── for_test.go ├── func.go ├── func_test.go ├── group.go ├── identifier.go ├── if.go ├── if_test.go ├── import.go ├── import_test.go ├── interpolate.go ├── interpolate_test.go ├── key_value.go ├── literal.go ├── map.go ├── map_test.go ├── object_test.go ├── parse.go ├── parse_test.go ├── parser.go ├── raise.go ├── raise_test.go ├── return.go ├── return_test.go ├── statement.go ├── switch.go ├── switch_test.go ├── test.go ├── test_test.go ├── type.go ├── type_test.go ├── types.go └── util.go ├── tests ├── arithmetic │ ├── main.ok │ └── stdout.txt ├── assign │ ├── main.ok │ └── stdout.txt ├── chained-methods │ ├── main.ok │ └── stdout.txt ├── closure │ ├── main.ok │ └── stdout.txt ├── comments │ ├── main.ok │ └── stdout.txt ├── comparison │ ├── main.ok │ └── stdout.txt ├── error-unhandled │ ├── main.ok │ └── stdout.txt ├── example-arrays │ ├── main.ok │ └── stdout.txt ├── example-closure │ ├── main.ok │ └── stdout.txt ├── example-errors │ ├── main.ok │ └── stdout.txt ├── example-finally │ ├── main.ok │ └── stdout.txt ├── example-for │ ├── main.ok │ └── stdout.txt ├── example-functions │ ├── main.ok │ └── stdout.txt ├── example-hello-world │ ├── main.ok │ └── stdout.txt ├── example-if-else │ ├── main.ok │ └── stdout.txt ├── example-interfaces │ ├── main.ok │ └── stdout.txt ├── example-interpolate │ ├── main.ok │ └── stdout.txt ├── example-iteration │ ├── main.ok │ └── stdout.txt ├── example-maps │ ├── main.ok │ └── stdout.txt ├── example-methods │ ├── main.ok │ └── stdout.txt ├── example-multiple-return-values │ ├── main.ok │ └── stdout.txt ├── example-objects │ ├── main.ok │ └── stdout.txt ├── example-recursion │ ├── main.ok │ └── stdout.txt ├── example-switch │ ├── main.ok │ └── stdout.txt ├── example-time │ ├── main.ok │ └── stdout-ignored.txt ├── example-values │ ├── main.ok │ └── stdout.txt ├── example-variables │ ├── main.ok │ └── stdout.txt ├── for-in │ ├── main.ok │ └── stdout.txt ├── for │ ├── main.ok │ └── stdout.txt ├── functions │ ├── main.ok │ └── stdout.txt ├── group │ ├── main.ok │ └── stdout.txt ├── hello-world │ ├── main.ok │ └── stdout.txt ├── if │ ├── main.ok │ └── stdout.txt ├── import │ ├── main.ok │ ├── mypkg │ │ └── add.ok │ └── stdout.txt ├── interpolate │ ├── main.ok │ └── stdout.txt ├── len │ ├── main.ok │ └── stdout.txt ├── literals │ ├── main.ok │ └── stdout.txt ├── logger │ ├── main.ok │ └── stdout.txt ├── logical │ ├── main.ok │ └── stdout.txt ├── maps │ ├── main.ok │ └── stdout.txt ├── math │ ├── main.ok │ └── stdout.txt ├── methods │ ├── main.ok │ └── stdout.txt ├── nested-functions │ ├── main.ok │ └── stdout.txt ├── package-func-as-literal │ ├── main.ok │ └── stdout.txt ├── print │ ├── main.ok │ └── stdout.txt ├── raise │ ├── main.ok │ └── stdout.txt ├── strings │ ├── main.ok │ └── stdout.txt ├── switch-value │ ├── main.ok │ └── stdout.txt ├── switch │ ├── main.ok │ └── stdout.txt ├── test-assert-failed │ ├── main.ok │ ├── main.okt │ ├── stdout-test.txt │ └── stdout.txt ├── test-raised-error │ ├── main.ok │ ├── main.okt │ ├── stdout-test.txt │ └── stdout.txt ├── tests │ ├── main.ok │ ├── main.okt │ └── stdout.txt ├── time │ ├── main.ok │ └── stdout.txt ├── unary │ ├── main.ok │ └── stdout.txt └── variables │ ├── main.ok │ └── stdout.txt ├── types ├── base.go ├── kind.go ├── parse.go ├── registry.go ├── registry_test.go ├── type.go └── type_test.go ├── util ├── env.go ├── errors.go ├── functions.go ├── glob.go ├── glob_test.go ├── package.go └── package_test.go └── vm ├── add.go ├── add_test.go ├── and.go ├── and_test.go ├── append.go ├── append_test.go ├── array_alloc.go ├── array_alloc_test.go ├── array_get.go ├── array_get_test.go ├── array_set.go ├── array_set_test.go ├── assert.go ├── assert_test.go ├── assign.go ├── assign_test.go ├── call.go ├── call_test.go ├── cast.go ├── cast_test.go ├── close.go ├── combine.go ├── combine_test.go ├── concat.go ├── concat_test.go ├── divide.go ├── divide_test.go ├── doc.go ├── dynamic_call.go ├── env.go ├── equal.go ├── equal_test.go ├── error_scope.go ├── exit.go ├── finally.go ├── from_unix.go ├── func.go ├── get.go ├── greater_than.go ├── greater_than_equal.go ├── greater_than_equal_test.go ├── greater_than_test.go ├── info.go ├── instruction.go ├── instructions.go ├── interface.go ├── interpolate.go ├── is.go ├── jump.go ├── jump_unless.go ├── len.go ├── len_test.go ├── less_than.go ├── less_than_equal.go ├── less_than_equal_test.go ├── less_than_test.go ├── log.go ├── map_alloc.go ├── map_get.go ├── map_set.go ├── merge.go ├── merge_test.go ├── mkdir.go ├── multiply.go ├── multiply_test.go ├── next_array.go ├── next_map.go ├── next_string.go ├── not.go ├── not_equal.go ├── not_equal_test.go ├── not_test.go ├── now.go ├── okc.go ├── open.go ├── or.go ├── or_test.go ├── parent_scope.go ├── path.go ├── power.go ├── print.go ├── print_test.go ├── props.go ├── raise.go ├── rand.go ├── read.go ├── register.go ├── register_test.go ├── remainder.go ├── remainder_test.go ├── remove.go ├── rename.go ├── render.go ├── return.go ├── seek.go ├── set.go ├── sleep.go ├── stack.go ├── string_index.go ├── subtract.go ├── subtract_test.go ├── symbol.go ├── type.go ├── unicode.go ├── unix.go ├── vm.go └── write.go /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /ok 3 | /coverage.txt 4 | /README.md.bak 5 | /README.md.bak2 6 | /fs/lib.go.bak 7 | /lib-gen 8 | /cmd/version/main.go.bak 9 | /gh-md-toc 10 | 11 | # Releases 12 | /bin 13 | /ok-macos.zip 14 | /ok-linux.zip 15 | /ok-windows.zip 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Elliot Chance 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub release](https://img.shields.io/github/release/elliotchance/ok.svg)](https://github.com/elliotchance/ok/releases/) 2 | [![Build Status](https://travis-ci.org/elliotchance/ok.svg?branch=master)](https://travis-ci.org/elliotchance/ok) 3 | [![codecov](https://codecov.io/gh/elliotchance/ok/branch/master/graph/badge.svg)](https://codecov.io/gh/elliotchance/ok) 4 | [![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 5 | [![Join the chat at https://gitter.im/ok-lang/community](https://badges.gitter.im/ok-lang/community.svg)](https://gitter.im/ok-lang/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | **ok** is a strongly-duck-typed language. It has the rigidness and runtime 8 | safety of a strongly-typed language while providing a lot flexibility through 9 | interfaces and a simplified typing system. 10 | 11 | You can try it now (with examples) at [play.getok.dev](https://play.getok.dev), 12 | or read more about the language highlights in 13 | [Why OK?](https://github.com/elliotchance/ok/wiki/Why-OK%3F). 14 | 15 | [Visit the wiki](https://github.com/elliotchance/ok/wiki) for full 16 | documentation. 17 | -------------------------------------------------------------------------------- /ast/array.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "github.com/elliotchance/ok/types" 5 | ) 6 | 7 | // Array is zero or more elements. 8 | type Array struct { 9 | Kind *types.Type 10 | Elements []Node 11 | Pos string 12 | } 13 | 14 | // Position returns the position. 15 | func (node *Array) Position() string { 16 | return node.Pos 17 | } 18 | -------------------------------------------------------------------------------- /ast/assert.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Assert is for the `assert(BinaryExpr)` syntax. 4 | type Assert struct { 5 | Expr *Binary 6 | Pos string 7 | } 8 | 9 | // Position returns the position. 10 | func (node *Assert) Position() string { 11 | return node.Pos 12 | } 13 | 14 | // AssertRaise is for the `assert(Call raise TypeOrValue)` syntax. 15 | type AssertRaise struct { 16 | Call *Call 17 | TypeOrValue Node 18 | Pos string 19 | } 20 | 21 | // Position returns the position. 22 | func (node *AssertRaise) Position() string { 23 | return node.Pos 24 | } 25 | -------------------------------------------------------------------------------- /ast/assign.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Assign is a specific case of Binary, just for "=". 4 | type Assign struct { 5 | // There will always be at least one Lefts element. 6 | Lefts []Node 7 | 8 | // There may be one Right element, or the same number of elements as Lefts. 9 | Rights []Node 10 | } 11 | 12 | // Position returns the position. 13 | func (node *Assign) Position() string { 14 | return node.Lefts[0].Position() 15 | } 16 | -------------------------------------------------------------------------------- /ast/asttest/array.go: -------------------------------------------------------------------------------- 1 | package asttest 2 | 3 | import ( 4 | "github.com/elliotchance/ok/ast" 5 | "github.com/elliotchance/ok/types" 6 | ) 7 | 8 | // NewArrayNumbers creates an Array with some number literal values. 9 | func NewArrayNumbers(values []string) *ast.Array { 10 | var elements []ast.Node 11 | for _, value := range values { 12 | elements = append(elements, NewLiteralNumber(value)) 13 | } 14 | 15 | return &ast.Array{ 16 | Kind: types.NumberArray, 17 | Elements: elements, 18 | // No pos needed for testing. 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ast/asttest/array_test.go: -------------------------------------------------------------------------------- 1 | package asttest_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/ast/asttest" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestNewArrayNumbers(t *testing.T) { 12 | t.Run("zero-elements", func(t *testing.T) { 13 | node := asttest.NewArrayNumbers(nil) 14 | 15 | assert.Equal(t, "[]number", node.Kind.String()) 16 | assert.Nil(t, node.Elements) 17 | }) 18 | 19 | t.Run("two-elements", func(t *testing.T) { 20 | node := asttest.NewArrayNumbers([]string{"123", "4.56"}) 21 | 22 | assert.Equal(t, "[]number", node.Kind.String()) 23 | assert.Equal(t, []ast.Node{ 24 | asttest.NewLiteralNumber("123"), 25 | asttest.NewLiteralNumber("4.56"), 26 | }, node.Elements) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /ast/asttest/binary.go: -------------------------------------------------------------------------------- 1 | package asttest 2 | 3 | import "github.com/elliotchance/ok/ast" 4 | 5 | // NewBinary creates a binary operation. 6 | func NewBinary(left ast.Node, op string, right ast.Node) *ast.Binary { 7 | return &ast.Binary{ 8 | Left: left, 9 | Op: op, 10 | Right: right, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ast/asttest/binary_test.go: -------------------------------------------------------------------------------- 1 | package asttest_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNewBinary(t *testing.T) { 11 | node := asttest.NewBinary( 12 | asttest.NewLiteralData([]byte("foo")), 13 | "/", 14 | asttest.NewLiteralData([]byte("bar")), 15 | ) 16 | assert.Equal(t, asttest.NewLiteralData([]byte("foo")), node.Left) 17 | assert.Equal(t, "/", node.Op) 18 | assert.Equal(t, asttest.NewLiteralData([]byte("bar")), node.Right) 19 | } 20 | -------------------------------------------------------------------------------- /ast/asttest/call.go: -------------------------------------------------------------------------------- 1 | package asttest 2 | 3 | import "github.com/elliotchance/ok/ast" 4 | 5 | // NewCall produces a new call to functionName with any number of arguments. 6 | func NewCall(functionName string, arguments ...ast.Node) *ast.Call { 7 | return &ast.Call{ 8 | Expr: &ast.Identifier{Name: functionName}, 9 | Arguments: arguments, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ast/asttest/call_test.go: -------------------------------------------------------------------------------- 1 | package asttest_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/ast/asttest" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestNewCall(t *testing.T) { 12 | call := asttest.NewCall( 13 | "foo", 14 | asttest.NewLiteralString("bar"), 15 | asttest.NewLiteralString("baz"), 16 | ) 17 | assert.Equal(t, &ast.Identifier{Name: "foo"}, call.Expr) 18 | assert.Equal(t, []ast.Node{ 19 | asttest.NewLiteralString("bar"), 20 | asttest.NewLiteralString("baz"), 21 | }, call.Arguments) 22 | } 23 | -------------------------------------------------------------------------------- /ast/asttest/literal.go: -------------------------------------------------------------------------------- 1 | package asttest 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/types" 8 | ) 9 | 10 | // NewLiteralData create a new literal representing a data value. 11 | func NewLiteralData(data []byte) *ast.Literal { 12 | return &ast.Literal{ 13 | Kind: types.Data, 14 | Value: string(data), 15 | } 16 | } 17 | 18 | // NewLiteralNumber create a new literal representing a number value. 19 | func NewLiteralNumber(number string) *ast.Literal { 20 | return &ast.Literal{ 21 | Kind: types.Number, 22 | Value: number, 23 | } 24 | } 25 | 26 | // NewLiteralString create a new literal representing a string value. 27 | func NewLiteralString(str string) *ast.Literal { 28 | return &ast.Literal{ 29 | Kind: types.String, 30 | Value: str, 31 | } 32 | } 33 | 34 | // NewLiteralBool create a new literal representing a boolean value. 35 | func NewLiteralBool(b bool) *ast.Literal { 36 | return &ast.Literal{ 37 | Kind: types.Bool, 38 | Value: strconv.FormatBool(b), 39 | } 40 | } 41 | 42 | // NewLiteralChar create a new literal representing a character value. 43 | func NewLiteralChar(c rune) *ast.Literal { 44 | return &ast.Literal{ 45 | Kind: types.Char, 46 | Value: string(c), 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ast/asttest/map.go: -------------------------------------------------------------------------------- 1 | package asttest 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/types" 8 | ) 9 | 10 | // NewMapNumbers creates a Map with some number literal values. This function is 11 | // for convenience and should not be used for general production code. 12 | func NewMapNumbers(values map[string]string) *ast.Map { 13 | // For predictability we load the keys in order. 14 | var keys []string 15 | for key := range values { 16 | keys = append(keys, key) 17 | } 18 | 19 | sort.Strings(keys) 20 | 21 | var elements []*ast.KeyValue 22 | for _, key := range keys { 23 | value := values[key] 24 | elements = append(elements, &ast.KeyValue{ 25 | Key: NewLiteralString(key), 26 | Value: NewLiteralNumber(value), 27 | }) 28 | } 29 | 30 | return &ast.Map{ 31 | Kind: types.NumberMap, 32 | Elements: elements, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ast/binary.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Binary is an binary operator operation. 4 | type Binary struct { 5 | // Op is TokenPlus, TokenMinusAssign, etc. It will never be TokenAssign, 6 | // this special case is handled in an Assign operation. 7 | Op string 8 | 9 | Left, Right Node 10 | } 11 | 12 | // Position returns the position. 13 | func (node *Binary) Position() string { 14 | return node.Left.Position() 15 | } 16 | -------------------------------------------------------------------------------- /ast/break.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Break represents a "break" statement. 4 | type Break struct { 5 | Pos string 6 | } 7 | 8 | // Position returns the position. 9 | func (node *Break) Position() string { 10 | return node.Pos 11 | } 12 | -------------------------------------------------------------------------------- /ast/call.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Call represents a function call with zero or more arguments. 4 | type Call struct { 5 | // Expr is the expression that returns the function to be called. 6 | Expr Node 7 | 8 | // Arguments contains zero or more elements that represent each of the 9 | // arguments respectively. 10 | Arguments []Node 11 | 12 | Pos string 13 | } 14 | 15 | // Position returns the position. 16 | func (node *Call) Position() string { 17 | return node.Pos 18 | } 19 | -------------------------------------------------------------------------------- /ast/comment.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "strings" 4 | 5 | // Comment represents a single or multiline comment. All characters immediately 6 | // following `//` are part of the comment (even the proceeding space) up to but 7 | // not including the new line. 8 | type Comment struct { 9 | Comment string 10 | 11 | // Func will be the name of the function this comment is attached to; 12 | // otherwise it will be empty. 13 | Func string 14 | 15 | Pos string 16 | } 17 | 18 | // String returns the cleaner presentation version of the comment. 19 | func (c *Comment) String() string { 20 | lines := strings.Split(c.Comment, "\n") 21 | var newLines []string 22 | for _, line := range lines { 23 | newLines = append(newLines, strings.TrimSpace(line)) 24 | } 25 | 26 | return strings.Join(newLines, "\n") 27 | } 28 | 29 | // Position returns the position. 30 | func (c *Comment) Position() string { 31 | return c.Pos 32 | } 33 | -------------------------------------------------------------------------------- /ast/continue.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Continue represents a "continue" statement. 4 | type Continue struct { 5 | Pos string 6 | } 7 | 8 | // Position returns the position. 9 | func (node *Continue) Position() string { 10 | return node.Pos 11 | } 12 | -------------------------------------------------------------------------------- /ast/doc.go: -------------------------------------------------------------------------------- 1 | // Package ast provides structures for representing parsed source code. 2 | // 3 | // The structures in this package will be initialized from the parser package. 4 | package ast 5 | -------------------------------------------------------------------------------- /ast/for.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // For represents a for loop. 4 | type For struct { 5 | // All of Init, Condition and Next may be nil. 6 | Init, Condition, Next Node 7 | 8 | // Statements may be nil. 9 | Statements []Node 10 | 11 | Pos string 12 | } 13 | 14 | // Position returns the position. 15 | func (node *For) Position() string { 16 | return node.Pos 17 | } 18 | 19 | // In represents an "in" expression in for loops. 20 | type In struct { 21 | Value, Key string 22 | Expr Node 23 | Pos string 24 | } 25 | 26 | // Position returns the position. 27 | func (node *In) Position() string { 28 | return node.Pos 29 | } 30 | -------------------------------------------------------------------------------- /ast/group.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Group is an expression wrapped in "()". 4 | type Group struct { 5 | Expr Node 6 | Pos string 7 | } 8 | 9 | // Position returns the position. 10 | func (node *Group) Position() string { 11 | return node.Pos 12 | } 13 | -------------------------------------------------------------------------------- /ast/identifier.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Identifier could refer to a variable, function, etc. 4 | type Identifier struct { 5 | Name string 6 | Pos string 7 | } 8 | 9 | // Position returns the position. 10 | func (node *Identifier) Position() string { 11 | return node.Pos 12 | } 13 | -------------------------------------------------------------------------------- /ast/if.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // If represents an if/else combination. 4 | type If struct { 5 | Condition Node 6 | 7 | // Either or both is allowed to be nil. 8 | True, False []Node 9 | 10 | Pos string 11 | } 12 | 13 | // Position returns the position. 14 | func (node *If) Position() string { 15 | return node.Pos 16 | } 17 | -------------------------------------------------------------------------------- /ast/import.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Import is used to include packages. 4 | type Import struct { 5 | VariableName string 6 | PackageName string 7 | Pos string 8 | } 9 | 10 | // Position returns the position. 11 | func (node *Import) Position() string { 12 | return node.Pos 13 | } 14 | -------------------------------------------------------------------------------- /ast/interpolate.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Interpolate is a string literal that contains expressions. 4 | type Interpolate struct { 5 | // Parts will have at least one element. Each element will be one of 6 | // StringLiteral or Group. However, they can appear in any order. 7 | Parts []Node 8 | 9 | Pos string 10 | } 11 | 12 | // Position returns the position. 13 | func (node *Interpolate) Position() string { 14 | return node.Pos 15 | } 16 | -------------------------------------------------------------------------------- /ast/key.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Key returns the value based on the index. It is used for arrays, maps and 4 | // objects. 5 | type Key struct { 6 | Expr Node 7 | Key Node 8 | Pos string 9 | } 10 | 11 | // Position returns the position. 12 | func (node *Key) Position() string { 13 | return node.Pos 14 | } 15 | -------------------------------------------------------------------------------- /ast/key_value.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // KeyValue represents a key-value pair used in maps and object initialization. 4 | type KeyValue struct { 5 | Key, Value Node 6 | } 7 | 8 | // Position returns the position. 9 | func (node *KeyValue) Position() string { 10 | return node.Key.Position() 11 | } 12 | -------------------------------------------------------------------------------- /ast/map.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "github.com/elliotchance/ok/types" 4 | 5 | // Map is zero or more elements. 6 | type Map struct { 7 | Kind *types.Type 8 | Elements []*KeyValue 9 | Pos string 10 | } 11 | 12 | // Position returns the position. 13 | func (node *Map) Position() string { 14 | return node.Pos 15 | } 16 | -------------------------------------------------------------------------------- /ast/node.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Node is the interface for all ast structures. 4 | type Node interface { 5 | Position() string 6 | } 7 | -------------------------------------------------------------------------------- /ast/raise.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Raise will raise an error to be handled. An error must be in the form of a 4 | // constructor call, this is for simplicity right now. 5 | type Raise struct { 6 | Err Node 7 | Pos string 8 | } 9 | 10 | // Position returns the position. 11 | func (node *Raise) Position() string { 12 | return node.Pos 13 | } 14 | -------------------------------------------------------------------------------- /ast/return.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Return is a statement to return values. 4 | type Return struct { 5 | // Exprs can be zero or more elements. 6 | Exprs []Node 7 | Pos string 8 | } 9 | 10 | // Position returns the position. 11 | func (node *Return) Position() string { 12 | return node.Pos 13 | } 14 | -------------------------------------------------------------------------------- /ast/switch.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Case represents a switch case statement. 4 | type Case struct { 5 | // Conditions will always contain at least one element. 6 | Conditions []Node 7 | 8 | // Statements may be nil. 9 | Statements []Node 10 | 11 | Pos string 12 | } 13 | 14 | // Position returns the position. 15 | func (node *Case) Position() string { 16 | return node.Pos 17 | } 18 | 19 | // Switch represents a switch statement. 20 | type Switch struct { 21 | // Expr may be nil. 22 | Expr Node 23 | 24 | // Cases may be nil. 25 | Cases []*Case 26 | 27 | // Else may be nil. 28 | Else []Node 29 | 30 | Pos string 31 | } 32 | 33 | // Position returns the position. 34 | func (node *Switch) Position() string { 35 | return node.Pos 36 | } 37 | -------------------------------------------------------------------------------- /ast/test.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Test is a named test. 4 | type Test struct { 5 | Name string 6 | Statements []Node 7 | Pos string 8 | } 9 | 10 | // Position returns the position. 11 | func (node *Test) Position() string { 12 | return node.Pos 13 | } 14 | -------------------------------------------------------------------------------- /ast/type.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/types" 7 | ) 8 | 9 | func TypeOf(node Node) (*types.Type, error) { 10 | switch n := node.(type) { 11 | case *Literal: 12 | return n.Kind, nil 13 | 14 | case *Func: 15 | return n.Type(), nil 16 | 17 | case *Interpolate: 18 | // The result of an interpolation is always a string. 19 | return types.String, nil 20 | } 21 | 22 | return nil, fmt.Errorf("cannot resolve type for %v (%T)", node, node) 23 | } 24 | -------------------------------------------------------------------------------- /ast/type_test.go: -------------------------------------------------------------------------------- 1 | package ast_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/types" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestTypeOf(t *testing.T) { 12 | tests := map[string]struct { 13 | n ast.Node 14 | expected *types.Type 15 | }{ 16 | "literal-bool": { 17 | &ast.Literal{ 18 | Kind: types.Bool, 19 | Value: "true", 20 | }, 21 | types.Bool, 22 | }, 23 | "func": { 24 | &ast.Func{ 25 | Name: "foo", 26 | Arguments: []*ast.Argument{ 27 | {Name: "bar", Type: types.String}, 28 | }, 29 | Returns: []*types.Type{types.Number}, 30 | }, 31 | types.TypeFromString("func(string) number"), 32 | }, 33 | "closure": { 34 | &ast.Func{}, 35 | types.TypeFromString("func()"), 36 | }, 37 | "interpolate": { 38 | &ast.Interpolate{}, 39 | types.String, 40 | }, 41 | } 42 | for testName, tt := range tests { 43 | t.Run(testName, func(t *testing.T) { 44 | ty, err := ast.TypeOf(tt.n) 45 | assert.NoError(t, err) 46 | assert.Equal(t, tt.expected, ty) 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ast/unary.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | // Unary is an unary operator operation. 4 | type Unary struct { 5 | // Op is TokenMinus, TokenNot, TokenIncrement or TokenDecrement. 6 | Op string 7 | Expr Node 8 | Pos string 9 | } 10 | 11 | // Position returns the position. 12 | func (node *Unary) Position() string { 13 | return node.Pos 14 | } 15 | -------------------------------------------------------------------------------- /cmd/run/main.go: -------------------------------------------------------------------------------- 1 | package run 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/elliotchance/ok/compiler" 7 | "github.com/elliotchance/ok/util" 8 | "github.com/elliotchance/ok/vm" 9 | ) 10 | 11 | type Command struct{} 12 | 13 | func check(err error) { 14 | if err != nil { 15 | log.Fatalln(err) 16 | } 17 | } 18 | 19 | // Description is shown in "ok -help". 20 | func (*Command) Description() string { 21 | return "run ok program" 22 | } 23 | 24 | // Run is the entry point for the "ok run" command. 25 | func (*Command) Run(args []string) { 26 | if len(args) == 0 { 27 | args = []string{"."} 28 | } 29 | 30 | okPath, err := util.OKPath() 31 | check(err) 32 | 33 | for _, arg := range args { 34 | packageName := util.PackageNameFromPath(okPath, arg) 35 | if arg == "." { 36 | packageName = "." 37 | } 38 | 39 | m := vm.NewVM("no-package") 40 | anonFunctionName := 0 41 | _, packageType, errs := compiler.Compile(okPath, packageName, false, 42 | &anonFunctionName, false) 43 | util.CheckErrorsWithExit(errs) 44 | 45 | check(m.LoadPackage(packageName)) 46 | check(m.Run("$" + packageType.Name)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cmd/version/main.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Command struct{} 8 | 9 | // Version will be replaced when building the binaries. See "make version". 10 | const Version = "ok version v0.17.2 2020-08-01" 11 | 12 | // Description is shown in "ok -help". 13 | func (*Command) Description() string { 14 | return "print ok version" 15 | } 16 | 17 | // Run is the entry point for the "ok version" command. 18 | func (*Command) Run(args []string) { 19 | fmt.Println(Version) 20 | } 21 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | informational: true 6 | patch: 7 | default: 8 | informational: true 9 | -------------------------------------------------------------------------------- /compiler/doc.go: -------------------------------------------------------------------------------- 1 | // Package compiler translates the ast structures that represent the parsed 2 | // course code into instructions that can be executed by the VM. 3 | package compiler 4 | -------------------------------------------------------------------------------- /compiler/interpolate.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "github.com/elliotchance/ok/ast" 5 | "github.com/elliotchance/ok/types" 6 | "github.com/elliotchance/ok/vm" 7 | ) 8 | 9 | func compileInterpolate( 10 | compiledFunc *vm.CompiledFunc, 11 | n *ast.Interpolate, 12 | file *vm.File, 13 | scopeOverrides map[string]*types.Type, 14 | ) (vm.Register, error) { 15 | ins := &vm.Interpolate{ 16 | Result: compiledFunc.NextRegister(), 17 | } 18 | 19 | for _, part := range n.Parts { 20 | partRegisters, _, err := compileExpr(compiledFunc, part, file, scopeOverrides) 21 | if err != nil { 22 | return ins.Result, err 23 | } 24 | 25 | ins.Args = append(ins.Args, partRegisters[0]) 26 | } 27 | 28 | compiledFunc.Append(ins) 29 | 30 | return ins.Result, nil 31 | } 32 | -------------------------------------------------------------------------------- /compiler/literal.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "github.com/elliotchance/ok/ast" 5 | "github.com/elliotchance/ok/types" 6 | "github.com/elliotchance/ok/vm" 7 | ) 8 | 9 | func compileLiteral( 10 | compiledFunc *vm.CompiledFunc, 11 | e *ast.Literal, 12 | file *vm.File, 13 | ) (vm.Register, *types.Type) { 14 | returns := compiledFunc.NextRegister() 15 | compiledFunc.Append(&vm.AssignSymbol{ 16 | Result: returns, 17 | Symbol: file.AddSymbolLiteral(e), 18 | }) 19 | 20 | return returns, e.Kind 21 | } 22 | -------------------------------------------------------------------------------- /compiler/raise.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "github.com/elliotchance/ok/ast" 5 | "github.com/elliotchance/ok/types" 6 | "github.com/elliotchance/ok/vm" 7 | ) 8 | 9 | func compileRaise( 10 | compiledFunc *vm.CompiledFunc, 11 | n *ast.Raise, 12 | file *vm.File, 13 | scopeOverrides map[string]*types.Type, 14 | ) error { 15 | // TODO(elliot): Check this call returns a type that satisfies an error 16 | // interface. 17 | // 18 | // TODO(elliot): It may not be a Call. 19 | result, resultKind, err := compileCall(compiledFunc, n.Err.(*ast.Call), 20 | file, scopeOverrides) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | typeRegister := file.AddType(resultKind[0]) 26 | compiledFunc.Append(&vm.Raise{ 27 | Err: result[0], 28 | Type: typeRegister, 29 | Pos: n.Pos, 30 | }) 31 | 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /compiler/return.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "github.com/elliotchance/ok/ast" 5 | "github.com/elliotchance/ok/types" 6 | "github.com/elliotchance/ok/vm" 7 | ) 8 | 9 | func compileReturn( 10 | compiledFunc *vm.CompiledFunc, 11 | n *ast.Return, 12 | file *vm.File, 13 | scopeOverrides map[string]*types.Type, 14 | ) error { 15 | var results []vm.Register 16 | for _, expr := range n.Exprs { 17 | // TODO(elliot): Check return types are valid. 18 | result, _, err := compileExpr(compiledFunc, expr, file, scopeOverrides) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | results = append(results, result...) 24 | } 25 | 26 | compiledFunc.Append(&vm.Return{ 27 | Results: results, 28 | }) 29 | 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /compiler/test.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "github.com/elliotchance/ok/ast" 5 | "github.com/elliotchance/ok/types" 6 | "github.com/elliotchance/ok/vm" 7 | ) 8 | 9 | // CompileTest will compile a test. 10 | func CompileTest( 11 | fn *ast.Test, 12 | file *vm.File, 13 | constants map[string]*ast.Literal, 14 | imports map[string]*types.Type, 15 | ) (*vm.CompiledTest, error) { 16 | // Tests can be compiled as if they were functions, then wrapped in a 17 | // CompiledTest. 18 | // 19 | // TODO(elliot): When tests can be nested the third argument for parentFunc 20 | // should not be nil. 21 | compiledFunc, err := CompileFunc(&ast.Func{ 22 | Statements: fn.Statements, 23 | Pos: fn.Pos, 24 | }, file, nil, constants, imports, map[string]*types.Type{}) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | return &vm.CompiledTest{ 30 | CompiledFunc: compiledFunc, 31 | TestName: fn.Name, 32 | }, nil 33 | } 34 | -------------------------------------------------------------------------------- /compiler/test_test.go: -------------------------------------------------------------------------------- 1 | package compiler_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/compiler" 8 | "github.com/elliotchance/ok/types" 9 | "github.com/elliotchance/ok/vm" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestTest(t *testing.T) { 15 | for testName, test := range map[string]struct { 16 | fn *ast.Test 17 | expected []vm.Instruction 18 | err error 19 | }{ 20 | "no-statements": { 21 | fn: &ast.Test{}, 22 | }, 23 | "one-statement": { 24 | fn: &ast.Test{ 25 | Statements: []ast.Node{ 26 | &ast.Call{ 27 | Expr: &ast.Identifier{Name: "print"}, 28 | }, 29 | }, 30 | }, 31 | expected: []vm.Instruction{ 32 | &vm.Print{}, 33 | }, 34 | }, 35 | } { 36 | t.Run(testName, func(t *testing.T) { 37 | compiledFunc, err := compiler.CompileTest(test.fn, 38 | &vm.File{ 39 | Types: types.Registry{}, 40 | }, nil, nil) 41 | if test.err != nil { 42 | assert.EqualError(t, err, test.err.Error()) 43 | } else { 44 | require.NoError(t, err) 45 | assert.Equal(t, test.expected, compiledFunc.Instructions.Instructions) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /fs/vfs.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "github.com/blang/vfs/mountfs" 5 | ) 6 | 7 | // Filesystem wraps the OS field system, but allow the stdlib to be mounted. See 8 | // lib-gen. 9 | var Filesystem *mountfs.MountFS 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/elliotchance/ok 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/blang/vfs v1.0.0 7 | github.com/cockroachdb/apd/v2 v2.0.2 8 | github.com/google/go-cmp v0.5.0 9 | github.com/lib/pq v1.7.0 // indirect 10 | github.com/pkg/errors v0.8.0 11 | github.com/stretchr/testify v1.6.0 12 | ) 13 | -------------------------------------------------------------------------------- /lexer/doc.go: -------------------------------------------------------------------------------- 1 | // Package lexer converts source code into tokens. Tokens are indivisible units 2 | // that are used by the parser package to create the AST structures that 3 | // represent the source code. 4 | package lexer 5 | -------------------------------------------------------------------------------- /lexer/options.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | // Options allows configuration of the lexer. 4 | type Options struct { 5 | // IncludeComments will include TokenComment in the returned tokens. 6 | IncludeComments bool 7 | } 8 | -------------------------------------------------------------------------------- /lexer/pos.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | import "fmt" 4 | 5 | // Pos describes the position of a token. 6 | type Pos struct { 7 | FileName string 8 | LineNumber, CharacterNumber int 9 | } 10 | 11 | func (pos Pos) add(characters int) Pos { 12 | pos.CharacterNumber += characters 13 | 14 | return pos 15 | } 16 | 17 | func (pos *Pos) nextLine() { 18 | pos.LineNumber++ 19 | pos.CharacterNumber = 0 20 | } 21 | 22 | // String returns a human-readable position. 23 | func (pos *Pos) String() string { 24 | return fmt.Sprintf("%s:%d:%d", pos.FileName, pos.LineNumber, pos.CharacterNumber) 25 | } 26 | -------------------------------------------------------------------------------- /lexer/token_test.go: -------------------------------------------------------------------------------- 1 | package lexer_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/lexer" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestToken_String(t *testing.T) { 11 | for testName, test := range map[string]struct { 12 | kind string 13 | value string 14 | expected string 15 | }{ 16 | "EOF": {lexer.TokenEOF, "", "end of file"}, 17 | "paren-open": {lexer.TokenParenOpen, "(", "'('"}, 18 | "paren-close": {lexer.TokenParenClose, ")", "')'"}, 19 | "identifier": {lexer.TokenIdentifier, "main", `"main"`}, 20 | } { 21 | t.Run(testName, func(t *testing.T) { 22 | actual := lexer.Token{Kind: test.kind, Value: test.value}.String() 23 | assert.Equal(t, test.expected, actual) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/README.md: -------------------------------------------------------------------------------- 1 | # Standard Library 2 | 3 | - [error](https://github.com/elliotchance/ok/tree/master/lib/error) - Error handling. 4 | - [log](https://github.com/elliotchance/ok/tree/master/lib/log) - Logging. 5 | - [math](https://github.com/elliotchance/ok/tree/master/lib/math) - Mathematical functions. 6 | - [os](https://github.com/elliotchance/ok/tree/master/lib/os) - Operating and file system functions. 7 | - [reflect](https://github.com/elliotchance/ok/tree/master/lib/reflect) - Runtime checking and manipulating of types and values. 8 | - [runtime](https://github.com/elliotchance/ok/tree/master/lib/runtime) - Current process and runtime environment. 9 | - [strings](https://github.com/elliotchance/ok/tree/master/lib/strings) - Common string checking and manipulation. 10 | - [time](https://github.com/elliotchance/ok/tree/master/lib/time) - Time and date functions. 11 | - [unicode](https://github.com/elliotchance/ok/tree/master/lib/unicode) - Unicode and character functions. 12 | -------------------------------------------------------------------------------- /lib/error/README.md: -------------------------------------------------------------------------------- 1 | # Package error 2 | 3 | The `errors` package contains standard interfaces for basic error handling. 4 | 5 | ### Example 6 | 7 | ``` 8 | import "error" 9 | 10 | func divide(a, b number) number { 11 | if b == 0 { 12 | raise error.Error("cannot divide by zero") 13 | } 14 | 15 | return a / b 16 | } 17 | ``` 18 | 19 | 20 | ## Index 21 | 22 | - [func Error(Error string) Error](#Error) 23 | 24 | ### Error 25 | 26 | ``` 27 | func Error(Error string) Error 28 | ``` 29 | 30 | Error is a basic type to carry an error message. 31 | 32 | -------------------------------------------------------------------------------- /lib/error/error.ok: -------------------------------------------------------------------------------- 1 | // Error is a basic type to carry an error message. 2 | func Error(Error string) Error { 3 | } 4 | -------------------------------------------------------------------------------- /lib/error/package.md: -------------------------------------------------------------------------------- 1 | The `errors` package contains standard interfaces for basic error handling. 2 | 3 | ### Example 4 | 5 | ``` 6 | import "error" 7 | 8 | func divide(a, b number) number { 9 | if b == 0 { 10 | raise error.Error("cannot divide by zero") 11 | } 12 | 13 | return a / b 14 | } 15 | ``` 16 | -------------------------------------------------------------------------------- /lib/lang/README.md: -------------------------------------------------------------------------------- 1 | # Package lang 2 | 3 | ## Index 4 | 5 | 6 | -------------------------------------------------------------------------------- /lib/lang/add-assign.okt: -------------------------------------------------------------------------------- 1 | test "append element" { 2 | a = ["a"] 3 | a += ["b"] 4 | assert(a == ["a", "b"]) 5 | } 6 | -------------------------------------------------------------------------------- /lib/lang/add.okt: -------------------------------------------------------------------------------- 1 | test "+" { 2 | number1 = 12.3 + 4.56 3 | assert(number1 == 16.86) 4 | number2 = 12.3 + -4.56 5 | assert(number2 == 7.74) 6 | 7 | bools1 = [true, false] + [true] 8 | assert(bools1 == [true, false, true]) 9 | bools2 = []bool [] + []bool [] 10 | assert(bools2 == []bool []) 11 | 12 | chars1 = ['a', 'b'] + ['c'] 13 | assert(chars1 == ['a', 'b', 'c']) 14 | 15 | datas1 = [`a`, `b`] + [`c`] 16 | assert(datas1 == [`a`, `b`, `c`]) 17 | 18 | numbers1 = [1, 2] + [3] 19 | assert(numbers1 == [1, 2, 3]) 20 | 21 | strings1 = ["foo", "bar"] + ["baz"] 22 | assert(strings1 == ["foo", "bar", "baz"]) 23 | } 24 | -------------------------------------------------------------------------------- /lib/lang/assert-raise.okt: -------------------------------------------------------------------------------- 1 | import "error" 2 | 3 | func raiseAnError() { 4 | raise error.Error("oh oh") 5 | } 6 | 7 | test "assert raising a type" { 8 | assert(raiseAnError() raise error.Error) 9 | } 10 | -------------------------------------------------------------------------------- /lib/lang/char.okt: -------------------------------------------------------------------------------- 1 | test "casting a number to a character" { 2 | assert(char 97 == 'a') 3 | assert(char 128515 == '😃') 4 | } 5 | 6 | test "comparing character literals" { 7 | assert(('B' == 'A') == false) 8 | assert(('A' == 'A') == true) 9 | assert(('A' == 'B') == false) 10 | 11 | assert(('B' != 'A') == true) 12 | assert(('A' != 'A') == false) 13 | assert(('A' != 'B') == true) 14 | 15 | assert(('B' > 'A') == true) 16 | assert(('A' > 'A') == false) 17 | assert(('A' > 'B') == false) 18 | 19 | assert(('B' >= 'A') == true) 20 | assert(('A' >= 'A') == true) 21 | assert(('A' >= 'B') == false) 22 | 23 | assert(('B' < 'A') == false) 24 | assert(('A' < 'A') == false) 25 | assert(('A' < 'B') == true) 26 | 27 | assert(('B' <= 'A') == false) 28 | assert(('A' <= 'A') == true) 29 | assert(('A' <= 'B') == true) 30 | } 31 | -------------------------------------------------------------------------------- /lib/lang/compare-any.okt: -------------------------------------------------------------------------------- 1 | func compareValue(x any) string { 2 | if x == true { 3 | return "bool(true)" 4 | } 5 | 6 | if 123 == x { 7 | return "number(123)" 8 | } 9 | 10 | return "unknown" 11 | } 12 | 13 | test "any comparison" { 14 | assert(compareValue(true) == "bool(true)") 15 | assert(compareValue(false) == "unknown") 16 | assert(compareValue(123) == "number(123)") 17 | assert(compareValue(123.00) == "number(123)") 18 | assert(compareValue(123.01) == "unknown") 19 | assert(compareValue("hi") == "unknown") 20 | assert(compareValue([]string []) == "unknown") 21 | } 22 | -------------------------------------------------------------------------------- /lib/lang/escape.okt: -------------------------------------------------------------------------------- 1 | test "escape characters" { 2 | // The values are hard to test, but we can check the lengths. 3 | assert(len("\a") == 1) 4 | assert(len("\b") == 1) 5 | assert(len("\f") == 1) 6 | assert(len("\n") == 1) 7 | assert(len("\r") == 1) 8 | assert(len("\t") == 1) 9 | assert(len("\v") == 1) 10 | assert(len("\{") == 1) 11 | assert(len("\"") == 1) 12 | } 13 | -------------------------------------------------------------------------------- /lib/lang/is.okt: -------------------------------------------------------------------------------- 1 | test "is for type check" { 2 | a = 123 3 | matches = 0 4 | 5 | if a is number { 6 | ++matches 7 | } 8 | 9 | if a is string { 10 | assert(false == true) 11 | } else { 12 | ++matches 13 | } 14 | 15 | assert(matches == 2) 16 | } 17 | 18 | test "compiler understands type" { 19 | x = any "foo" 20 | x = 12.3 21 | 22 | if x is number { 23 | x += 3.4 24 | } 25 | 26 | assert(x == 15.7) 27 | } 28 | -------------------------------------------------------------------------------- /lib/lang/len.okt: -------------------------------------------------------------------------------- 1 | test "len" { 2 | assert(len("") == 0) 3 | assert(len("hello") == 5) 4 | assert(len("😃") == 1) 5 | 6 | assert(len(``) == 0) 7 | assert(len(`hello`) == 5) 8 | assert(len(`😃`) == 4) 9 | 10 | assert(len([]bool []) == 0) 11 | assert(len([true]) == 1) 12 | assert(len([true, false]) == 2) 13 | 14 | assert(len([]string []) == 0) 15 | assert(len(["foo"]) == 1) 16 | assert(len(["foo", "bar"]) == 2) 17 | 18 | a = []char [] 19 | b = [1, 2, 3] 20 | assert(len(a) == 0) 21 | assert(len(b) == 3) 22 | } 23 | -------------------------------------------------------------------------------- /lib/lang/number.okt: -------------------------------------------------------------------------------- 1 | import "error" 2 | 3 | test "casting a character to a number" { 4 | assert(number 'a' == 97) 5 | assert(number '😃' == 128515) 6 | } 7 | 8 | test "casting a string to a number" { 9 | assert(number "123" == 123) 10 | 11 | assert(badNumberCast() raise error.Error("not a number: 123a")) 12 | } 13 | 14 | func badNumberCast() { 15 | number "123a" 16 | } 17 | -------------------------------------------------------------------------------- /lib/lang/string.okt: -------------------------------------------------------------------------------- 1 | test "string()" { 2 | s1 = string 'a' 3 | assert(s1 == "a") 4 | 5 | s2 = string 1.2300 6 | assert(s2 == "1.23") 7 | 8 | s3 = string `1.2300` 9 | assert(s3 == "1.2300") 10 | 11 | s4 = string true 12 | assert(s4 == "true") 13 | 14 | s5 = string false 15 | assert(s5 == "false") 16 | 17 | s6 = string s2[1] 18 | assert(s6 == ".") 19 | } 20 | -------------------------------------------------------------------------------- /lib/lang/try.okt: -------------------------------------------------------------------------------- 1 | import "error" 2 | 3 | test "try and catch" { 4 | raised = "" 5 | try { 6 | raise error.Error("uh oh") 7 | } on error.Error { 8 | raised = err.Error 9 | } 10 | 11 | assert(raised == "uh oh") 12 | } 13 | -------------------------------------------------------------------------------- /lib/lang/unary.okt: -------------------------------------------------------------------------------- 1 | test "unary minus" { 2 | c = 1.23 3 | assert(-c == -1.23) 4 | assert(-foo() == -234) 5 | } 6 | 7 | func foo() number { 8 | return 234 9 | } 10 | -------------------------------------------------------------------------------- /lib/log/log.okt: -------------------------------------------------------------------------------- 1 | test "Logger" { 2 | output = []string [] 3 | logger = Logger(func (level, message string) { 4 | ^output += ["{level} {message}"] 5 | }) 6 | logger.Debug("debug message") 7 | logger.Info("info message") 8 | logger.Warn("warning message") 9 | logger.Error("error message") 10 | logger.Fatal("fatal message") 11 | 12 | assert(output == [ 13 | "DEBUG debug message", 14 | "INFO info message", 15 | "WARN warning message", 16 | "ERROR error message", 17 | "FATAL fatal message" 18 | ]) 19 | } 20 | -------------------------------------------------------------------------------- /lib/log/package.md: -------------------------------------------------------------------------------- 1 | The `log` package contains the standard log levels and interfaces for logging. 2 | 3 | ### Using the Standard Logger 4 | 5 | ``` 6 | import "log" 7 | 8 | func main() { 9 | // Create a simple logger for stdout. 10 | l = log.Logger(log.Log) 11 | 12 | l.Info("ready") 13 | } 14 | ``` 15 | 16 | ### Implementing a Custom Logger 17 | 18 | ``` 19 | import "log" 20 | 21 | func main() { 22 | // A buffered logger that can be inspected. 23 | messages = []string [] 24 | logger = log.Logger(func (level, message string) { 25 | ^messages += ["{level} {message}"] 26 | }) 27 | 28 | l.Info("ready") 29 | 30 | // messages = ["INFO ready"] 31 | } 32 | ``` 33 | 34 | When creating a custom logger, be aware that `LogLevelFatal` should invoke 35 | `runtime.Exit(1)` as clients can expect that `Fatal()` will stop the execution 36 | of the program. One such case that you might not exist, however, is under 37 | specific controlled conditions like a unit test. 38 | -------------------------------------------------------------------------------- /lib/math/abs.ok: -------------------------------------------------------------------------------- 1 | // Abs returns the absolute (positive number). 2 | func Abs(x number) number { 3 | if x < 0 { 4 | return -x 5 | } 6 | 7 | return x 8 | } 9 | -------------------------------------------------------------------------------- /lib/math/abs.okt: -------------------------------------------------------------------------------- 1 | test "Abs" { 2 | assert(Abs(-2) == 2) 3 | assert(Abs(3.1) == 3.1) 4 | assert(Abs(0.00) == 0) 5 | } 6 | -------------------------------------------------------------------------------- /lib/math/constants.ok: -------------------------------------------------------------------------------- 1 | E = 2.71828182845904523536028747135266249775724709369995957496696763 // https://oeis.org/A001113 2 | Pi = 3.14159265358979323846264338327950288419716939937510582097494459 // https://oeis.org/A000796 3 | Phi = 1.61803398874989484820458683436563811772030917980576286213544862 // https://oeis.org/A001622 4 | 5 | Sqrt2 = 1.41421356237309504880168872420969807856967187537694807317667974 // https://oeis.org/A002193 6 | SqrtE = 1.64872127070012814684865078781416357165377610071014801157507931 // https://oeis.org/A019774 7 | SqrtPi = 1.77245385090551602729816748334114518279754945612238712821380779 // https://oeis.org/A002161 8 | SqrtPhi = 1.27201964951406896425242246173749149171560804184009624861664038 // https://oeis.org/A139339 9 | 10 | Ln2 = 0.693147180559945309417232121458176568075500134360255254120680009 // https://oeis.org/A002162 11 | Ln10 = 2.30258509299404568401799145468436420760110148862877297603332790 // https://oeis.org/A002392 12 | -------------------------------------------------------------------------------- /lib/math/constants.okt: -------------------------------------------------------------------------------- 1 | test "mathematical constants" { 2 | assert(E == 2.71828182845904523536028747135266249775724709369995957496696763) 3 | assert(Pi == 3.14159265358979323846264338327950288419716939937510582097494459) 4 | assert(Phi == 1.61803398874989484820458683436563811772030917980576286213544862) 5 | 6 | assert(Sqrt2 == 1.41421356237309504880168872420969807856967187537694807317667974) 7 | assert(SqrtE == 1.64872127070012814684865078781416357165377610071014801157507931) 8 | assert(SqrtPi == 1.77245385090551602729816748334114518279754945612238712821380779) 9 | assert(SqrtPhi == 1.27201964951406896425242246173749149171560804184009624861664038) 10 | 11 | assert(Ln2 == 0.693147180559945309417232121458176568075500134360255254120680009) 12 | assert(Ln10 == 2.30258509299404568401799145468436420760110148862877297603332790) 13 | } 14 | -------------------------------------------------------------------------------- /lib/math/log.ok: -------------------------------------------------------------------------------- 1 | // LogE is the natural logarithm (base e). 2 | func LogE(x number) number { 3 | return __log(x) 4 | } 5 | 6 | // Log10 is a logarithm with base 10. 7 | func Log10(x number) number { 8 | return __log(x) / __log(10) 9 | } 10 | -------------------------------------------------------------------------------- /lib/math/log.okt: -------------------------------------------------------------------------------- 1 | test "LogE" { 2 | assert(LogE(0.123) == -2.0955709236097195568) 3 | assert(LogE(12.34) == 2.5128460184772417554) 4 | } 5 | 6 | test "Log10" { 7 | assert(Log10(0.123) == -0.91009488856060206821) 8 | assert(Log10(12.34) == 1.0913151596972228773) 9 | } 10 | -------------------------------------------------------------------------------- /lib/math/package.md: -------------------------------------------------------------------------------- 1 | The `math` package contains mathematical functions. 2 | -------------------------------------------------------------------------------- /lib/math/powers.ok: -------------------------------------------------------------------------------- 1 | // Exp returns e to the power of x. 2 | func Exp(x number) number { 3 | return __pow(E, x) 4 | } 5 | 6 | // Pow returns base to power. 7 | func Pow(base, power number) number { 8 | return __pow(base, power) 9 | } 10 | 11 | // Sqrt returns the square root of x. 12 | func Sqrt(x number) number { 13 | return __pow(x, 0.5) 14 | } 15 | 16 | // Cbrt returns the cube root of x. 17 | func Cbrt(x number) number { 18 | return __pow(x, 1/3) 19 | } 20 | -------------------------------------------------------------------------------- /lib/math/powers.okt: -------------------------------------------------------------------------------- 1 | test "Exp" { 2 | assert(Exp(0) == 1) 3 | assert(Exp(1.23) == 3.4212295362896735738) 4 | } 5 | 6 | test "Pow" { 7 | assert(Pow(0, 1) == 0) 8 | assert(Pow(1.23, 4.56) == 2.5702023016193030109) 9 | } 10 | 11 | test "Sqrt" { 12 | assert(Sqrt(0) == 0) 13 | assert(Sqrt(12.34) == 3.5128336140500591606) 14 | } 15 | 16 | test "Cbrt" { 17 | assert(Cbrt(0) == 0) 18 | assert(Cbrt(12.34) == 2.3108498088312434878) 19 | } 20 | -------------------------------------------------------------------------------- /lib/math/rand.ok: -------------------------------------------------------------------------------- 1 | // Rand returns a random number between 0 and 1. 2 | func Rand() number { 3 | return __rand() 4 | } 5 | -------------------------------------------------------------------------------- /lib/math/rand.okt: -------------------------------------------------------------------------------- 1 | test "Rand" { 2 | assert(Rand() != Rand()) 3 | assert(Rand() != Rand()) 4 | assert(Rand() != Rand()) 5 | } 6 | -------------------------------------------------------------------------------- /lib/math/rounding.ok: -------------------------------------------------------------------------------- 1 | // Ceil will round x up to the nearest integer. 2 | func Ceil(x number) number { 3 | frac = x % 1 4 | if frac == 0 { 5 | return x 6 | } 7 | 8 | if x < 0 { 9 | return x - frac 10 | } 11 | 12 | return x + (1 - frac) 13 | } 14 | 15 | // Floor will round x down to the nearest integer. 16 | func Floor(x number) number { 17 | frac = x % 1 18 | if frac == 0 { 19 | return x 20 | } 21 | 22 | if x < 0 { 23 | return x - frac + 1 24 | } 25 | 26 | return x - frac 27 | } 28 | 29 | // Round will return a new number rounded to prec number of digits after the 30 | // decimal point. Prec must be at least 0. 31 | func Round(x, prec number) number { 32 | p = __pow(10, prec) 33 | y = x * p 34 | 35 | diff = y % 1 36 | if diff >= 0.5 { 37 | return (y + (1 - diff)) / p 38 | } 39 | 40 | return (y - diff) / p 41 | } 42 | -------------------------------------------------------------------------------- /lib/math/rounding.okt: -------------------------------------------------------------------------------- 1 | test "Ceil" { 2 | assert(Ceil(0) == 0) 3 | 4 | assert(Ceil(1.0) == 1) 5 | assert(Ceil(1.1) == 2) 6 | assert(Ceil(1.5) == 2) 7 | assert(Ceil(1.8) == 2) 8 | assert(Ceil(2) == 2) 9 | 10 | assert(Ceil(-1.0) == -1) 11 | assert(Ceil(-1.1) == -1) 12 | assert(Ceil(-1.5) == -1) 13 | assert(Ceil(-1.8) == -1) 14 | assert(Ceil(-2) == -2) 15 | } 16 | 17 | test "Floor" { 18 | assert(Floor(0) == 0) 19 | 20 | assert(Floor(1.0) == 1) 21 | assert(Floor(1.1) == 1) 22 | assert(Floor(1.5) == 1) 23 | assert(Floor(1.8) == 1) 24 | assert(Floor(2) == 2) 25 | 26 | assert(Floor(-1.0) == -1) 27 | assert(Floor(-1.1) == -2) 28 | assert(Floor(-1.5) == -2) 29 | assert(Floor(-1.8) == -2) 30 | assert(Floor(-2) == -2) 31 | } 32 | 33 | test "Round" { 34 | assert(Round(1.23, 3) == 1.23) 35 | assert(Round(1.23, 2) == 1.23) 36 | assert(Round(1.23, 1) == 1.2) 37 | assert(Round(1.23, 0) == 1) 38 | 39 | assert(Round(-1.23, 3) == -1.23) 40 | assert(Round(-1.23, 2) == -1.23) 41 | assert(Round(-1.23, 1) == -1.2) 42 | assert(Round(-1.23, 0) == -1) 43 | 44 | assert(Round(1.16, 1) == 1.2) 45 | assert(Round(1.15, 1) == 1.2) 46 | assert(Round(1.14, 1) == 1.1) 47 | } 48 | -------------------------------------------------------------------------------- /lib/os/filesystem.ok: -------------------------------------------------------------------------------- 1 | // Remove will delete (unlink) the file located at `path`. 2 | func Remove(path string) { 3 | __remove(path) 4 | } 5 | 6 | // Rename will move the file or directory located at `old` to `new`. 7 | func Rename(old, new string) { 8 | __rename(old, new) 9 | } 10 | 11 | // CreateDirectory will create a directory at `path`, creating any necessary 12 | // directories along the way. 13 | func CreateDirectory(path string) { 14 | __mkdir(path) 15 | } 16 | -------------------------------------------------------------------------------- /lib/os/filesystem.okt: -------------------------------------------------------------------------------- 1 | import "error" 2 | 3 | test "removing a file that does not exist" { 4 | assert(Remove("doesnotexist") raise error.Error) 5 | } 6 | 7 | test "removing a file" { 8 | path = TempPath() 9 | f = Open(path) 10 | f.Close() 11 | 12 | Remove(path) 13 | } 14 | 15 | test "renaming a file that does not exist" { 16 | assert(Rename("doesnotexist", "nowhere") raise error.Error) 17 | } 18 | 19 | test "renaming a file" { 20 | path = TempPath() 21 | f = Open(path) 22 | f.Close() 23 | 24 | Rename(path, "{path}0") 25 | Remove("{path}0") 26 | } 27 | 28 | test "creating a directory" { 29 | path = TempPath() 30 | CreateDirectory(path) 31 | info = Info(path) 32 | assert(info.IsDir == true) 33 | } 34 | -------------------------------------------------------------------------------- /lib/os/info.ok: -------------------------------------------------------------------------------- 1 | import "time" 2 | 3 | func FileInfo( 4 | Name string, // base name of the file 5 | Size number, // length in bytes for regular files; system-dependent for others 6 | Mode string, // file mode bits 7 | ModifiedTime time.Time, // modification time 8 | IsDir bool // easier than decoding from Mode 9 | ) FileInfo {} 10 | 11 | // Info returns the FileInfo for a path, or raises an error if the path does not 12 | // exist. 13 | func Info(path string) FileInfo { 14 | name, size, mode, modTimeYear, modTimeMonth, modTimeDay, modTimeHour, modTimeMinute, modTimeSecond, isDir = __info(path) 15 | modTime = time.Time(modTimeYear, modTimeMonth, modTimeDay, modTimeHour, modTimeMinute, modTimeSecond) 16 | 17 | return FileInfo(name, size, mode, modTime, isDir) 18 | } 19 | -------------------------------------------------------------------------------- /lib/os/info.okt: -------------------------------------------------------------------------------- 1 | import "error" 2 | 3 | test "Info on path that does not exist" { 4 | assert(Info("doesnotexist") raise error.Error) 5 | } 6 | 7 | test "Info on directory" { 8 | info = Info(".") 9 | assert(info.Name == ".") 10 | assert(info.Size > 0) 11 | assert(info.Mode[0] == 'd') 12 | assert(time.Before(info.ModifiedTime, time.Now()) == true) 13 | assert(info.IsDir == true) 14 | } 15 | 16 | test "Info on file" { 17 | info = Info("./Makefile") 18 | assert(info.Name == "Makefile") 19 | assert(info.Size > 0) 20 | assert(info.Mode[0] == '-') 21 | assert(time.Before(info.ModifiedTime, time.Now()) == true) 22 | assert(info.IsDir == false) 23 | } 24 | -------------------------------------------------------------------------------- /lib/os/package.md: -------------------------------------------------------------------------------- 1 | The `os` package contains operating and file system functions. 2 | 3 | ### Reading a File 4 | 5 | ``` 6 | import "os" 7 | 8 | func main() { 9 | f = os.Open("foo.txt") 10 | for { 11 | line = f.ReadLine() 12 | if line == "" { 13 | break 14 | } 15 | 16 | print(line) 17 | } 18 | 19 | f.Close() 20 | } 21 | ``` 22 | 23 | ### Writing to a File 24 | 25 | ``` 26 | import "os" 27 | 28 | func main() { 29 | f = Open("file.txt") 30 | f.WriteString("hello\n") 31 | f.WriteString("world\n") 32 | f.Close() 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /lib/os/temp.ok: -------------------------------------------------------------------------------- 1 | // TempPath returns an absolute path that is safe to create a new file. 2 | func TempPath() string { 3 | // TODO(elliot): "/tmp" is not always the right choice. Also, maybe we 4 | // should incorporate the date? 5 | return "/tmp/ok." + string (__rand() * 1000000000) 6 | } 7 | -------------------------------------------------------------------------------- /lib/os/temp.okt: -------------------------------------------------------------------------------- 1 | test "TempPath" { 2 | assert(TempPath() != TempPath()) 3 | } 4 | -------------------------------------------------------------------------------- /lib/reflect/call.ok: -------------------------------------------------------------------------------- 1 | // Call can be used to call a function variable without knowing the type. 2 | // 3 | // `fn` must be a callable function and args must have the correct number of 4 | // arguments for the function. The number of return values will depend on the 5 | // function being called. 6 | // 7 | // Example: 8 | // 9 | // ``` 10 | // fn = func(a, b number) number { 11 | // return a + b 12 | // } 13 | // 14 | // print(reflect.Call(fn, []any [1.2, 3.4])) 15 | // ``` 16 | func Call(fn any, args []any) []any { 17 | return __call(fn, args) 18 | } 19 | -------------------------------------------------------------------------------- /lib/reflect/call.okt: -------------------------------------------------------------------------------- 1 | func fn(a, b number) number { 2 | return a + b 3 | } 4 | 5 | test "Call" { 6 | assert(Call(fn, []any [1.2, 3.4]) == []any [4.6]) 7 | } 8 | -------------------------------------------------------------------------------- /lib/reflect/get.ok: -------------------------------------------------------------------------------- 1 | // Get performs one of the following (depending on the input type): 2 | // 3 | // - Arrays: `prop` must be an number that represents the index in the array. If 4 | // it is not a number or is out of bound then an error is raised. 5 | // 6 | // - Maps: `prop` must be a string that represents the key in the map. If prop 7 | // is not a string of the key does not exist in the map an error will be raised. 8 | // 9 | // - Objects: `prop` must be a string representing the property name. You may 10 | // only access public properties (starting with a capital letter). Trying to 11 | // access a property that does not exist or is not public will result in an 12 | // error. 13 | // 14 | // Any other type will always result in an error. 15 | func Get(obj, prop any) any { 16 | return __get(obj, prop) 17 | } 18 | -------------------------------------------------------------------------------- /lib/reflect/get.okt: -------------------------------------------------------------------------------- 1 | func SomeObject() SomeObject { 2 | Foo = 1.23 3 | 4 | func bar() {} 5 | 6 | func Baz(a number) string { 7 | return string a 8 | } 9 | } 10 | 11 | test "Get" { 12 | // arrays 13 | a = ["foo", "bar", "baz"] 14 | assert(Get(a, 1) == "bar") 15 | 16 | // maps 17 | m = {"foo": "bar", "baz": "qux"} 18 | assert(Get(m, "baz") == "qux") 19 | 20 | // objects 21 | o = SomeObject() 22 | assert(Get(o, "Foo") == 1.23) 23 | 24 | // TODO(elliot): Test for failure conditions. 25 | } 26 | -------------------------------------------------------------------------------- /lib/reflect/interface.ok: -------------------------------------------------------------------------------- 1 | // Interface returns the canonical interface as a string. 2 | // 3 | // Examples: 4 | // 5 | // ``` 6 | // { } 7 | // { Name string } 8 | // { Greet(string) bool; Name string } 9 | // ``` 10 | func Interface(value any) string { 11 | return __interface(value) 12 | } 13 | -------------------------------------------------------------------------------- /lib/reflect/interface.okt: -------------------------------------------------------------------------------- 1 | func Person(Name string, age number) Person { 2 | func Greet(nickname string) { 3 | } 4 | 5 | foo = func() { 6 | } 7 | 8 | GetAge = func() number { 9 | return ^age 10 | } 11 | } 12 | 13 | test "Interface" { 14 | bob = Person("Bob", 30) 15 | assert(Interface(bob) == "\{ GetAge() number; Greet(string); Name string }") 16 | } 17 | -------------------------------------------------------------------------------- /lib/reflect/kind.ok: -------------------------------------------------------------------------------- 1 | import "strings" 2 | 3 | // Kind returns the runtime type of a value. One of; "bool", "char", "data", 4 | // "number", "string", "array", "map" or "func". 5 | func Kind(value any) string { 6 | type = Type(value) 7 | 8 | switch { 9 | case strings.HasPrefix(type, "[]") { 10 | return "array" 11 | } 12 | 13 | case strings.HasPrefix(type, "\{}") { 14 | return "map" 15 | } 16 | 17 | case strings.HasPrefix(type, "func(") { 18 | return "func" 19 | } 20 | } 21 | 22 | return type 23 | } 24 | -------------------------------------------------------------------------------- /lib/reflect/kind.okt: -------------------------------------------------------------------------------- 1 | test "Kind" { 2 | assert(Kind(true) == "bool") 3 | assert(Kind('a') == "char") 4 | assert(Kind(``) == "data") 5 | assert(Kind(12.3) == "number") 6 | assert(Kind("") == "string") 7 | 8 | assert(Kind([]any []) == "array") 9 | assert(Kind([1, 2, 3]) == "array") 10 | 11 | assert(Kind({}any {}) == "map") 12 | assert(Kind({"a": 1, "b": 2}) == "map") 13 | 14 | assert(Kind(func() {}) == "func") 15 | } 16 | -------------------------------------------------------------------------------- /lib/reflect/len.ok: -------------------------------------------------------------------------------- 1 | // Len returns the number of elements in an array or map. If the value is not an 2 | // array or map then an error is raised. 3 | func Len(value any) number { 4 | return __len(value) 5 | } 6 | -------------------------------------------------------------------------------- /lib/reflect/len.okt: -------------------------------------------------------------------------------- 1 | test "Len" { 2 | // arrays 3 | a = ["foo", "bar", "baz"] 4 | assert(Len(a) == 3) 5 | 6 | // maps 7 | m = {"foo": "bar", "baz": "qux"} 8 | assert(Len(m) == 2) 9 | 10 | // TODO(elliot): Test for failure conditions. 11 | } 12 | -------------------------------------------------------------------------------- /lib/reflect/package.md: -------------------------------------------------------------------------------- 1 | The `reflect` package contains runtime checking and manipulating of types and 2 | values. 3 | 4 | ### Objects 5 | 6 | ``` 7 | import "reflect" 8 | 9 | func MyObject(Foo, Bar number) MyObject {} 10 | 11 | func main() { 12 | obj = MyObject(123, 456) 13 | 14 | reflect.Set(obj, "Bar", 789) 15 | reflect.Get(obj, "Bar") // 789 16 | 17 | reflect.Properties(obj) // ["Bar", "Foo"] 18 | } 19 | ``` 20 | 21 | ### Iterating Arrays or Maps 22 | 23 | `Properties` works the same way with arrays or maps, but the key type will be a 24 | `number` for array and `string` for a map. 25 | 26 | ``` 27 | import "reflect" 28 | 29 | func iterateMap(x any) { 30 | for key in reflect.Properties(x) { 31 | print("{key} = {reflect.Get(x, key)}") 32 | } 33 | } 34 | ``` -------------------------------------------------------------------------------- /lib/reflect/props.ok: -------------------------------------------------------------------------------- 1 | // Properties returns the public properties of an object, or the keys in a map. 2 | // If the input is not an object or map, an error is raised. The values returned 3 | // will be sorted in either case. 4 | func Properties(obj any) []string { 5 | return __props(obj) 6 | } 7 | -------------------------------------------------------------------------------- /lib/reflect/props.okt: -------------------------------------------------------------------------------- 1 | test "Properties on an object" { 2 | o = SomeObject() 3 | assert(Properties(o) == ["Baz", "Foo"]) 4 | 5 | // TODO(elliot): Test for failure conditions. 6 | } 7 | 8 | test "Properties on a map" { 9 | assert(Properties({}number {}) == []string []) 10 | assert(Properties({}string {"c": "foo", "a": "bar"}) == ["a", "c"]) 11 | } 12 | -------------------------------------------------------------------------------- /lib/reflect/set.ok: -------------------------------------------------------------------------------- 1 | // Set performs one of the following (depending on the input type): 2 | // 3 | // - Arrays: `prop` must be an number that represents the index in the array. If 4 | // it is not a number or is out of bound then an error is raised. 5 | // 6 | // - Maps: `prop` must be a string that represents the key in the map. If prop 7 | // is not a string an error will be raised. However, if the key does not exist 8 | // it will be created. If the key already exists its value will be replaced. 9 | // 10 | // - Objects: `prop` must be a string representing the property name. You may 11 | // only access public properties (starting with a capital letter). Trying to 12 | // access a property that does not exist or is not public will result in an 13 | // error. 14 | // 15 | // Any other type will always result in an error. 16 | func Set(obj, prop, value any) any { 17 | return __set(obj, prop, value) 18 | } 19 | -------------------------------------------------------------------------------- /lib/reflect/set.okt: -------------------------------------------------------------------------------- 1 | test "Set" { 2 | // arrays 3 | a = []any ["foo", "bar", "baz"] 4 | Set(a, 1, "qux") 5 | assert(a == []any ["foo", "qux", "baz"]) 6 | 7 | // maps 8 | m = {}any {"foo": "bar", "baz": "qux"} 9 | Set(m, "baz", 1.23) 10 | assert(m == {}any {"foo": "bar", "baz": 1.23}) 11 | 12 | Set(m, "qux", "hello") 13 | assert(m == {}any {"foo": "bar", "baz": 1.23, "qux": "hello"}) 14 | 15 | // objects 16 | o = SomeObject() 17 | Set(o, "Foo", 23) 18 | assert(o.Foo == 23) 19 | 20 | // TODO(elliot): Test for failure conditions. 21 | } 22 | -------------------------------------------------------------------------------- /lib/reflect/type.ok: -------------------------------------------------------------------------------- 1 | // Type returns the runtime type of a value, some examples would be: 2 | // 3 | // ``` 4 | // reflect.Type(3) == "number" 5 | // reflect.Type(["foo", "bar"]) == "[]string" 6 | // reflect.Type(func hello(ok bool) {}) == "func(bool)" 7 | // ``` 8 | func Type(value any) string { 9 | return __type(value) 10 | } 11 | -------------------------------------------------------------------------------- /lib/runtime/env.ok: -------------------------------------------------------------------------------- 1 | // Env returns the value for a environment variable. If the variable does not 2 | // exist an empty string is returned. To check if an environment variable is set 3 | // but empty you should use LookupEnv. 4 | func Env(name string) string { 5 | value, _ = LookupEnv(name) 6 | 7 | return value 8 | } 9 | 10 | // LookupEnv return the value and the existence of a environment variable. 11 | // LookupEnv is only required to determine cases where the environment variable 12 | // is set but empty. See Env. 13 | func LookupEnv(name string) (string, bool) { 14 | return __env_get(name) 15 | } 16 | 17 | // SetEnv is used to set or replace or existing environment variable. Setting an 18 | // environment variable to an empty value is not the same as unsetting it. See 19 | // UnsetEnv. 20 | func SetEnv(name, value string) { 21 | return __env_set(name, value) 22 | } 23 | 24 | // UnsetEnv will remove an environment variable and return if the environment 25 | // variable previously existed. To check for the existence first, use LookupEnv. 26 | func UnsetEnv(name string) { 27 | __env_unset(name) 28 | } 29 | -------------------------------------------------------------------------------- /lib/runtime/env.okt: -------------------------------------------------------------------------------- 1 | test "Env is empty for nonexistent env var" { 2 | assert(Env("DOESNT_EXIST") == "") 3 | } 4 | 5 | test "Env exists" { 6 | assert(Env("HOME") != "") 7 | } 8 | 9 | test "LookupEnv does not exist" { 10 | value, exists = LookupEnv("DOESNOTEXIST") 11 | assert(exists == false) 12 | assert(value == "") 13 | } 14 | 15 | test "LookupEnv does exist" { 16 | value, exists = LookupEnv("HOME") 17 | assert(exists == true) 18 | assert(value != "") 19 | } 20 | 21 | test "SetEnv" { 22 | SetEnv("FOO", "bar") 23 | assert(Env("FOO") == "bar") 24 | } 25 | 26 | test "SetEnv replace" { 27 | SetEnv("FOO", "bar") 28 | SetEnv("FOO", "baz") 29 | assert(Env("FOO") == "baz") 30 | } 31 | 32 | test "UnsetEnv does not exist" { 33 | UnsetEnv("FOO") 34 | 35 | value, exists = LookupEnv("FOO") 36 | assert(exists == false) 37 | assert(value == "") 38 | } 39 | 40 | test "UnsetEnv exists" { 41 | SetEnv("FOO", "bar") 42 | UnsetEnv("FOO") 43 | 44 | value, exists = LookupEnv("FOO") 45 | assert(exists == false) 46 | assert(value == "") 47 | } 48 | -------------------------------------------------------------------------------- /lib/runtime/exit.ok: -------------------------------------------------------------------------------- 1 | // Exit will kill the current process and return the `status` code to the parent 2 | // process or system. 3 | func Exit(status number) { 4 | __exit(status) 5 | } 6 | -------------------------------------------------------------------------------- /lib/runtime/package.md: -------------------------------------------------------------------------------- 1 | The `runtime` package contains functionality for the current process and 2 | runtime environment. 3 | 4 | ### Environment Variables 5 | 6 | ``` 7 | import "runtime" 8 | 9 | func main() { 10 | print("$HOME = {runtime.Env("HOME")}") 11 | } 12 | ``` 13 | 14 | ### Stack Traces 15 | 16 | ``` 17 | import "runtime" 18 | 19 | func main() { 20 | print(runtime.Stack().String()) 21 | } 22 | ``` 23 | -------------------------------------------------------------------------------- /lib/runtime/stack.ok: -------------------------------------------------------------------------------- 1 | import "strings" 2 | 3 | func StackElement( 4 | File string, 5 | LineNumber, LineOffset number, 6 | FunctionName string 7 | ) StackElement {} 8 | 9 | // Stack returns the current stack in reverse order so that the deepest call 10 | // will be at the top. 11 | func Stack() StackTrace { 12 | elements = strings.Split(__stack(), "\n") 13 | stack = []StackElement [] 14 | for element in elements { 15 | parts = strings.Split(element, "|") 16 | locationParts = strings.Split(parts[0], ":") 17 | stack += [StackElement( 18 | locationParts[0], 19 | number locationParts[1], 20 | number locationParts[2], 21 | parts[1])] 22 | } 23 | 24 | return StackTrace(stack) 25 | } 26 | 27 | func StackTrace(Elements []StackElement) StackTrace { 28 | func String() string { 29 | s = "" 30 | for i = 1; element in ^Elements; ++i { 31 | s += "{i} {element.FunctionName} {element.File}:{element.LineNumber}\n" 32 | } 33 | 34 | // Remove the final newline. 35 | return strings.TrimRight(s, "\n") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/runtime/stack.okt: -------------------------------------------------------------------------------- 1 | import "strings" 2 | 3 | func foo() StackTrace { 4 | return Bar() 5 | } 6 | 7 | func Bar() StackTrace { 8 | return Stack() 9 | } 10 | 11 | test "StackTrace.String" { 12 | stack = foo() 13 | s = stack.String() 14 | 15 | assert(strings.Contains(s, "3 Bar ") == true) 16 | assert(strings.Contains(s, "2 foo ") == true) 17 | assert(strings.Contains(s, "1 test \"StackTrace.String\" ") == true) 18 | 19 | assert(strings.Contains(s, "lib/runtime/stack.okt:12") == true) 20 | assert(strings.Contains(s, "lib/runtime/stack.okt:4") == true) 21 | assert(strings.Contains(s, "lib/runtime/stack.okt:8") == true) 22 | } 23 | -------------------------------------------------------------------------------- /lib/strings/case.ok: -------------------------------------------------------------------------------- 1 | // ToLower returns a lower case version of s. 2 | // 3 | // TODO(elliot): This only works for ASCII characters. 4 | func ToLower(s string) string { 5 | result = "" 6 | for c in s { 7 | n = number c 8 | if n >= number 'A' and n <= number 'Z' { 9 | result += string char (n + 32) 10 | } else { 11 | result += string c 12 | } 13 | } 14 | 15 | return result 16 | } 17 | 18 | // ToUpper returns a upper case version of s. 19 | // 20 | // TODO(elliot): This only works for ASCII characters. 21 | func ToUpper(s string) string { 22 | result = "" 23 | for c in s { 24 | n = number c 25 | if n >= number 'a' and n <= number 'z' { 26 | result += string char (n - 32) 27 | } else { 28 | result += string c 29 | } 30 | } 31 | 32 | return result 33 | } 34 | -------------------------------------------------------------------------------- /lib/strings/case.okt: -------------------------------------------------------------------------------- 1 | test "ToLower" { 2 | assert(ToLower("foo") == "foo") 3 | assert(ToLower("Foo Bar") == "foo bar") 4 | } 5 | 6 | test "ToUpper" { 7 | assert(ToUpper("foo") == "FOO") 8 | assert(ToUpper("Foo Bar") == "FOO BAR") 9 | } 10 | -------------------------------------------------------------------------------- /lib/strings/contains.ok: -------------------------------------------------------------------------------- 1 | // Contains checks whether substr exists in s. 2 | func Contains(s, substr string) bool { 3 | return Index(s, substr) != -1 4 | } 5 | 6 | // HasPrefix will return true if s starts with prefix. 7 | func HasPrefix(s, prefix string) bool { 8 | if len(s) < len(prefix) { 9 | return false 10 | } 11 | 12 | for i = 0; i < len(prefix); ++i { 13 | if s[i] != prefix[i] { 14 | return false 15 | } 16 | } 17 | 18 | return true 19 | } 20 | 21 | // HasSuffix will return true if s ends with suffix. 22 | func HasSuffix(s, suffix string) bool { 23 | if len(s) < len(suffix) { 24 | return false 25 | } 26 | 27 | j = len(s) - 1 28 | for i = len(suffix) - 1; i >= 0; --i { 29 | if s[j] != suffix[i] { 30 | return false 31 | } 32 | 33 | --j 34 | } 35 | 36 | return true 37 | } 38 | -------------------------------------------------------------------------------- /lib/strings/contains.okt: -------------------------------------------------------------------------------- 1 | test "Contains" { 2 | assert(Contains("foobar", "foo") == true) 3 | assert(Contains("foobar", "o") == true) 4 | assert(Contains("foobar", "b") == true) 5 | assert(Contains("foobar", "B") == false) 6 | assert(Contains("foobar", "bar") == true) 7 | assert(Contains("foobar", "bard") == false) 8 | } 9 | 10 | test "HasPrefix" { 11 | assert(HasPrefix("foobar", "foo") == true) 12 | assert(HasPrefix("foobar", "Foo") == false) 13 | assert(HasPrefix("foo", "foob") == false) 14 | assert(HasPrefix("foobar", "") == true) 15 | assert(HasPrefix("", "") == true) 16 | } 17 | 18 | test "HasSuffix" { 19 | assert(HasSuffix("foobar", "bar") == true) 20 | assert(HasSuffix("foobar", "Bar") == false) 21 | assert(HasSuffix("foo", "foob") == false) 22 | assert(HasSuffix("foobar", "") == true) 23 | assert(HasSuffix("", "") == true) 24 | } 25 | -------------------------------------------------------------------------------- /lib/strings/join.ok: -------------------------------------------------------------------------------- 1 | // Join returns a string containing all elements joined with glue. The glue will 2 | // only appear between elements, even if those elements are empty strings 3 | // themselves. Glue is allowed to be empty. 4 | func Join(strings []string, glue string) string { 5 | result = "" 6 | for s, i in strings { 7 | if i > 0 { 8 | result += glue 9 | } 10 | 11 | result += s 12 | } 13 | 14 | return result 15 | } 16 | -------------------------------------------------------------------------------- /lib/strings/join.okt: -------------------------------------------------------------------------------- 1 | test "Join" { 2 | empty = []string [] 3 | assert(Join(empty, "-") == "") 4 | 5 | assert(Join(["hi"], "-") == "hi") 6 | assert(Join(["hi", "there"], "-") == "hi-there") 7 | assert(Join(["hi", "", "there"], "-") == "hi--there") 8 | assert(Join(["hi", "there"], "") == "hithere") 9 | } 10 | -------------------------------------------------------------------------------- /lib/strings/package.md: -------------------------------------------------------------------------------- 1 | The `strings` package contains common string checking and manipulation. 2 | -------------------------------------------------------------------------------- /lib/strings/pad.ok: -------------------------------------------------------------------------------- 1 | // PadLeft will return a string with a length of at least `toLen` in length. 2 | // `s` will not be truncated if it's longer than `toLen`. 3 | // 4 | // If `pad` is more than one character, the whole string is repeated, except if 5 | // `pad` does not entirely fit, then `pad` will be truncated on the last 6 | // occurrence. 7 | // 8 | // If `pad` is empty, the original string will always be returned. 9 | func PadLeft(s, pad string, toLen number) string { 10 | if len(s) >= toLen or pad == "" { 11 | return s 12 | } 13 | 14 | return createPad(pad, toLen - len(s)) + s 15 | } 16 | 17 | // PadRight follows all the same rules as PadLeft, but will place padding (if 18 | // any) on the right side of the string. 19 | func PadRight(s, pad string, toLen number) string { 20 | if len(s) >= toLen or pad == "" { 21 | return s 22 | } 23 | 24 | return s + createPad(pad, toLen - len(s)) 25 | } 26 | 27 | func createPad(pad string, toLen number) string { 28 | return Substr(Repeat(pad, toLen / len(pad)), 0, toLen) 29 | } 30 | -------------------------------------------------------------------------------- /lib/strings/pad.okt: -------------------------------------------------------------------------------- 1 | test "PadLeft" { 2 | // toLen <= s 3 | assert(PadLeft("abcdef", "0123", 0) == "abcdef") 4 | assert(PadLeft("abcdef", "0123", 4) == "abcdef") 5 | assert(PadLeft("abcdef", "0123", 6) == "abcdef") 6 | 7 | // normal padding 8 | assert(PadLeft("abcdef", "0", 10) == "0000abcdef") 9 | assert(PadLeft("abcdef", "01", 10) == "0101abcdef") 10 | assert(PadLeft("abcdef", "012", 10) == "0120abcdef") 11 | 12 | // empty pad 13 | assert(PadLeft("abcdef", "", 0) == "abcdef") 14 | assert(PadLeft("abcdef", "", 6) == "abcdef") 15 | assert(PadLeft("abcdef", "", 10) == "abcdef") 16 | } 17 | 18 | test "PadRight" { 19 | // toLen <= s 20 | assert(PadRight("abcdef", "0123", 0) == "abcdef") 21 | assert(PadRight("abcdef", "0123", 4) == "abcdef") 22 | assert(PadRight("abcdef", "0123", 6) == "abcdef") 23 | 24 | // normal padding 25 | assert(PadRight("abcdef", "0", 10) == "abcdef0000") 26 | assert(PadRight("abcdef", "01", 10) == "abcdef0101") 27 | assert(PadRight("abcdef", "012", 10) == "abcdef0120") 28 | 29 | // empty pad 30 | assert(PadRight("abcdef", "", 0) == "abcdef") 31 | assert(PadRight("abcdef", "", 6) == "abcdef") 32 | assert(PadRight("abcdef", "", 10) == "abcdef") 33 | } 34 | -------------------------------------------------------------------------------- /lib/strings/repeat.ok: -------------------------------------------------------------------------------- 1 | // Repeat returns str repeated times. Any values for times that is 0 or below 2 | // will result in an empty string. 3 | func Repeat(str string, times number) string { 4 | result = "" 5 | for i = 0; i < times; ++i { 6 | result += str 7 | } 8 | 9 | return result 10 | } 11 | -------------------------------------------------------------------------------- /lib/strings/repeat.okt: -------------------------------------------------------------------------------- 1 | test "Repeat" { 2 | assert(Repeat("", -2) == "") 3 | assert(Repeat("", 0) == "") 4 | assert(Repeat("", 10) == "") 5 | 6 | assert(Repeat("foo", -1) == "") 7 | assert(Repeat("foo", 0) == "") 8 | assert(Repeat("foo", 1) == "foo") 9 | assert(Repeat("foo", 2) == "foofoo") 10 | assert(Repeat("foo", 3) == "foofoofoo") 11 | } 12 | -------------------------------------------------------------------------------- /lib/strings/replace.ok: -------------------------------------------------------------------------------- 1 | // ReplaceAll return a string with each of find substituted with replace. 2 | // 3 | // If `find` is empty, then `replace` will be inserted between every character. 4 | // It will not be insert before the first character or after the last. 5 | func ReplaceAll(s, find, replace string) string { 6 | return Join(Split(s, find), replace) 7 | } 8 | -------------------------------------------------------------------------------- /lib/strings/replace.okt: -------------------------------------------------------------------------------- 1 | test "ReplaceAll" { 2 | assert(ReplaceAll("", "", "") == "") 3 | assert(ReplaceAll("foo bar", "", "") == "foo bar") 4 | assert(ReplaceAll("foo bar", "", "!") == "f!o!o! !b!a!r") 5 | 6 | assert(ReplaceAll("foo bar foo bar", "o", "M") == "fMM bar fMM bar") 7 | assert(ReplaceAll("foo bar foo bar", "oo", "A") == "fA bar fA bar") 8 | 9 | assert(ReplaceAll("foo bar foo bar", "f", "F") == "Foo bar Foo bar") 10 | assert(ReplaceAll("foo bar foo bar", "r", "R") == "foo baR foo baR") 11 | 12 | assert(ReplaceAll("foo bar foo bar", "rab", "!") == "foo bar foo bar") 13 | } 14 | -------------------------------------------------------------------------------- /lib/strings/reverse.ok: -------------------------------------------------------------------------------- 1 | // Reverse creates a string with all characters in the opposite order. 2 | func Reverse(s string) string { 3 | result = "" 4 | for i = len(s) - 1; i >= 0; --i { 5 | result += string(s[i]) 6 | } 7 | 8 | return result 9 | } 10 | -------------------------------------------------------------------------------- /lib/strings/reverse.okt: -------------------------------------------------------------------------------- 1 | test "Reverse" { 2 | assert(Reverse("") == "") 3 | assert(Reverse("foobar") == "raboof") 4 | } 5 | -------------------------------------------------------------------------------- /lib/strings/split.ok: -------------------------------------------------------------------------------- 1 | // Split will always returns one or more elements. If the glue is empty the 2 | // string will be split into characters. 3 | // 4 | // TODO(elliot): This is a horribly inefficient algorithm. This was very early 5 | // on in the language when there we're barely any features, please clean this 6 | // up if you see it. 7 | func Split(s, delimiter string) []string { 8 | elements = []string [] 9 | 10 | if delimiter == "" { 11 | // TODO(elliot): In the future this should be possible by casting s to 12 | // []char. 13 | for i = 0; i < len(s); ++i { 14 | elements += [string s[i]] 15 | } 16 | } else { 17 | element = "" 18 | for i = 0; i < len(s); ++i { 19 | if IndexAfter(s, delimiter, i - 1) - i == 0 { 20 | elements += [element] 21 | element = "" 22 | i += len(delimiter) - 1 23 | } else { 24 | // TODO(elliot): We shouldn't need to have this extra cast here. 25 | element += string s[i] 26 | } 27 | } 28 | 29 | elements += [element] 30 | } 31 | 32 | return elements 33 | } 34 | -------------------------------------------------------------------------------- /lib/strings/split.okt: -------------------------------------------------------------------------------- 1 | test "Split" { 2 | assert(Split("foo", "") == ["f", "o", "o"]) 3 | assert(Split("foo-bar-baz", "-") == ["foo", "bar", "baz"]) 4 | assert(Split("-bar-", "-") == ["", "bar", ""]) 5 | assert(Split("foo!@bar!@baz", "!@") == ["foo", "bar", "baz"]) 6 | } 7 | -------------------------------------------------------------------------------- /lib/strings/substr.ok: -------------------------------------------------------------------------------- 1 | // Substr returns a portion of the string. The `fromIndex` and `toIndex` must be 2 | // within the bounds of the string. 3 | func Substr(s string, fromIndex, toIndex number) string { 4 | r = "" 5 | for i = fromIndex; i < toIndex; ++i { 6 | r += string(s[i]) 7 | } 8 | 9 | return r 10 | } 11 | -------------------------------------------------------------------------------- /lib/strings/substr.okt: -------------------------------------------------------------------------------- 1 | test "Substr" { 2 | assert(Substr("abcdef", 0, 3) == "abc") 3 | assert(Substr("abcdef", 0, 6) == "abcdef") 4 | assert(Substr("abcdef", 1, 3) == "bc") 5 | } 6 | -------------------------------------------------------------------------------- /lib/time/add.ok: -------------------------------------------------------------------------------- 1 | // Add returns a new time after applying a duration. You may use a negative 2 | // duration to subtract. 3 | func Add(t Time, duration Duration) Time { 4 | return FromUnix(Unix(t) + duration.Seconds()) 5 | } 6 | -------------------------------------------------------------------------------- /lib/time/add.okt: -------------------------------------------------------------------------------- 1 | test "Add" { 2 | t1 = FromUnix(123456789.123) 3 | assert(t1.String() == "1973-11-29 21:33:09.123") 4 | 5 | d = Duration(2 * Hour + 50 * Millisecond) 6 | assert(Add(t1, d).String() == "1973-11-29 23:33:09.173") 7 | } 8 | -------------------------------------------------------------------------------- /lib/time/compare.ok: -------------------------------------------------------------------------------- 1 | // Sub returns the duration between two time. The result will be negative if `b` 2 | // is before `a`, positive if `b` is after `a` and `0` if the two times are 3 | // equal. 4 | func Sub(a, b Time) Duration { 5 | return Duration(Unix(a) - Unix(b)) 6 | } 7 | 8 | // Equal returns true only if both instances represent the exact same time 9 | // (including fractional seconds). 10 | func Equal(a, b Time) bool { 11 | duration = Sub(a, b) 12 | 13 | return duration.Seconds() == 0 14 | } 15 | 16 | // Before returns true if `a` is before `b`. 17 | func Before(a, b Time) bool { 18 | duration = Sub(a, b) 19 | 20 | return duration.Seconds() < 0 21 | } 22 | 23 | // After returns true if `a` is after `b`. 24 | func After(a, b Time) bool { 25 | duration = Sub(a, b) 26 | 27 | return duration.Seconds() > 0 28 | } 29 | -------------------------------------------------------------------------------- /lib/time/compare.okt: -------------------------------------------------------------------------------- 1 | test "Sub" { 2 | t1 = FromUnix(123456789.123) 3 | assert(t1.String() == "1973-11-29 21:33:09.123") 4 | 5 | t2 = FromUnix(123123123.456) 6 | assert(t2.String() == "1973-11-26 00:52:03.456") 7 | 8 | duration = Sub(t1, t2) 9 | assert(duration.Seconds() == 333665.667) 10 | assert(duration.String() == "92h41m5.667s") 11 | } 12 | 13 | test "Equal" { 14 | t1 = FromUnix(123456789.123) 15 | t2 = FromUnix(123123123.456) 16 | assert(Equal(t1, t2) == false) 17 | assert(Equal(t1, t1) == true) 18 | } 19 | 20 | test "Before" { 21 | t1 = FromUnix(123456789.123) 22 | t2 = FromUnix(123123123.456) 23 | assert(Before(t1, t2) == false) 24 | assert(Before(t2, t1) == true) 25 | assert(Before(t1, t1) == false) 26 | } 27 | 28 | test "After" { 29 | t1 = FromUnix(123456789.123) 30 | t2 = FromUnix(123123123.456) 31 | assert(After(t1, t2) == true) 32 | assert(After(t2, t1) == false) 33 | assert(After(t1, t1) == false) 34 | } 35 | -------------------------------------------------------------------------------- /lib/time/duration.okt: -------------------------------------------------------------------------------- 1 | test "duration constants" { 2 | assert(Nanosecond == 0.000000001) 3 | assert(Microsecond == 0.000001) 4 | assert(Millisecond == 0.001) 5 | assert(Second == 1) 6 | assert(Minute == 60) 7 | assert(Hour == 3600) 8 | } 9 | 10 | test "Duration" { 11 | d = Duration(2 * Hour + 50 * Millisecond) 12 | assert(d.Nanoseconds() == 7200050000000) 13 | assert(d.Microseconds() == 7200050000) 14 | assert(d.Milliseconds() == 7200050) 15 | assert(d.Seconds() == 7200.05) 16 | assert(d.Minutes() == 120.00083333333333333) 17 | assert(d.Hours() == 2.0000138888888888889) 18 | } 19 | 20 | test "Duration.String" { 21 | assert(Duration(0.5 * Second).String() == "0.5s") 22 | assert(Duration(60 * Second).String() == "1m") 23 | assert(Duration(172.01 * Second).String() == "2m52.01s") 24 | assert(Duration(Hour).String() == "1h") 25 | assert(Duration(Hour + 3 * Second).String() == "1h3s") 26 | assert(Duration(172.01 * Minute).String() == "2h52m0.6s") 27 | } 28 | -------------------------------------------------------------------------------- /lib/time/package.md: -------------------------------------------------------------------------------- 1 | The `time` package contains time and date functions. 2 | 3 | ### Current Time 4 | 5 | ``` 6 | import "time" 7 | 8 | func main() { 9 | // eg. "2020-08-09 01:46:40.123" 10 | print(time.Now().String()) 11 | } 12 | ``` 13 | -------------------------------------------------------------------------------- /lib/time/sleep.ok: -------------------------------------------------------------------------------- 1 | // Sleep will pause the execution for a specific duration of time. 2 | func Sleep(duration Duration) { 3 | __sleep(duration.Seconds()) 4 | } 5 | -------------------------------------------------------------------------------- /lib/time/sleep.okt: -------------------------------------------------------------------------------- 1 | test "Sleep" { 2 | duration = Duration(10 * Millisecond) 3 | 4 | startTime = Now() 5 | Sleep(duration) 6 | 7 | assert(duration.Seconds() == 0.01) 8 | assert(Unix(Now()) - Unix(startTime) >= duration.Seconds()) 9 | } 10 | -------------------------------------------------------------------------------- /lib/time/time.ok: -------------------------------------------------------------------------------- 1 | January = 1 2 | February = 2 3 | March = 3 4 | April = 4 5 | May = 5 6 | June = 6 7 | July = 7 8 | August = 8 9 | September = 9 10 | October = 10 11 | November = 11 12 | December = 12 13 | 14 | // A Time represents a single point in time. The Second may be fractional. 15 | func Time(Year, Month, Day, Hour, Minute, Second number) Time { 16 | func String() string { 17 | // TODO(elliot): This is a known bug with how out-of-scope variables are 18 | // currently handled. See the todo in compiler/expr.go for 19 | // ast.Indentifier. We need to copy the values into this scope as a 20 | // work-around for now. 21 | month = ^Month 22 | day = ^Day 23 | hour = ^Hour 24 | minute = ^Minute 25 | second = ^Second 26 | 27 | return "{^Year}-{zeroPad(month)}-{zeroPad(day)} {zeroPad(hour)}:{zeroPad(minute)}:{zeroPad(second)}" 28 | } 29 | } 30 | 31 | // Now returns the current time. 32 | func Now() Time { 33 | year, month, day, hour, minute, second = __now() 34 | 35 | return Time(year, month, day, hour, minute, second) 36 | } 37 | -------------------------------------------------------------------------------- /lib/time/unix.ok: -------------------------------------------------------------------------------- 1 | // Unix returns the unix timestamp, the number of seconds elapsed since January 2 | // 1, 1970 UTC. The value returned may be fractional. 3 | func Unix(t Time) number { 4 | return __unix(t) 5 | } 6 | 7 | // FromUnix performs the opposite operation as Unix. It receives the number of 8 | // seconds elapsed since January 1, 1970 UTC and returns the Time. The input 9 | // seconds may be fractional. 10 | func FromUnix(seconds number) Time { 11 | year, month, day, hour, minute, second = __fromunix(seconds) 12 | 13 | return Time(year, month, day, hour, minute, second) 14 | } 15 | -------------------------------------------------------------------------------- /lib/time/unix.okt: -------------------------------------------------------------------------------- 1 | test "Unix" { 2 | assert(Unix(Time(2001, September, 9, 1, 46, 40.123)) == 1000000000.123) 3 | } 4 | 5 | test "FromUnix" { 6 | expected = Time(2001, September, 9, 1, 46, 40.123) 7 | actual = FromUnix(1000000000.123) 8 | assert(expected.String() == actual.String()) 9 | } 10 | -------------------------------------------------------------------------------- /lib/time/util.ok: -------------------------------------------------------------------------------- 1 | func zeroPad(n number) string { 2 | if n < 10 { 3 | return "0" + string n 4 | } 5 | 6 | return string n 7 | } 8 | -------------------------------------------------------------------------------- /lib/unicode/to.ok: -------------------------------------------------------------------------------- 1 | // ToLower maps the character to lower case. 2 | func ToLower(c char) char { 3 | return __unicode_to("Lower", c) 4 | } 5 | 6 | // ToUpper maps the character to upper case. 7 | func ToUpper(c char) char { 8 | return __unicode_to("Upper", c) 9 | } 10 | 11 | // ToTitle maps the character to title case. 12 | func ToTitle(c char) char { 13 | return __unicode_to("Title", c) 14 | } 15 | -------------------------------------------------------------------------------- /lib/unicode/to.okt: -------------------------------------------------------------------------------- 1 | test "ToUpper" { 2 | assert(ToUpper('a') == 'A') 3 | assert(ToUpper('B') == 'B') 4 | assert(ToUpper('5') == '5') 5 | } 6 | 7 | test "ToLower" { 8 | assert(ToLower('a') == 'a') 9 | assert(ToLower('B') == 'b') 10 | assert(ToLower('5') == '5') 11 | } 12 | 13 | test "ToTitle" { 14 | assert(ToUpper('a') == 'A') 15 | assert(ToUpper('B') == 'B') 16 | assert(ToUpper('5') == '5') 17 | } 18 | -------------------------------------------------------------------------------- /number/cmp.go: -------------------------------------------------------------------------------- 1 | package number 2 | 3 | import "github.com/cockroachdb/apd/v2" 4 | 5 | // Cmp compares x and y and returns (regardless of the precision): 6 | // 7 | // -1 if x < y 8 | // 0 if x == y 9 | // +1 if x > y 10 | // 11 | func Cmp(a, b *apd.Decimal) int { 12 | return a.Cmp(b) 13 | } 14 | -------------------------------------------------------------------------------- /number/cmp_test.go: -------------------------------------------------------------------------------- 1 | package number_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/number" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | var comparisonTests = map[string]struct { 11 | left, right string 12 | cmp int 13 | }{ 14 | "zero": { 15 | "0", "0", 16 | 0, 17 | }, 18 | "negative-zero": { 19 | "0", "-0", 20 | 0, 21 | }, 22 | "positive-ints": { 23 | "456", "123", 24 | 1, 25 | }, 26 | "mixed-ints": { 27 | "-456", "123", 28 | -1, 29 | }, 30 | "same-precision": { 31 | "45.69", "45.69", 32 | 0, 33 | }, 34 | "precision-greater-left": { 35 | "45.690", "45.69", 36 | 0, 37 | }, 38 | "precision-greater-right": { 39 | "45", "45.0000", 40 | 0, 41 | }, 42 | } 43 | 44 | func TestCmp(t *testing.T) { 45 | for testName, test := range comparisonTests { 46 | t.Run(testName, func(t *testing.T) { 47 | left := number.NewNumber(test.left) 48 | right := number.NewNumber(test.right) 49 | assert.Equal(t, test.cmp, number.Cmp(left, right)) 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /number/doc.go: -------------------------------------------------------------------------------- 1 | // Package number contains the implementation of the number data type. 2 | package number 3 | -------------------------------------------------------------------------------- /number/int.go: -------------------------------------------------------------------------------- 1 | package number 2 | 3 | import ( 4 | "github.com/cockroachdb/apd/v2" 5 | ) 6 | 7 | // Int64 returns the int64 value. This is only for internal use when we need the 8 | // actual int for things like array sizes. Non-integers will be truncated 9 | // (rounded down). 10 | func Int64(x *apd.Decimal) int64 { 11 | // Extract the fractional part and ignore it. If we don't do this Int64 will 12 | // throw an error. 13 | integ, frac := new(apd.Decimal), new(apd.Decimal) 14 | x.Modf(integ, frac) 15 | 16 | i64, _ := integ.Int64() 17 | 18 | return i64 19 | } 20 | 21 | // Int returns the int value. This is only for internal use when we need the 22 | // actual int for things like array lengths. Non-integers will be truncated 23 | // (rounded down). 24 | func Int(n *apd.Decimal) int { 25 | return int(Int64(n)) 26 | } 27 | -------------------------------------------------------------------------------- /number/int_test.go: -------------------------------------------------------------------------------- 1 | package number_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/number" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | var intTests = map[string]int64{ 11 | "0": 0, 12 | "1234": 1234, 13 | "-100": -100, 14 | "123.4": 123, 15 | "123.8": 123, 16 | "1234567890123456": 1234567890123456, 17 | } 18 | 19 | func TestInt64(t *testing.T) { 20 | for n, expected := range intTests { 21 | t.Run(n, func(t *testing.T) { 22 | num := number.NewNumber(n) 23 | assert.Equal(t, expected, number.Int64(num)) 24 | }) 25 | } 26 | } 27 | 28 | func TestInt(t *testing.T) { 29 | for n, expected := range intTests { 30 | t.Run(n, func(t *testing.T) { 31 | num := number.NewNumber(n) 32 | assert.Equal(t, int(expected), number.Int(num)) 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /number/log.go: -------------------------------------------------------------------------------- 1 | package number 2 | 3 | import ( 4 | "github.com/cockroachdb/apd/v2" 5 | ) 6 | 7 | // Log is the natural logarithm (base e). 8 | func Log(x *apd.Decimal) *apd.Decimal { 9 | d := new(apd.Decimal) 10 | _, _ = context.Ln(d, x) 11 | 12 | return fix(d) 13 | } 14 | -------------------------------------------------------------------------------- /number/log_test.go: -------------------------------------------------------------------------------- 1 | package number_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/number" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestLog(t *testing.T) { 11 | for testName, test := range map[string]string{ 12 | "0.123": "-2.0955709236097195568", 13 | "12.34": "2.5128460184772417554", 14 | } { 15 | t.Run(testName, func(t *testing.T) { 16 | result := number.Log(number.NewNumber(testName)) 17 | assert.Equal(t, test, number.Format(result, -1)) 18 | }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /number/number.go: -------------------------------------------------------------------------------- 1 | package number 2 | 3 | import ( 4 | "github.com/cockroachdb/apd/v2" 5 | ) 6 | 7 | // apdPrecision controls the accuracy of the calculations (in terms of 8 | // significant figures). This value wasn't chosen for any specific reason other 9 | // than it should be big enough to handle all reasonable cases without adding 10 | // too much strain on the CPU. 11 | const apdPrecision = 20 12 | 13 | // context should be used for all calculations. 14 | var context = apd.BaseContext.WithPrecision(apdPrecision) 15 | 16 | // NewNumber creates a new number from a string that is well formatted. 17 | func NewNumber(s string) *apd.Decimal { 18 | // We do not care about the error because we expect the number to be well 19 | // formatted. 20 | dec, _, _ := apd.NewFromString(s) 21 | 22 | return fix(dec) 23 | } 24 | 25 | func fix(dec *apd.Decimal) *apd.Decimal { 26 | // Correct for negative zeros. 27 | if dec.IsZero() { 28 | return new(apd.Decimal) 29 | } 30 | 31 | return dec 32 | } 33 | 34 | // IsZero returns true if the value is equivalent to zero (at any precision). 35 | func IsZero(n *apd.Decimal) bool { 36 | return n.IsZero() 37 | } 38 | -------------------------------------------------------------------------------- /number/pow.go: -------------------------------------------------------------------------------- 1 | package number 2 | 3 | import ( 4 | "github.com/cockroachdb/apd/v2" 5 | ) 6 | 7 | // Pow returns the result of applying a to the power of b. 8 | func Pow(a, b *apd.Decimal) *apd.Decimal { 9 | d := new(apd.Decimal) 10 | _, _ = context.Pow(d, a, b) 11 | 12 | return fix(d) 13 | } 14 | -------------------------------------------------------------------------------- /number/pow_test.go: -------------------------------------------------------------------------------- 1 | package number_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/elliotchance/ok/number" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var powTests = []struct { 12 | a, b string 13 | pow string 14 | }{ 15 | {"0", "4.567", "0"}, 16 | {"1.23", "0", "1"}, 17 | {"1.23", "4.567", "2.5739294795546728988"}, 18 | } 19 | 20 | func TestPow(t *testing.T) { 21 | for _, test := range powTests { 22 | t.Run(fmt.Sprintf("%s,%s", test.a, test.b), func(t *testing.T) { 23 | a := number.NewNumber(test.a) 24 | b := number.NewNumber(test.b) 25 | result := number.Pow(a, b) 26 | assert.Equal(t, test.pow, number.Format(result, -1)) 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /parser/block.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/elliotchance/ok/ast" 5 | "github.com/elliotchance/ok/lexer" 6 | ) 7 | 8 | func consumeBlock(parser *Parser, offset int) ([]ast.Node, int, error) { 9 | var err error 10 | originalOffset := offset 11 | 12 | offset, err = consume(parser, offset, []string{lexer.TokenCurlyOpen}) 13 | if err != nil { 14 | return nil, offset, err 15 | } 16 | 17 | var statements []ast.Node 18 | for { 19 | if parser.tokens[offset].Kind == lexer.TokenCurlyClose { 20 | break 21 | } 22 | 23 | var statement ast.Node 24 | var hoist bool 25 | statement, offset, hoist, err = consumeStatement(parser, offset) 26 | if err != nil { 27 | parser.appendErrorAt(parser.pos(offset), err.Error()) 28 | 29 | return nil, originalOffset, err 30 | } 31 | 32 | if hoist { 33 | statements = append([]ast.Node{statement}, statements...) 34 | } else { 35 | statements = append(statements, statement) 36 | } 37 | } 38 | 39 | offset, err = consume(parser, offset, []string{lexer.TokenCurlyClose}) 40 | if err != nil { 41 | return nil, offset, err 42 | } 43 | 44 | return statements, offset, nil 45 | } 46 | -------------------------------------------------------------------------------- /parser/consume.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/elliotchance/ok/lexer" 7 | ) 8 | 9 | func consumeOneOf(p *Parser, offset int, tokens []string) (lexer.Token, int, error) { 10 | for _, ty := range tokens { 11 | if t := p.tokens[offset]; t.Kind == ty { 12 | return t, offset + 1, nil 13 | } 14 | } 15 | 16 | return lexer.Token{}, offset, errors.New("no token found") 17 | } 18 | -------------------------------------------------------------------------------- /parser/doc.go: -------------------------------------------------------------------------------- 1 | // Package parser translates the linear stream of tokens into ast structures 2 | // based on the syntax of the ok language. 3 | package parser 4 | -------------------------------------------------------------------------------- /parser/error.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type tokenMismatch struct { 8 | expected, after, found string 9 | } 10 | 11 | func newTokenMismatch(expected, after, found string) *tokenMismatch { 12 | return &tokenMismatch{ 13 | expected: expected, 14 | after: after, 15 | found: found, 16 | } 17 | } 18 | 19 | // Error implements the error interface. 20 | func (err *tokenMismatch) Error() string { 21 | return fmt.Sprintf( 22 | "expecting %s after %s, but found %s", 23 | err.expected, err.after, err.found) 24 | } 25 | -------------------------------------------------------------------------------- /parser/errors.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Errors is a collection of errors. 8 | type Errors []error 9 | 10 | func (errs Errors) String() string { 11 | var ss []string 12 | for _, err := range errs { 13 | ss = append(ss, err.Error()) 14 | } 15 | 16 | return strings.Join(ss, "; ") 17 | } 18 | -------------------------------------------------------------------------------- /parser/group.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/elliotchance/ok/ast" 5 | "github.com/elliotchance/ok/lexer" 6 | ) 7 | 8 | // group := "(" expr ")" 9 | func consumeGroup(parser *Parser, offset int) (*ast.Group, int, error) { 10 | originalOffset := offset 11 | 12 | var err error 13 | offset, err = consume(parser, offset, []string{lexer.TokenParenOpen}) 14 | if err != nil { 15 | return nil, originalOffset, err 16 | } 17 | 18 | var expr ast.Node 19 | expr, offset, err = consumeExpr(parser, offset, unlimitedTokens) 20 | if err != nil { 21 | return nil, originalOffset, err 22 | } 23 | 24 | offset, err = consume(parser, offset, []string{lexer.TokenParenClose}) 25 | if err != nil { 26 | return nil, originalOffset, err 27 | } 28 | 29 | return &ast.Group{ 30 | Expr: expr, 31 | Pos: parser.pos(originalOffset), 32 | }, offset, nil 33 | } 34 | -------------------------------------------------------------------------------- /parser/import.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/lexer" 8 | ) 9 | 10 | func consumeImport(parser *Parser, offset int) (*ast.Import, int, error) { 11 | originalOffset := offset 12 | var err error 13 | 14 | offset, err = consume(parser, offset, []string{ 15 | lexer.TokenImport, lexer.TokenStringLiteral}) 16 | if err != nil { 17 | return nil, originalOffset, err 18 | } 19 | 20 | packageName := parser.tokens[offset-1].Value 21 | parts := strings.Split(packageName, "/") 22 | 23 | return &ast.Import{ 24 | VariableName: parts[len(parts)-1], 25 | PackageName: packageName, 26 | Pos: parser.pos(originalOffset), 27 | }, offset, nil 28 | } 29 | -------------------------------------------------------------------------------- /parser/import_test.go: -------------------------------------------------------------------------------- 1 | package parser_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/parser" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestImport(t *testing.T) { 11 | for testName, test := range map[string]struct { 12 | str string 13 | expected map[string]string 14 | }{ 15 | "math": { 16 | str: `import "math"`, 17 | expected: map[string]string{ 18 | "math": "math", 19 | }, 20 | }, 21 | } { 22 | t.Run(testName, func(t *testing.T) { 23 | p := parser.NewParser(0) 24 | p.ParseString(test.str, "a.ok") 25 | 26 | assert.Nil(t, p.Errors()) 27 | assert.Equal(t, test.expected, p.Imports()) 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /parser/key_value.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/elliotchance/ok/ast" 5 | "github.com/elliotchance/ok/lexer" 6 | ) 7 | 8 | func consumeKeyValues(parser *Parser, offset int) ([]*ast.KeyValue, int, error) { 9 | var keyValues []*ast.KeyValue 10 | 11 | for { 12 | var keyValue *ast.KeyValue 13 | var err error 14 | keyValue, offset, err = consumeKeyValue(parser, offset) 15 | if err != nil { 16 | return nil, offset, err 17 | } 18 | 19 | keyValues = append(keyValues, keyValue) 20 | 21 | if parser.tokens[offset].Kind == lexer.TokenComma { 22 | offset++ 23 | } else { 24 | break 25 | } 26 | } 27 | 28 | return keyValues, offset, nil 29 | } 30 | 31 | func consumeKeyValue(parser *Parser, offset int) (*ast.KeyValue, int, error) { 32 | var err error 33 | node := &ast.KeyValue{} 34 | 35 | node.Key, offset, err = consumeExpr(parser, offset, unlimitedTokens) 36 | if err != nil { 37 | return nil, offset, err 38 | } 39 | 40 | offset, err = consume(parser, offset, []string{lexer.TokenColon}) 41 | if err != nil { 42 | return nil, offset, err 43 | } 44 | 45 | node.Value, offset, err = consumeExpr(parser, offset, unlimitedTokens) 46 | if err != nil { 47 | return nil, offset, err 48 | } 49 | 50 | return node, offset, nil 51 | } 52 | -------------------------------------------------------------------------------- /parser/raise.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/elliotchance/ok/ast" 5 | "github.com/elliotchance/ok/lexer" 6 | ) 7 | 8 | func consumeRaise(parser *Parser, offset int) (*ast.Raise, int, error) { 9 | var err error 10 | originalOffset := offset 11 | 12 | offset, err = consume(parser, offset, []string{lexer.TokenRaise}) 13 | if err != nil { 14 | return nil, offset, err 15 | } 16 | 17 | node := &ast.Raise{ 18 | Pos: parser.pos(originalOffset), 19 | } 20 | 21 | node.Err, offset, err = consumeExpr(parser, offset, unlimitedTokens) 22 | if err != nil { 23 | return nil, offset, err 24 | } 25 | 26 | return node, offset, nil 27 | } 28 | -------------------------------------------------------------------------------- /parser/return.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/elliotchance/ok/ast" 5 | "github.com/elliotchance/ok/lexer" 6 | ) 7 | 8 | func consumeReturn(parser *Parser, offset int) (*ast.Return, int, error) { 9 | originalOffset := offset 10 | var err error 11 | node := &ast.Return{ 12 | Pos: parser.pos(originalOffset), 13 | } 14 | 15 | offset, err = consume(parser, offset, []string{lexer.TokenReturn}) 16 | if err != nil { 17 | return nil, originalOffset, err 18 | } 19 | 20 | // Detect zero return values because consumeExprs will require at least one 21 | // expression. 22 | if parser.tokens[offset-1].IsEndOfLine { 23 | return node, offset, nil 24 | } 25 | 26 | node.Exprs, offset, err = consumeExprs(parser, offset) 27 | if err != nil { 28 | return nil, originalOffset, err 29 | } 30 | 31 | return node, offset, nil 32 | } 33 | -------------------------------------------------------------------------------- /parser/return_test.go: -------------------------------------------------------------------------------- 1 | package parser_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/elliotchance/ok/ast" 8 | "github.com/elliotchance/ok/ast/asttest" 9 | "github.com/elliotchance/ok/parser" 10 | ) 11 | 12 | func TestReturn(t *testing.T) { 13 | for testName, test := range map[string]struct { 14 | str string 15 | expected ast.Node 16 | errs []error 17 | }{ 18 | "return-zero": { 19 | str: "return\n", 20 | expected: &ast.Return{}, 21 | }, 22 | "return-number": { 23 | str: "return 123\n", 24 | expected: &ast.Return{ 25 | Exprs: []ast.Node{ 26 | asttest.NewLiteralNumber("123"), 27 | }, 28 | }, 29 | }, 30 | "return-two-values": { 31 | str: "return 123, 'a'\n", 32 | expected: &ast.Return{ 33 | Exprs: []ast.Node{ 34 | asttest.NewLiteralNumber("123"), 35 | asttest.NewLiteralChar('a'), 36 | }, 37 | }, 38 | }, 39 | } { 40 | t.Run(testName, func(t *testing.T) { 41 | str := fmt.Sprintf("func main() { %s }", test.str) 42 | p := parser.NewParser(0) 43 | p.ParseString(str, "a.ok") 44 | 45 | assertEqualErrors(t, test.errs, p.Errors()) 46 | asttest.AssertEqual(t, map[string]*ast.Func{ 47 | "1": newFunc(test.expected), 48 | }, p.Funcs()) 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /parser/test.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/elliotchance/ok/ast" 5 | "github.com/elliotchance/ok/lexer" 6 | ) 7 | 8 | func consumeTest(parser *Parser, offset int) (*ast.Test, int, error) { 9 | originalOffset := offset 10 | var err error 11 | 12 | offset, err = consume(parser, offset, []string{ 13 | lexer.TokenTest, lexer.TokenStringLiteral}) 14 | if err != nil { 15 | return nil, originalOffset, err 16 | } 17 | 18 | t := &ast.Test{ 19 | Name: parser.tokens[offset-1].Value, 20 | Pos: parser.pos(originalOffset), 21 | } 22 | 23 | t.Statements, offset, err = consumeBlock(parser, offset) 24 | if err != nil { 25 | return nil, originalOffset, err 26 | } 27 | 28 | return t, offset, nil 29 | } 30 | -------------------------------------------------------------------------------- /parser/test_test.go: -------------------------------------------------------------------------------- 1 | package parser_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/ast/asttest" 8 | "github.com/elliotchance/ok/parser" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestTest(t *testing.T) { 14 | for testName, test := range map[string]struct { 15 | str string 16 | expected *ast.Test 17 | errs []error 18 | }{ 19 | "empty": { 20 | str: `test "foo bar" {}`, 21 | expected: &ast.Test{ 22 | Name: "foo bar", 23 | }, 24 | }, 25 | } { 26 | t.Run(testName, func(t *testing.T) { 27 | p := parser.NewParser(0) 28 | p.ParseString(test.str, "a.ok") 29 | 30 | assertEqualErrors(t, test.errs, p.Errors()) 31 | asttest.AssertEqual(t, []*ast.Test{test.expected}, p.Tests()) 32 | assert.Len(t, p.Funcs(), 0) 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /parser/util.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "path" 5 | 6 | "github.com/elliotchance/ok/fs" 7 | ) 8 | 9 | // getAllOKFilesInPath non-recursively returns a list of OK files. 10 | func getAllOKFilesInPath(dir string, includeTests bool) ([]string, error) { 11 | files, err := fs.Filesystem.ReadDir(dir) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | var fileNames []string 17 | for _, f := range files { 18 | fileName := path.Join(dir, f.Name()) 19 | if path.Ext(fileName) == ".ok" { 20 | fileNames = append(fileNames, fileName) 21 | } 22 | 23 | if includeTests && path.Ext(fileName) == ".okt" { 24 | fileNames = append(fileNames, fileName) 25 | } 26 | } 27 | 28 | return fileNames, nil 29 | } 30 | -------------------------------------------------------------------------------- /tests/arithmetic/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | print("he" + "llo") 3 | 4 | print(1+1) 5 | print(7.0/3.0) 6 | } 7 | -------------------------------------------------------------------------------- /tests/arithmetic/stdout.txt: -------------------------------------------------------------------------------- 1 | hello 2 | 2 3 | 2.3333333333333333333 4 | -------------------------------------------------------------------------------- /tests/assign/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | i = 27 3 | print(i) 4 | 5 | i += 3 6 | print(i) 7 | 8 | i /= 2 9 | print(i) 10 | 11 | i *= 5 12 | print(i) 13 | 14 | i -= 6 15 | print(i) 16 | 17 | i %= 7 18 | print(i) 19 | } 20 | -------------------------------------------------------------------------------- /tests/assign/stdout.txt: -------------------------------------------------------------------------------- 1 | 27 2 | 30 3 | 15 4 | 75 5 | 69 6 | 6 7 | -------------------------------------------------------------------------------- /tests/chained-methods/main.ok: -------------------------------------------------------------------------------- 1 | func greeter() greeter { 2 | func SayHello(name string) { 3 | print("Hi", name) 4 | } 5 | } 6 | 7 | func main() { 8 | greeter().SayHello("Jane") 9 | } 10 | -------------------------------------------------------------------------------- /tests/chained-methods/stdout.txt: -------------------------------------------------------------------------------- 1 | Hi Jane 2 | -------------------------------------------------------------------------------- /tests/closure/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | fn = func (a number) number { 3 | return a * 2 4 | } 5 | 6 | print(fn(7.3)) 7 | 8 | A = 12 9 | b = 3.7 10 | 11 | fn2 = func (a, b number) number { 12 | return a * b + ^A + ^b 13 | } 14 | 15 | print(fn2(3, 5)) 16 | 17 | b = 5.7 18 | 19 | print(fn2(3, 5)) 20 | 21 | func setA(newVal number) { 22 | ^A = newVal 23 | print(^A) 24 | } 25 | 26 | setA(7.8) 27 | print(fn2(3, 5)) 28 | } 29 | -------------------------------------------------------------------------------- /tests/closure/stdout.txt: -------------------------------------------------------------------------------- 1 | 14.6 2 | 30.7 3 | 32.7 4 | 7.8 5 | 28.5 6 | -------------------------------------------------------------------------------- /tests/comments/main.ok: -------------------------------------------------------------------------------- 1 | // Comments can be everywhere. 2 | 3 | func main() { 4 | // okie 5 | print("hi") // Even here! 6 | // dokie 7 | } 8 | 9 | // Or here... -------------------------------------------------------------------------------- /tests/comments/stdout.txt: -------------------------------------------------------------------------------- 1 | hi 2 | -------------------------------------------------------------------------------- /tests/comparison/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | print("he" > "llo") 3 | 4 | print(1.0 == 1) 5 | print(1.0 != 1) 6 | print(1.000000001 < 1.000000002) 7 | } 8 | -------------------------------------------------------------------------------- /tests/comparison/stdout.txt: -------------------------------------------------------------------------------- 1 | false 2 | true 3 | false 4 | true 5 | -------------------------------------------------------------------------------- /tests/error-unhandled/main.ok: -------------------------------------------------------------------------------- 1 | import "error" 2 | 3 | func foo() { 4 | raise error.Error("uh oh") 5 | } 6 | 7 | func bar() { 8 | foo() 9 | } 10 | 11 | func main() { 12 | bar() 13 | } 14 | -------------------------------------------------------------------------------- /tests/error-unhandled/stdout.txt: -------------------------------------------------------------------------------- 1 | Error: "uh oh" 2 | 3 foo() at /tests/error-unhandled/main.ok:4:5 3 | 2 bar() at /tests/error-unhandled/main.ok:8:8 4 | 1 main() at /tests/error-unhandled/main.ok:12:8 5 | Exit: 1 6 | -------------------------------------------------------------------------------- /tests/example-arrays/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | // Arrays are defined within square brackets. If all the elements are the 3 | // same type, then the type of the array is inferred. In this case the type 4 | // of "a" will be "[]number". 5 | a = [1, 2, 3] 6 | print(a) 7 | 8 | // Arrays can contain mixed types but the type of the array must be given 9 | // explicitly to indicate this. 10 | b = []any [123, "foo", true] 11 | 12 | // Arrays are always printed as valid JSON. This makes it easier and more 13 | // natural to pass data to other systems. 14 | print(b) 15 | 16 | // Access an element in an array by using its index. Remember, 0 is the 17 | // index for the first element. 18 | print(a[0], b[1]) 19 | 20 | // Assigning an element works the same way with the index. 21 | a[1] = 7 22 | print(a) 23 | } 24 | -------------------------------------------------------------------------------- /tests/example-arrays/stdout.txt: -------------------------------------------------------------------------------- 1 | [1, 2, 3] 2 | [123, "foo", true] 3 | 1 foo 4 | [1, 7, 3] 5 | -------------------------------------------------------------------------------- /tests/example-closure/stdout.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 1 5 | -------------------------------------------------------------------------------- /tests/example-errors/stdout.txt: -------------------------------------------------------------------------------- 1 | f1 worked: 10 2 | f1 failed: can't work with 42 3 | f2 worked: 10 4 | f2 failed: 42 - can't work with it 5 | 42 6 | can't work with it 7 | f1 failed, all we have is the message: can't work with 42 8 | -------------------------------------------------------------------------------- /tests/example-finally/main.ok: -------------------------------------------------------------------------------- 1 | import "error" 2 | 3 | func f1(arg number) number { 4 | if arg == 42 { 5 | raise error.Error("can't work with 42") 6 | } 7 | 8 | return arg + 3 9 | } 10 | 11 | func main() { 12 | for i in [7, 42] { 13 | try { 14 | r = f1(i) 15 | print("f1 worked:", r) 16 | } on error.Error { 17 | print("f1 failed:", err.Error) 18 | } finally { 19 | // Finally will always run after the try block or the error 20 | // handler. 21 | print("finally f1") 22 | } 23 | } 24 | 25 | for i in [7, 42] { 26 | try { 27 | r = f2(i) 28 | print("f2 worked:", r) 29 | } on error.Error { 30 | print("f2 failed:", err.Error) 31 | } 32 | } 33 | } 34 | 35 | func f2(arg number) number { 36 | try { 37 | return f1(arg) 38 | } finally { 39 | // Finally will always run before the return, even if there is 40 | // an error. 41 | print("f2 done") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/example-finally/stdout.txt: -------------------------------------------------------------------------------- 1 | f1 worked: 10 2 | finally f1 3 | f1 failed: can't work with 42 4 | finally f1 5 | f2 done 6 | f2 worked: 10 7 | f2 done 8 | f2 failed: can't work with 42 9 | -------------------------------------------------------------------------------- /tests/example-for/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | 3 | // The most basic type, with a single condition. 4 | i = 1 5 | for i <= 3 { 6 | print(i) 7 | i = i + 1 8 | } 9 | 10 | // A classic initial/condition/after for loop. 11 | for j = 7; j <= 9; ++j { 12 | print(j) 13 | } 14 | 15 | // for without a condition will loop repeatedly until you break out of the 16 | // loop or return from the enclosing function. 17 | for { 18 | print("loop") 19 | break 20 | } 21 | 22 | // You can also continue to the next iteration of the loop. 23 | for n = 0; n <= 5; ++n { 24 | if n%2 == 0 { 25 | continue 26 | } 27 | print(n) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/example-for/stdout.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 7 5 | 8 6 | 9 7 | loop 8 | 1 9 | 3 10 | 5 11 | -------------------------------------------------------------------------------- /tests/example-functions/main.ok: -------------------------------------------------------------------------------- 1 | // Here's a function that takes two numbers and returns their sum. 2 | func plus(a number, b number) number { 3 | // ok requires explicit returns, i.e. it won’t automatically return the 4 | // value of the last expression. 5 | return a + b 6 | } 7 | 8 | // When you have multiple consecutive parameters of the same type, you may omit 9 | // the type name for the like-typed parameters up to the final parameter that 10 | // declares the type. 11 | func plusPlus(a, b, c number) number { 12 | return a + b + c 13 | } 14 | 15 | func main() { 16 | // Call a function just as you’d expect, with name(args). 17 | 18 | res = plus(1, 2) 19 | print("1+2 =", res) 20 | 21 | res = plusPlus(1, 2, 3) 22 | print("1+2+3 =", res) 23 | } 24 | -------------------------------------------------------------------------------- /tests/example-functions/stdout.txt: -------------------------------------------------------------------------------- 1 | 1+2 = 3 2 | 1+2+3 = 6 3 | -------------------------------------------------------------------------------- /tests/example-hello-world/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | print("hello world") 3 | } 4 | -------------------------------------------------------------------------------- /tests/example-hello-world/stdout.txt: -------------------------------------------------------------------------------- 1 | hello world 2 | -------------------------------------------------------------------------------- /tests/example-if-else/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | 3 | // Here's a basic example. 4 | if 7%2 == 0 { 5 | print("7 is even") 6 | } else { 7 | print("7 is odd") 8 | } 9 | 10 | // You can have an if statement without an else. 11 | if 8%4 == 0 { 12 | print("8 is divisible by 4") 13 | } 14 | 15 | // Note that you don't need parentheses around conditions in ok, but that 16 | // the braces are required. 17 | num = 9 18 | if num < 10 { 19 | print(num, "has 1 digit") 20 | } else { 21 | print(num, "has multiple digits") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/example-if-else/stdout.txt: -------------------------------------------------------------------------------- 1 | 7 is odd 2 | 8 is divisible by 4 3 | 9 has 1 digit 4 | -------------------------------------------------------------------------------- /tests/example-interfaces/stdout.txt: -------------------------------------------------------------------------------- 1 | g = {"Height": 4, "Sides": 4, "Width": 3} 2 | sides = 4 3 | area = 12 4 | perim = 14 5 | g = {"Radius": 5, "Sides": 0} 6 | sides = 0 7 | area = 78.539816339744830962 8 | perim = 31.415926535897932384 9 | g = {"Sides": 4} 10 | sides = 4 11 | area = 25 12 | perim = 20 13 | -------------------------------------------------------------------------------- /tests/example-interpolate/main.ok: -------------------------------------------------------------------------------- 1 | import "math" 2 | 3 | func main() { 4 | // Variables can be placed in {}. This is called interpolation. 5 | name = "Bob" 6 | print("Hello, {name}. How are you?") 7 | 8 | // Interpolation can be used with any expression. The result doesn't 9 | // even have to be a string. 10 | print("3 + 5 = {3+5}") 11 | 12 | // The spaces added between {} are not required, but might be easier 13 | // to read. 14 | total = 3.557 15 | print("The total is ${ math.Round(total, 2) }") 16 | } 17 | -------------------------------------------------------------------------------- /tests/example-interpolate/stdout.txt: -------------------------------------------------------------------------------- 1 | Hello, Bob. How are you? 2 | 3 + 5 = 8 3 | The total is $3.56 4 | -------------------------------------------------------------------------------- /tests/example-iteration/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | myArray = [7, 11, 13] 3 | 4 | // When iterating an array the first and second variable are assigned the 5 | // value and the index respectively. 6 | for v, i in myArray { 7 | print(i, v) 8 | } 9 | 10 | myMap = {"foo": 1.23, "bar": 4.56} 11 | 12 | // Maps work the same way but the second variable will be the key. 13 | for value, key in myMap { 14 | print(key, value) 15 | } 16 | 17 | // For both arrays and maps you may omit the second variable if you only 18 | // need to iterate the values. 19 | for value in myArray { 20 | print(value) 21 | } 22 | 23 | for value in myMap { 24 | print("value is", value) 25 | } 26 | 27 | // If you also need to keep a numeric iterator while iterating a map you can 28 | // use another form of for. 29 | for i = 0; value, key in myMap; ++i { 30 | print(i, key, value) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/example-iteration/stdout.txt: -------------------------------------------------------------------------------- 1 | 0 7 2 | 1 11 3 | 2 13 4 | foo 1.23 5 | bar 4.56 6 | 7 7 | 11 8 | 13 9 | value is 1.23 10 | value is 4.56 11 | 0 foo 1.23 12 | 1 bar 4.56 13 | -------------------------------------------------------------------------------- /tests/example-maps/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | // Maps are defined within curly brackets. If all the elements are the 3 | // same type, then the type of the map is inferred. In this case the type 4 | // of "a" will be "{}number". 5 | a = {"a": 1, "b": 2, "c": 3} 6 | print(a) 7 | 8 | // Like arrays, maps can contain mixed values by it has to be declared 9 | // explicitly. 10 | b = {}any {"a": 123, "b": "foo", "c": true} 11 | 12 | // Maps are always printed as valid JSON with their keys sorted. This makes 13 | // it easier and more natural to pass data to other systems. 14 | print(b) 15 | 16 | // Access an element in an map by using its key. Maps only support strings 17 | // as the key. 18 | print(a["b"], b["b"]) 19 | 20 | // Assigning an element works the same way with the key. 21 | a["b"] = 7 22 | print(a) 23 | } 24 | -------------------------------------------------------------------------------- /tests/example-maps/stdout.txt: -------------------------------------------------------------------------------- 1 | {"a": 1, "b": 2, "c": 3} 2 | {"a": 123, "b": "foo", "c": true} 3 | 2 foo 4 | {"a": 1, "b": 7, "c": 3} 5 | -------------------------------------------------------------------------------- /tests/example-methods/main.ok: -------------------------------------------------------------------------------- 1 | func Rectangle(width, height number) Rectangle { 2 | // Methods are nested functions. Like closures they can reference variables 3 | // in their outer scope with "^". 4 | func Area() number { 5 | return ^width * ^height 6 | } 7 | 8 | // A nested function can be written as an assignment, this worked exactly 9 | // the same way. 10 | Perim = func() number { 11 | return 2 * ^width + 2 * ^height 12 | } 13 | } 14 | 15 | func main() { 16 | r = Rectangle(10, 5) 17 | 18 | print("area: { r.Area() }") 19 | print("perim: { r.Perim() }") 20 | } 21 | -------------------------------------------------------------------------------- /tests/example-methods/stdout.txt: -------------------------------------------------------------------------------- 1 | area: 50 2 | perim: 30 3 | -------------------------------------------------------------------------------- /tests/example-multiple-return-values/main.ok: -------------------------------------------------------------------------------- 1 | // OK has built-in support for multiple return values. 2 | 3 | // The (number, number) in this function signature shows 4 | // that the function returns 2 numbers. 5 | func vals() (number, number) { 6 | return 3, 7 7 | } 8 | 9 | func main() { 10 | // Here we use the 2 different return values from the 11 | // call with multiple assignment. 12 | a, b = vals() 13 | print(a) 14 | print(b) 15 | 16 | // If you only want a subset of the returned values, 17 | // use the blank identifier _. 18 | _, c = vals() 19 | print(c) 20 | } 21 | -------------------------------------------------------------------------------- /tests/example-multiple-return-values/stdout.txt: -------------------------------------------------------------------------------- 1 | 3 2 | 7 3 | 7 4 | -------------------------------------------------------------------------------- /tests/example-objects/main.ok: -------------------------------------------------------------------------------- 1 | // When a function has a single return type that is also the same name 2 | // as the function, this is called a constructor. Constructors are used 3 | // to create stateful functions. Sometimes called objects, records or 4 | // structures in other languages. 5 | func person(Name string) person { 6 | // When you set variables here, they will be accessible outside if they 7 | // start with a capital letter. Arguments that follow the same rules will be 8 | // treated the same way. 9 | Age = 42 10 | 11 | // A constructor does not need to have a return statement. It will 12 | // always return itself. 13 | } 14 | 15 | func main() { 16 | // Create an instance by calling the constructor. 17 | p = person("Bob") 18 | 19 | // You can read and write properties using the dot notation. 20 | p.Name = "John" 21 | print(p.Age) 22 | 23 | // Objects are always printed as JSON objects. 24 | print(p) 25 | } 26 | -------------------------------------------------------------------------------- /tests/example-objects/stdout.txt: -------------------------------------------------------------------------------- 1 | 42 2 | {"Age": 42, "Name": "John"} 3 | -------------------------------------------------------------------------------- /tests/example-recursion/main.ok: -------------------------------------------------------------------------------- 1 | // ok supports recursive functions. Here's a classic factorial example. 2 | func fact(n number) number { 3 | if n == 0 { 4 | return 1 5 | } 6 | 7 | // This fact function calls itself until it reaches the base case of fact(0). 8 | return n * fact(n-1) 9 | } 10 | 11 | func main() { 12 | print(fact(7)) 13 | } 14 | -------------------------------------------------------------------------------- /tests/example-recursion/stdout.txt: -------------------------------------------------------------------------------- 1 | 5040 2 | -------------------------------------------------------------------------------- /tests/example-switch/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | 3 | // Here's a basic switch. 4 | i = 2 5 | print("Write", i, "as") 6 | switch i { 7 | case 1 { 8 | print("one") 9 | } 10 | case 2 { 11 | print("two") 12 | } 13 | case 3 { 14 | print("three") 15 | } 16 | } 17 | 18 | // You can use commas to separate multiple expressions in the same case 19 | // statement. We use the optional else case in this example as well. 20 | weekday = "sunday" 21 | switch weekday { 22 | case "saturday", "sunday" { 23 | print("It's the weekend") 24 | } 25 | else { 26 | print("It's a weekday") 27 | } 28 | } 29 | 30 | // switch without an expression is an alternate way to express if/else 31 | // logic. Here we also show how the case expressions can be non-constants. 32 | hour = 11 33 | switch { 34 | case hour < 12 { 35 | print("It's before noon") 36 | } 37 | else { 38 | print("It's after noon") 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/example-switch/stdout.txt: -------------------------------------------------------------------------------- 1 | Write 2 as 2 | two 3 | It's the weekend 4 | It's before noon 5 | -------------------------------------------------------------------------------- /tests/example-time/main.ok: -------------------------------------------------------------------------------- 1 | import "time" 2 | 3 | func main() { 4 | now = time.Now() 5 | print(now.String()) 6 | 7 | then = time.Time(2009, 11, 17, 20, 34, 58.651387237) 8 | print(then.String()) 9 | 10 | print(then.Year) 11 | print(then.Month) 12 | print(then.Day) 13 | print(then.Hour) 14 | print(then.Minute) 15 | print(then.Second) 16 | 17 | print(time.Before(then, now)) 18 | print(time.After(then, now)) 19 | print(time.Equal(then, now)) 20 | 21 | diff = time.Sub(now, then) 22 | print(diff.String()) 23 | 24 | print(diff.Hours()) 25 | print(diff.Minutes()) 26 | print(diff.Seconds()) 27 | 28 | print(time.Add(then, diff)) 29 | print(time.Add(then, time.Duration(-diff.Seconds()))) 30 | } 31 | -------------------------------------------------------------------------------- /tests/example-time/stdout-ignored.txt: -------------------------------------------------------------------------------- 1 | Stdout is not compared because of the dynamic output. 2 | -------------------------------------------------------------------------------- /tests/example-values/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | // Strings, which can be added together with +. 3 | print("he" + "llo") 4 | 5 | // Numbers. 6 | print("1+1 =", 1+1) 7 | print("7.0/3.0 =", 7.0/3.0) 8 | 9 | // Booleans. 10 | print(true and false) 11 | print(true or false) 12 | print(not true) 13 | } 14 | -------------------------------------------------------------------------------- /tests/example-values/stdout.txt: -------------------------------------------------------------------------------- 1 | hello 2 | 1+1 = 2 3 | 7.0/3.0 = 2.3333333333333333333 4 | false 5 | true 6 | false 7 | -------------------------------------------------------------------------------- /tests/example-variables/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | 3 | // Declare a variable. 4 | a = "initial" 5 | print(a) 6 | 7 | // ok will infer the type of initialized variables. 8 | b = true 9 | print(b) 10 | 11 | c = 1.23 12 | print(c) 13 | } 14 | -------------------------------------------------------------------------------- /tests/example-variables/stdout.txt: -------------------------------------------------------------------------------- 1 | initial 2 | true 3 | 1.23 4 | -------------------------------------------------------------------------------- /tests/for-in/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | values = [7, 11, 13] 3 | for v1 in values { 4 | print(v1) 5 | } 6 | 7 | for v2, k2 in values { 8 | print(k2, v2) 9 | } 10 | 11 | m = {"foo": 1.23, "bar": 4.56} 12 | for v3, k3 in m { 13 | print(k3, v3) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/for-in/stdout.txt: -------------------------------------------------------------------------------- 1 | 7 2 | 11 3 | 13 4 | 0 7 5 | 1 11 6 | 2 13 7 | foo 1.23 8 | bar 4.56 9 | -------------------------------------------------------------------------------- /tests/for/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | i = 0 3 | for i < 5 { 4 | print(i) 5 | i = i + 1 6 | } 7 | 8 | print("before for 1") 9 | for { 10 | print("before break") 11 | break 12 | print("after break") 13 | } 14 | print("after for") 15 | 16 | print("before for 2") 17 | i = 0 18 | for i < 3 { 19 | print("before continue", i) 20 | i = i + 1 21 | continue 22 | print("after continue") 23 | } 24 | print("after for") 25 | 26 | print("before for 3") 27 | for a = 0; a < 3; ++a { 28 | print(a) 29 | } 30 | print("after for") 31 | } 32 | -------------------------------------------------------------------------------- /tests/for/stdout.txt: -------------------------------------------------------------------------------- 1 | 0 2 | 1 3 | 2 4 | 3 5 | 4 6 | before for 1 7 | before break 8 | after for 9 | before for 2 10 | before continue 0 11 | before continue 1 12 | before continue 2 13 | after for 14 | before for 3 15 | 0 16 | 1 17 | 2 18 | after for 19 | -------------------------------------------------------------------------------- /tests/functions/main.ok: -------------------------------------------------------------------------------- 1 | func foo() { 2 | print("called foo") 3 | } 4 | 5 | func main() { 6 | foo() 7 | print(add(2.3, 7.90)) 8 | } 9 | 10 | func add(a, b number) number { 11 | return a + b 12 | } 13 | -------------------------------------------------------------------------------- /tests/functions/stdout.txt: -------------------------------------------------------------------------------- 1 | called foo 2 | 10.2 3 | -------------------------------------------------------------------------------- /tests/group/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | print(2 + 3 * 4) // 14 3 | print(2 * 3 + 4) // 10 4 | 5 | print((2 + 3) * 4) // 20 6 | print(2 + (3 * 4)) // 14 7 | print((2 * 3) + 4) // 10 8 | print(2 * (3 + 4)) // 14 9 | 10 | print((7.2 + 1)) // 8.2 11 | print((123)) // 123 12 | print((((((((("super deep"))))))))) // "super deep" 13 | } 14 | -------------------------------------------------------------------------------- /tests/group/stdout.txt: -------------------------------------------------------------------------------- 1 | 14 2 | 10 3 | 20 4 | 14 5 | 10 6 | 14 7 | 8.2 8 | 123 9 | super deep 10 | -------------------------------------------------------------------------------- /tests/hello-world/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | print("Hello, World!") 3 | } 4 | -------------------------------------------------------------------------------- /tests/hello-world/stdout.txt: -------------------------------------------------------------------------------- 1 | Hello, World! 2 | -------------------------------------------------------------------------------- /tests/if/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | print("before if 1") 3 | if true { 4 | print("should be true") 5 | } 6 | print("after if 1") 7 | 8 | print("before if 2") 9 | if false { 10 | print("uh oh!") 11 | } 12 | print("after if 2") 13 | 14 | print("before if 3") 15 | if true { 16 | print("good") 17 | } else { 18 | print("bad") 19 | } 20 | print("after if 3") 21 | 22 | print("before if 4") 23 | if not true { 24 | print("bad") 25 | } else { 26 | print("good") 27 | } 28 | print("after if 4") 29 | } 30 | -------------------------------------------------------------------------------- /tests/if/stdout.txt: -------------------------------------------------------------------------------- 1 | before if 1 2 | should be true 3 | after if 1 4 | before if 2 5 | after if 2 6 | before if 3 7 | good 8 | after if 3 9 | before if 4 10 | good 11 | after if 4 12 | -------------------------------------------------------------------------------- /tests/import/main.ok: -------------------------------------------------------------------------------- 1 | import "tests/import/mypkg" 2 | 3 | func main() { 4 | print(mypkg.Add(1.2, 3.4)) 5 | } 6 | -------------------------------------------------------------------------------- /tests/import/mypkg/add.ok: -------------------------------------------------------------------------------- 1 | func Add(a, b number) number { 2 | return a + b 3 | } 4 | -------------------------------------------------------------------------------- /tests/import/stdout.txt: -------------------------------------------------------------------------------- 1 | 4.6 2 | -------------------------------------------------------------------------------- /tests/interpolate/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | print("3 + 5 = {3+5}") 3 | } 4 | -------------------------------------------------------------------------------- /tests/interpolate/stdout.txt: -------------------------------------------------------------------------------- 1 | 3 + 5 = 8 2 | -------------------------------------------------------------------------------- /tests/len/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | print("array len =", len(["foo", "bar"])) 3 | print("map len =", len({"foo": 50, "bar": 100, "baz": 75})) 4 | } 5 | -------------------------------------------------------------------------------- /tests/len/stdout.txt: -------------------------------------------------------------------------------- 1 | array len = 2 2 | map len = 3 3 | -------------------------------------------------------------------------------- /tests/literals/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | print(true) 3 | print(false) 4 | print('A') 5 | print('😃') 6 | print(0) 7 | print(456) 8 | print(1.23) 9 | print(`some 😳 data`) 10 | print("hello") 11 | print("夜") 12 | } 13 | -------------------------------------------------------------------------------- /tests/literals/stdout.txt: -------------------------------------------------------------------------------- 1 | true 2 | false 3 | A 4 | 😃 5 | 0 6 | 456 7 | 1.23 8 | some 😳 data 9 | hello 10 | 夜 11 | -------------------------------------------------------------------------------- /tests/logger/main.ok: -------------------------------------------------------------------------------- 1 | import "log" 2 | import "strings" 3 | 4 | func main() { 5 | logger = log.Logger(func (level, message string) { 6 | level = strings.PadLeft(level, " ", 5) 7 | print("{level} {message}") 8 | }) 9 | logger.Debug("debug message") 10 | logger.Info("info message") 11 | logger.Warn("warning message") 12 | logger.Error("error message") 13 | logger.Fatal("fatal message") 14 | } 15 | -------------------------------------------------------------------------------- /tests/logger/stdout.txt: -------------------------------------------------------------------------------- 1 | DEBUG debug message 2 | INFO info message 3 | WARN warning message 4 | ERROR error message 5 | FATAL fatal message 6 | -------------------------------------------------------------------------------- /tests/logical/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | print(true and true) 3 | print(true and false) 4 | print(false and true) 5 | print(false and false) 6 | 7 | print(true or true) 8 | print(true or false) 9 | print(false or true) 10 | print(false or false) 11 | 12 | print(not true) 13 | print(not false) 14 | } 15 | -------------------------------------------------------------------------------- /tests/logical/stdout.txt: -------------------------------------------------------------------------------- 1 | true 2 | false 3 | false 4 | false 5 | true 6 | true 7 | true 8 | false 9 | false 10 | true 11 | -------------------------------------------------------------------------------- /tests/maps/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | map1 = {"a": 123, "b": 456} 3 | print(map1) 4 | 5 | map2 = {}any {} 6 | print(map2) 7 | 8 | map1["a"] = 789 9 | print(map1) 10 | 11 | a = "b" 12 | map1[a] = 234 13 | print(map1) 14 | } 15 | -------------------------------------------------------------------------------- /tests/maps/stdout.txt: -------------------------------------------------------------------------------- 1 | {"a": 123, "b": 456} 2 | {} 3 | {"a": 789, "b": 456} 4 | {"a": 789, "b": 234} 5 | -------------------------------------------------------------------------------- /tests/math/main.ok: -------------------------------------------------------------------------------- 1 | import "math" 2 | 3 | func main() { 4 | print(math.Abs(-1.23)) 5 | print(math.Pi) 6 | } 7 | -------------------------------------------------------------------------------- /tests/math/stdout.txt: -------------------------------------------------------------------------------- 1 | 1.23 2 | 3.14159265358979323846264338327950288419716939937510582097494459 3 | -------------------------------------------------------------------------------- /tests/methods/main.ok: -------------------------------------------------------------------------------- 1 | func greeter() greeter { 2 | times = 0 3 | 4 | func SayHello(name string) { 5 | print("Hi", name) 6 | ++^times 7 | } 8 | 9 | func Times() number { 10 | return ^times 11 | } 12 | } 13 | 14 | func main() { 15 | g = greeter() 16 | g.SayHello("Jane") 17 | g.SayHello("John") 18 | 19 | times = g.Times() 20 | print(times + 5) 21 | } 22 | -------------------------------------------------------------------------------- /tests/methods/stdout.txt: -------------------------------------------------------------------------------- 1 | Hi Jane 2 | Hi John 3 | 7 4 | -------------------------------------------------------------------------------- /tests/nested-functions/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | func foo(a number) number { 3 | return a * 2 4 | } 5 | 6 | print(foo(7.3)) 7 | 8 | print(bar(3, 5)) 9 | 10 | func bar(a, b number) number { 11 | return a * b 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/nested-functions/stdout.txt: -------------------------------------------------------------------------------- 1 | 14.6 2 | 15 3 | -------------------------------------------------------------------------------- /tests/package-func-as-literal/main.ok: -------------------------------------------------------------------------------- 1 | // Functions at the package-level are special in a way, because they can be 2 | // referenced as literals but are available regardless of the actual scope of 3 | // the code referencing them. 4 | // 5 | // This verifies that package-level functions can be still treated as literals. 6 | 7 | func foo() number { 8 | return 13 9 | } 10 | 11 | func main() { 12 | bar = foo 13 | print(bar()) 14 | } 15 | -------------------------------------------------------------------------------- /tests/package-func-as-literal/stdout.txt: -------------------------------------------------------------------------------- 1 | 13 2 | -------------------------------------------------------------------------------- /tests/print/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | print("go" + "lang") 3 | 4 | print("1+1 =", 1+1) 5 | print("7.0/3.0 =", 7.0/3.0) 6 | 7 | print(true and false) 8 | print(true or false) 9 | print(not true) 10 | } 11 | -------------------------------------------------------------------------------- /tests/print/stdout.txt: -------------------------------------------------------------------------------- 1 | golang 2 | 1+1 = 2 3 | 7.0/3.0 = 2.3333333333333333333 4 | false 5 | true 6 | false 7 | -------------------------------------------------------------------------------- /tests/raise/main.ok: -------------------------------------------------------------------------------- 1 | func MyError(Message string) MyError { 2 | } 3 | 4 | func MyOtherError(Message string) MyOtherError { 5 | Secret = 1 6 | } 7 | 8 | func foo(raiseError bool) string { 9 | if raiseError { 10 | raise MyOtherError("okie dokes") 11 | print("you should not see this") 12 | } 13 | 14 | return "all good!" 15 | } 16 | 17 | func main() { 18 | print("0a") 19 | 20 | try { 21 | print("0b") 22 | } on MyError { 23 | print("0c") 24 | } 25 | 26 | print("0d") 27 | 28 | print("1a") 29 | 30 | try { 31 | print("1b") 32 | raise MyError("oh noes!") 33 | print("1c") 34 | } on MyOtherError { 35 | print("1d") 36 | } on MyError { 37 | print("1e") 38 | } 39 | 40 | print("1f") 41 | 42 | print("2a") 43 | 44 | try { 45 | print(foo(false)) 46 | print("2b") 47 | } on MyOtherError { 48 | print("2c") 49 | } on MyError { 50 | print("2d") 51 | } 52 | 53 | print("2e") 54 | 55 | print("3a") 56 | 57 | try { 58 | print(foo(true)) 59 | print("3b") 60 | } on MyOtherError { 61 | print("3c") 62 | } on MyError { 63 | print("3d") 64 | } 65 | 66 | print("3e") 67 | } 68 | -------------------------------------------------------------------------------- /tests/raise/stdout.txt: -------------------------------------------------------------------------------- 1 | 0a 2 | 0b 3 | 0d 4 | 1a 5 | 1b 6 | 1e 7 | 1f 8 | 2a 9 | all good! 10 | 2b 11 | 2e 12 | 3a 13 | 3c 14 | 3e 15 | -------------------------------------------------------------------------------- /tests/strings/main.ok: -------------------------------------------------------------------------------- 1 | import "strings" 2 | 3 | func main() { 4 | print(strings.Repeat("foo", 3)) 5 | } 6 | -------------------------------------------------------------------------------- /tests/strings/stdout.txt: -------------------------------------------------------------------------------- 1 | foofoofoo 2 | -------------------------------------------------------------------------------- /tests/switch-value/stdout.txt: -------------------------------------------------------------------------------- 1 | before switch 1 2 | TWO 3 | after switch 1 4 | before switch 1a 5 | TWO OR FOUR 6 | after switch 1a 7 | before switch 2 8 | FOUR 9 | after switch 2 10 | before switch 2a 11 | TWO OR FOUR 12 | after switch 2a 13 | before switch 3 14 | ELSE 15 | after switch 3 16 | before switch 3a 17 | ELSE 18 | after switch 3a 19 | -------------------------------------------------------------------------------- /tests/switch/stdout.txt: -------------------------------------------------------------------------------- 1 | before switch 1 2 | ONE 3 | after switch 1 4 | before switch 1a 5 | ONE OR TWO 6 | after switch 1a 7 | before switch 2 8 | TWO 9 | after switch 2 10 | before switch 2a 11 | ONE OR TWO 12 | after switch 2a 13 | before switch 3 14 | ELSE 15 | after switch 3 16 | before switch 3a 17 | ELSE 18 | after switch 3a 19 | -------------------------------------------------------------------------------- /tests/test-assert-failed/main.ok: -------------------------------------------------------------------------------- 1 | func add(a, b number) number { 2 | return a + b 3 | } 4 | 5 | func main() { 6 | } 7 | -------------------------------------------------------------------------------- /tests/test-assert-failed/main.okt: -------------------------------------------------------------------------------- 1 | test "adding some numbers" { 2 | assert(add(3, 5) == 8) 3 | assert(add(1.2, 7.90) == 75) 4 | assert(add(1.2, 7.90) == 9.10) 5 | } 6 | 7 | test "adding some numbers 2" { 8 | assert(add(3, 5) == 8) 9 | assert(add(1.2, 7.90) == 75) 10 | assert(add(1.2, 7.90) == 9.10) 11 | } 12 | -------------------------------------------------------------------------------- /tests/test-assert-failed/stdout-test.txt: -------------------------------------------------------------------------------- 1 | no-package: /tests/test-assert-failed/main.okt:3:5: adding some numbers: assert(9.1 == 75) failed 2 | no-package: /tests/test-assert-failed/main.okt:9:5: adding some numbers 2: assert(9.1 == 75) failed 3 | tests/test-assert-failed: 2 failed 0 passed 6 asserts (0 ms) 4 | Exit: 1 5 | -------------------------------------------------------------------------------- /tests/test-assert-failed/stdout.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elliotchance/ok/57730a529fb1222dc70a0a6ea21a2207039a9dfe/tests/test-assert-failed/stdout.txt -------------------------------------------------------------------------------- /tests/test-raised-error/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | } 3 | -------------------------------------------------------------------------------- /tests/test-raised-error/main.okt: -------------------------------------------------------------------------------- 1 | import "error" 2 | 3 | func foo() { 4 | raise error.Error("uh oh") 5 | } 6 | 7 | func bar() { 8 | foo() 9 | } 10 | 11 | func main() { 12 | 13 | } 14 | 15 | test "A" { 16 | assert(true == true) 17 | bar() 18 | assert(false == true) 19 | } 20 | 21 | test "B" { 22 | assert(true == true) 23 | bar() 24 | assert(false == true) 25 | } 26 | -------------------------------------------------------------------------------- /tests/test-raised-error/stdout-test.txt: -------------------------------------------------------------------------------- 1 | tests/test-raised-error: /tests/test-raised-error/main.okt:15:1: A: unhandled error 2 | Error: "uh oh" 3 | 3 foo() at /tests/test-raised-error/main.okt:4:5 4 | 2 bar() at /tests/test-raised-error/main.okt:8:8 5 | 1 test "A"() at /tests/test-raised-error/main.okt:17:8 6 | tests/test-raised-error: /tests/test-raised-error/main.okt:21:1: B: unhandled error 7 | Error: "uh oh" 8 | 3 foo() at /tests/test-raised-error/main.okt:4:5 9 | 2 bar() at /tests/test-raised-error/main.okt:8:8 10 | 1 test "B"() at /tests/test-raised-error/main.okt:23:8 11 | tests/test-raised-error: 2 failed 0 passed 2 asserts (0 ms) 12 | Exit: 1 13 | -------------------------------------------------------------------------------- /tests/test-raised-error/stdout.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elliotchance/ok/57730a529fb1222dc70a0a6ea21a2207039a9dfe/tests/test-raised-error/stdout.txt -------------------------------------------------------------------------------- /tests/tests/main.ok: -------------------------------------------------------------------------------- 1 | func add(a, b number) number { 2 | return a + b 3 | } 4 | 5 | func main() { 6 | } 7 | -------------------------------------------------------------------------------- /tests/tests/main.okt: -------------------------------------------------------------------------------- 1 | test "adding some numbers" { 2 | assert(add(3, 5) == 8) 3 | assert(add(1.2, 7.90) == 9.10) 4 | } 5 | -------------------------------------------------------------------------------- /tests/tests/stdout.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elliotchance/ok/57730a529fb1222dc70a0a6ea21a2207039a9dfe/tests/tests/stdout.txt -------------------------------------------------------------------------------- /tests/time/main.ok: -------------------------------------------------------------------------------- 1 | import "time" 2 | 3 | func printTime(t time.Time) { 4 | print(t) 5 | } 6 | 7 | func main() { 8 | printTime(time.Time(2001, time.August, 9, 1, 46, 40.123)) 9 | } 10 | -------------------------------------------------------------------------------- /tests/time/stdout.txt: -------------------------------------------------------------------------------- 1 | {"Day": 9, "Hour": 1, "Minute": 46, "Month": 8, "Second": 40.123, "Year": 2001} 2 | -------------------------------------------------------------------------------- /tests/unary/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | c = 1.23 3 | print(-c) 4 | 5 | print(-foo()) 6 | } 7 | 8 | func foo() number { 9 | return 234 10 | } 11 | -------------------------------------------------------------------------------- /tests/unary/stdout.txt: -------------------------------------------------------------------------------- 1 | -1.23 2 | -234 3 | -------------------------------------------------------------------------------- /tests/variables/main.ok: -------------------------------------------------------------------------------- 1 | func main() { 2 | 3 | a = "initial" 4 | print(a) 5 | 6 | b = true 7 | print(b) 8 | 9 | c = 1.23 10 | print(c) 11 | } 12 | -------------------------------------------------------------------------------- /tests/variables/stdout.txt: -------------------------------------------------------------------------------- 1 | initial 2 | true 3 | 1.23 4 | -------------------------------------------------------------------------------- /types/base.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | var ( 4 | Any = TypeFromString("any") 5 | Bool = TypeFromString("bool") 6 | Char = TypeFromString("char") 7 | Data = TypeFromString("data") 8 | Number = TypeFromString("number") 9 | String = TypeFromString("string") 10 | 11 | AnyArray = NewArray(Any) 12 | BoolArray = NewArray(Bool) 13 | NumberArray = NewArray(Number) 14 | StringArray = NewArray(String) 15 | 16 | AnyMap = NewMap(Any) 17 | NumberMap = NewMap(Number) 18 | StringMap = NewMap(String) 19 | 20 | ErrorInterface = NewInterface("error.Error", map[string]*Type{ 21 | "Error": String, 22 | }) 23 | ) 24 | -------------------------------------------------------------------------------- /types/kind.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type Kind int 4 | 5 | const ( 6 | // An unresolved interface (object type) is named, but the properties will 7 | // be nil because they are not known. This is a distinction from a resolved 8 | // interface which allowed to have zero or more properties. 9 | // 10 | // KindUnresolvedInterface = 0 on purpose it acts as a fallthrough/unknown 11 | // type. 12 | KindUnresolvedInterface Kind = iota 13 | KindResolvedInterface 14 | 15 | // Basic types. 16 | KindAny 17 | KindBool 18 | KindChar 19 | KindData 20 | KindNumber 21 | KindString 22 | 23 | // Other. 24 | KindArray 25 | KindMap 26 | KindFunc 27 | ) 28 | 29 | func kindFromString(s string) Kind { 30 | switch s { 31 | case "any": 32 | return KindAny 33 | 34 | case "bool": 35 | return KindBool 36 | 37 | case "char": 38 | return KindChar 39 | 40 | case "data": 41 | return KindData 42 | 43 | case "number": 44 | return KindNumber 45 | 46 | case "string": 47 | return KindString 48 | } 49 | 50 | return KindUnresolvedInterface 51 | } 52 | -------------------------------------------------------------------------------- /util/env.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "os" 4 | 5 | // OKPath returns the root of where packages can be found. This can be provided 6 | // through the $OKPATH environment variable. However, if its not set (or is 7 | // empty) then the current directory will be used. 8 | func OKPath() (string, error) { 9 | okPath := os.Getenv("OKPATH") 10 | if okPath == "" { 11 | var err error 12 | okPath, err = os.Getwd() 13 | if err != nil { 14 | return "", err 15 | } 16 | } 17 | 18 | return okPath, nil 19 | } 20 | -------------------------------------------------------------------------------- /util/errors.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | // CheckErrorsWithExit is used in several places to bail out after a compilation 9 | // that contains failures. 10 | func CheckErrorsWithExit(errs []error) { 11 | for _, err := range errs { 12 | fmt.Println(err) 13 | } 14 | 15 | if len(errs) > 0 { 16 | os.Exit(1) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /util/functions.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "unicode" 4 | 5 | // IsPublic returns true if a function name is visible outside of a package. 6 | func IsPublic(funcName string) bool { 7 | return len(funcName) > 0 && unicode.IsUpper(rune(funcName[0])) 8 | } 9 | -------------------------------------------------------------------------------- /util/glob.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | // MatchesGlob is a very rudimentary implementation of glob matching on strings. 9 | // Right now it only supports "*" that will match zero or more characters 10 | // anywhere within the string. 11 | func MatchesGlob(s, glob string) bool { 12 | regex := "^" + strings.ReplaceAll(glob, "*", ".*") + "$" 13 | 14 | return regexp.MustCompile(regex).MatchString(s) 15 | } 16 | -------------------------------------------------------------------------------- /util/glob_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestMatchesGlob(t *testing.T) { 10 | tests := map[string]struct { 11 | s, glob string 12 | match bool 13 | }{ 14 | "1": {"foo", "foo", true}, 15 | "2": {"food", "foo", false}, 16 | "3": {"Foo", "foo", false}, 17 | "4": {"", "", true}, 18 | "5": {"foo", "foo*", true}, 19 | "6": {"food", "foo*", true}, 20 | "7": {"foo", "*foo", true}, 21 | "8": {"aafoo", "*foo", true}, 22 | "9": {"foo", "f*oo", true}, 23 | "10": {"f12oo", "f*oo", true}, 24 | "11": {"foo", "f**oo", true}, 25 | "12": {"f12oo", "f**oo", true}, 26 | "13": {"f12o1o", "f**oo", false}, 27 | "14": {"f1o2o", "f*o*o", true}, 28 | "15": {"f1oo", "f*o*o", true}, 29 | "16": {"foo", "*", true}, 30 | "17": {"", "*", true}, 31 | } 32 | for testName, tt := range tests { 33 | t.Run(testName, func(t *testing.T) { 34 | assert.Equal(t, tt.match, MatchesGlob(tt.s, tt.glob)) 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /util/package.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | // PackageNameFromPath returns the full package name from a path compared to its 9 | // base directory. 10 | // 11 | // The baseDir and packagePath may be relative or absolute. If baseDir is empty, 12 | // then the current working directory is used. 13 | func PackageNameFromPath(baseDir, packagePath string) string { 14 | if baseDir == "" { 15 | baseDir, _ = os.Getwd() 16 | } 17 | 18 | if !filepath.IsAbs(baseDir) { 19 | baseDir, _ = filepath.Abs(baseDir) 20 | } 21 | 22 | if !filepath.IsAbs(packagePath) { 23 | packagePath, _ = filepath.Abs(packagePath) 24 | } 25 | 26 | rel, _ := filepath.Rel(baseDir, packagePath) 27 | if rel == "." { 28 | return filepath.Base(baseDir) 29 | } 30 | 31 | return rel 32 | } 33 | -------------------------------------------------------------------------------- /util/package_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPackageNameFromPath(t *testing.T) { 10 | for testName, test := range map[string]struct { 11 | baseDir string 12 | packagePath string 13 | expected string 14 | }{ 15 | "1": {"/", "/foo", "foo"}, 16 | "2": {"/foo", "/foo/bar", "bar"}, 17 | "3": {"/", "/foo/bar", "foo/bar"}, 18 | "4": {"/foo/bar", "/foo/bar", "bar"}, 19 | "5": {"/", "/", "/"}, 20 | "6": {"/", "/foo/", "foo"}, 21 | "7": {"/foo", "/foo/bar/", "bar"}, 22 | "8": {"/foo/", "/foo/bar", "bar"}, 23 | "9": {"/foo/", "/foo/bar/", "bar"}, 24 | } { 25 | t.Run(testName, func(t *testing.T) { 26 | actual := PackageNameFromPath(test.baseDir, test.packagePath) 27 | assert.Equal(t, test.expected, actual) 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /vm/add.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | "github.com/elliotchance/ok/number" 8 | ) 9 | 10 | // Add will sum two numbers. 11 | type Add struct { 12 | Left, Right, Result Register 13 | } 14 | 15 | // Execute implements the Instruction interface for the VM. 16 | func (ins *Add) Execute(_ *int, vm *VM) error { 17 | vm.Set(ins.Result, asttest.NewLiteralNumber( 18 | number.Add( 19 | number.NewNumber(vm.Get(ins.Left).Value), 20 | number.NewNumber(vm.Get(ins.Right).Value), 21 | ).String(), 22 | )) 23 | 24 | return nil 25 | } 26 | 27 | // String is the human-readable description of the instruction. 28 | func (ins *Add) String() string { 29 | return fmt.Sprintf("%s = %s + %s", ins.Result, ins.Left, ins.Right) 30 | } 31 | -------------------------------------------------------------------------------- /vm/add_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/ast/asttest" 8 | "github.com/elliotchance/ok/vm" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestAdd_Execute(t *testing.T) { 14 | for testName, test := range map[string]struct { 15 | left, right string 16 | expected string 17 | }{ 18 | "maintain-precision": {"1.2200", "4.7", "5.9200"}, 19 | } { 20 | t.Run(testName, func(t *testing.T) { 21 | registers := map[vm.Register]*ast.Literal{ 22 | "0": asttest.NewLiteralNumber(test.left), 23 | "1": asttest.NewLiteralNumber(test.right), 24 | } 25 | ins := &vm.Add{Left: "0", Right: "1", Result: "2"} 26 | vm := &vm.VM{ 27 | Stack: []map[vm.Register]*ast.Literal{registers}, 28 | } 29 | assert.NoError(t, ins.Execute(nil, vm)) 30 | assert.Equal(t, test.expected, registers[ins.Result].Value) 31 | }) 32 | } 33 | } 34 | 35 | func TestAdd_String(t *testing.T) { 36 | ins := &vm.Add{Left: "0", Right: "1", Result: "2"} 37 | assert.Equal(t, "$2 = $0 + $1", ins.String()) 38 | } 39 | -------------------------------------------------------------------------------- /vm/and.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | ) 8 | 9 | // And is a logical AND between two bools. 10 | type And struct { 11 | Left, Right, Result Register 12 | } 13 | 14 | // Execute implements the Instruction interface for the VM. 15 | func (ins *And) Execute(_ *int, vm *VM) error { 16 | vm.Set(ins.Result, asttest.NewLiteralBool( 17 | (vm.Get(ins.Left).Value == "true") && 18 | (vm.Get(ins.Right).Value == "true"), 19 | )) 20 | 21 | return nil 22 | } 23 | 24 | // String is the human-readable description of the instruction. 25 | func (ins *And) String() string { 26 | return fmt.Sprintf("%s = %s and %s", ins.Result, ins.Left, ins.Right) 27 | } 28 | -------------------------------------------------------------------------------- /vm/and_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/ast/asttest" 8 | "github.com/elliotchance/ok/vm" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestAnd_Execute(t *testing.T) { 14 | for testName, test := range map[string]struct { 15 | left, right bool 16 | expected string 17 | }{ 18 | "false-false": {false, false, "false"}, 19 | "false-true": {false, true, "false"}, 20 | "true-false": {true, false, "false"}, 21 | "true-true": {true, true, "true"}, 22 | } { 23 | t.Run(testName, func(t *testing.T) { 24 | registers := map[vm.Register]*ast.Literal{ 25 | "0": asttest.NewLiteralBool(test.left), 26 | "1": asttest.NewLiteralBool(test.right), 27 | } 28 | ins := &vm.And{Left: "0", Right: "1", Result: "2"} 29 | vm := &vm.VM{ 30 | Stack: []map[vm.Register]*ast.Literal{registers}, 31 | } 32 | assert.NoError(t, ins.Execute(nil, vm)) 33 | assert.Equal(t, test.expected, registers[ins.Result].Value) 34 | }) 35 | } 36 | } 37 | 38 | func TestAnd_String(t *testing.T) { 39 | ins := &vm.And{Left: "0", Right: "1", Result: "2"} 40 | assert.Equal(t, "$2 = $0 and $1", ins.String()) 41 | } 42 | -------------------------------------------------------------------------------- /vm/append.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | ) 8 | 9 | // Append returns an array by combining two other arrays. 10 | type Append struct { 11 | A, B, Result Register 12 | } 13 | 14 | // Execute implements the Instruction interface for the VM. 15 | func (ins *Append) Execute(_ *int, vm *VM) error { 16 | vm.Set(ins.Result, &ast.Literal{ 17 | Kind: vm.Get(ins.A).Kind, 18 | Array: append(vm.Get(ins.A).Array, vm.Get(ins.B).Array...), 19 | }) 20 | 21 | return nil 22 | } 23 | 24 | // String is the human-readable description of the instruction. 25 | func (ins *Append) String() string { 26 | return fmt.Sprintf("%s = append(%s, %s)", ins.Result, ins.A, ins.B) 27 | } 28 | -------------------------------------------------------------------------------- /vm/append_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/vm" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestAppend_String(t *testing.T) { 11 | ins := &vm.Append{A: "0", B: "1", Result: "2"} 12 | assert.Equal(t, "$2 = append($0, $1)", ins.String()) 13 | } 14 | -------------------------------------------------------------------------------- /vm/array_alloc.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/number" 8 | ) 9 | 10 | // ArrayAlloc allocates an array of fixed size. 11 | type ArrayAlloc struct { 12 | Size, Result Register 13 | Kind TypeRegister 14 | } 15 | 16 | // Execute implements the Instruction interface for the VM. 17 | func (ins *ArrayAlloc) Execute(_ *int, vm *VM) error { 18 | size := number.Int64(number.NewNumber(vm.Get(ins.Size).Value)) 19 | kind := vm.Types[ins.Kind] 20 | 21 | vm.Set(ins.Result, &ast.Literal{ 22 | Kind: kind, 23 | Array: make([]*ast.Literal, size), 24 | }) 25 | 26 | return nil 27 | } 28 | 29 | // String is the human-readable description of the instruction. 30 | func (ins *ArrayAlloc) String() string { 31 | return fmt.Sprintf("%s = %s with %s elements", ins.Result, ins.Kind, ins.Size) 32 | } 33 | -------------------------------------------------------------------------------- /vm/array_alloc_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/vm" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestArrayAlloc_String(t *testing.T) { 11 | ins := &vm.ArrayAlloc{Size: "0", Result: "1", Kind: "7"} 12 | assert.Equal(t, "$1 = 7 with $0 elements", ins.String()) 13 | } 14 | -------------------------------------------------------------------------------- /vm/array_get.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/number" 7 | ) 8 | 9 | // ArrayGet gets a value from the array by its index. 10 | type ArrayGet struct { 11 | Array, Index, Result Register 12 | } 13 | 14 | // Execute implements the Instruction interface for the VM. 15 | func (ins *ArrayGet) Execute(_ *int, vm *VM) error { 16 | index := number.Int64(number.NewNumber(vm.Get(ins.Index).Value)) 17 | vm.Set(ins.Result, vm.Get(ins.Array).Array[index]) 18 | 19 | return nil 20 | } 21 | 22 | // String is the human-readable description of the instruction. 23 | func (ins *ArrayGet) String() string { 24 | return fmt.Sprintf("%s = %s[%s]", ins.Result, ins.Array, ins.Index) 25 | } 26 | -------------------------------------------------------------------------------- /vm/array_get_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/vm" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestArrayGet_String(t *testing.T) { 11 | ins := &vm.ArrayGet{Array: "0", Index: "1", Result: "2"} 12 | assert.Equal(t, "$2 = $0[$1]", ins.String()) 13 | } 14 | -------------------------------------------------------------------------------- /vm/array_set.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/number" 7 | ) 8 | 9 | // ArraySet sets a number value to an index. 10 | type ArraySet struct { 11 | Array, Index, Value Register 12 | } 13 | 14 | // Execute implements the Instruction interface for the VM. 15 | func (ins *ArraySet) Execute(_ *int, vm *VM) error { 16 | index := number.Int64(number.NewNumber(vm.Get(ins.Index).Value)) 17 | vm.Get(ins.Array).Array[index] = vm.Get(ins.Value) 18 | 19 | return nil 20 | } 21 | 22 | // String is the human-readable description of the instruction. 23 | func (ins *ArraySet) String() string { 24 | return fmt.Sprintf("%s[%s] = %s", ins.Array, ins.Index, ins.Value) 25 | } 26 | -------------------------------------------------------------------------------- /vm/array_set_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/vm" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestArraySet_String(t *testing.T) { 11 | ins := &vm.ArraySet{Array: "0", Index: "1", Value: "2"} 12 | assert.Equal(t, "$0[$1] = $2", ins.String()) 13 | } 14 | -------------------------------------------------------------------------------- /vm/assert.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Assert is used in tests. 8 | type Assert struct { 9 | Left, Right, Final Register 10 | Op string 11 | Pos string 12 | } 13 | 14 | // Execute implements the Instruction interface for the VM. 15 | func (ins *Assert) Execute(_ *int, vm *VM) error { 16 | pass := vm.Get(ins.Final).Value == "true" 17 | left := renderLiteral(vm.Get(ins.Left), true) 18 | right := renderLiteral(vm.Get(ins.Right), true) 19 | vm.assert(pass, left, ins.Op, right, ins.Pos) 20 | 21 | return nil 22 | } 23 | 24 | // String is the human-readable description of the instruction. 25 | func (ins *Assert) String() string { 26 | return fmt.Sprintf("assert(%s %s %s)", ins.Left, ins.Op, ins.Right) 27 | } 28 | -------------------------------------------------------------------------------- /vm/assert_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/vm" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestAssert_String(t *testing.T) { 11 | ins := &vm.Assert{Left: "0", Right: "1", Final: "2", Op: "==", Pos: "pos"} 12 | assert.Equal(t, "assert($0 == $1)", ins.String()) 13 | } 14 | -------------------------------------------------------------------------------- /vm/assign_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/ast/asttest" 8 | "github.com/elliotchance/ok/vm" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestAssignSymbol_Execute(t *testing.T) { 14 | registers := map[vm.Register]*ast.Literal{} 15 | ins := &vm.AssignSymbol{ 16 | Result: "1", 17 | Symbol: "123", 18 | } 19 | vm := &vm.VM{ 20 | Stack: []map[vm.Register]*ast.Literal{registers}, 21 | Symbols: map[vm.SymbolRegister]*ast.Literal{ 22 | "123": asttest.NewLiteralNumber("1.5"), 23 | }, 24 | } 25 | assert.NoError(t, ins.Execute(nil, vm)) 26 | assert.Equal(t, "1.5", registers["1"].Value) 27 | } 28 | 29 | func TestAssign_Execute(t *testing.T) { 30 | registers := map[vm.Register]*ast.Literal{ 31 | "0": asttest.NewLiteralNumber("1.5"), 32 | } 33 | ins := &vm.Assign{ 34 | Result: "1", 35 | Register: "0", 36 | } 37 | vm := &vm.VM{ 38 | Stack: []map[vm.Register]*ast.Literal{registers}, 39 | } 40 | assert.NoError(t, ins.Execute(nil, vm)) 41 | assert.Equal(t, "1.5", registers["1"].Value) 42 | } 43 | -------------------------------------------------------------------------------- /vm/call_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/vm" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCall_String(t *testing.T) { 11 | ins := &vm.Call{ 12 | FunctionName: "foo", 13 | Arguments: []vm.Register{"1", "2"}, 14 | Results: []vm.Register{"4", "5"}, 15 | } 16 | assert.Equal(t, "($4, $5) = foo($1, $2)", ins.String()) 17 | } 18 | -------------------------------------------------------------------------------- /vm/cast_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/vm" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCastString_String(t *testing.T) { 11 | ins := &vm.CastString{X: "1", Result: "2"} 12 | assert.Equal(t, "$2 = string $1", ins.String()) 13 | } 14 | 15 | func TestCastNumber_String(t *testing.T) { 16 | ins := &vm.CastNumber{X: "1", Result: "2"} 17 | assert.Equal(t, "$2 = number $1", ins.String()) 18 | } 19 | 20 | func TestCastChar_String(t *testing.T) { 21 | ins := &vm.CastChar{X: "1", Result: "2"} 22 | assert.Equal(t, "$2 = char $1", ins.String()) 23 | } 24 | -------------------------------------------------------------------------------- /vm/close.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Close closes a file handle. 8 | type Close struct { 9 | Fd Register 10 | } 11 | 12 | // Execute implements the Instruction interface for the VM. 13 | func (ins *Close) Execute(_ *int, vm *VM) error { 14 | f := vm.Get(ins.Fd).File 15 | err := f.Close() 16 | if err != nil { 17 | vm.Raise(err.Error()) 18 | } 19 | 20 | return nil 21 | } 22 | 23 | // String is the human-readable description of the instruction. 24 | func (ins *Close) String() string { 25 | return fmt.Sprintf("os.Close(%s)", ins.Fd) 26 | } 27 | -------------------------------------------------------------------------------- /vm/combine.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | ) 8 | 9 | // Combine will create a new data by joining two other datas. 10 | type Combine struct { 11 | Left, Right, Result Register 12 | } 13 | 14 | // Execute implements the Instruction interface for the VM. 15 | func (ins *Combine) Execute(_ *int, vm *VM) error { 16 | vm.Set(ins.Result, asttest.NewLiteralData( 17 | []byte(vm.Get(ins.Left).Value+vm.Get(ins.Right).Value))) 18 | 19 | return nil 20 | } 21 | 22 | // String is the human-readable description of the instruction. 23 | func (ins *Combine) String() string { 24 | return fmt.Sprintf("%s = combine(%s, %s)", ins.Result, ins.Left, ins.Right) 25 | } 26 | -------------------------------------------------------------------------------- /vm/combine_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/ast/asttest" 8 | "github.com/elliotchance/ok/vm" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestCombine_Execute(t *testing.T) { 14 | for testName, test := range map[string]struct { 15 | left, right []byte 16 | expected string 17 | }{ 18 | "both-empty": {[]byte(""), []byte(""), ""}, 19 | "both-non-empty": {[]byte("foo"), []byte("bar"), "foobar"}, 20 | } { 21 | t.Run(testName, func(t *testing.T) { 22 | registers := map[vm.Register]*ast.Literal{ 23 | "0": asttest.NewLiteralData(test.left), 24 | "1": asttest.NewLiteralData(test.right), 25 | } 26 | ins := &vm.Combine{Left: "0", Right: "1", Result: "2"} 27 | vm := &vm.VM{ 28 | Stack: []map[vm.Register]*ast.Literal{registers}, 29 | } 30 | assert.NoError(t, ins.Execute(nil, vm)) 31 | assert.Equal(t, test.expected, registers[ins.Result].Value) 32 | }) 33 | } 34 | } 35 | 36 | func TestCombine_String(t *testing.T) { 37 | ins := &vm.Combine{Left: "1", Right: "2", Result: "3"} 38 | assert.Equal(t, "$3 = combine($1, $2)", ins.String()) 39 | } 40 | -------------------------------------------------------------------------------- /vm/concat.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | ) 8 | 9 | // Concat will create a new string by joining two other strings. 10 | type Concat struct { 11 | Left, Right, Result Register 12 | } 13 | 14 | // Execute implements the Instruction interface for the VM. 15 | func (ins *Concat) Execute(_ *int, vm *VM) error { 16 | vm.Set(ins.Result, asttest.NewLiteralString( 17 | vm.Get(ins.Left).Value+vm.Get(ins.Right).Value)) 18 | 19 | return nil 20 | } 21 | 22 | // String is the human-readable description of the instruction. 23 | func (ins *Concat) String() string { 24 | return fmt.Sprintf("%s = concat(%s, %s)", ins.Result, ins.Left, ins.Right) 25 | } 26 | -------------------------------------------------------------------------------- /vm/concat_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/ast/asttest" 8 | "github.com/elliotchance/ok/vm" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestConcat_Execute(t *testing.T) { 14 | for testName, test := range map[string]struct { 15 | left, right string 16 | expected string 17 | }{ 18 | "both-empty": {"", "", ""}, 19 | "both-non-empty": {"foo", "bar", "foobar"}, 20 | } { 21 | t.Run(testName, func(t *testing.T) { 22 | registers := map[vm.Register]*ast.Literal{ 23 | "0": asttest.NewLiteralString(test.left), 24 | "1": asttest.NewLiteralString(test.right), 25 | } 26 | ins := &vm.Concat{Left: "0", Right: "1", Result: "2"} 27 | vm := &vm.VM{ 28 | Stack: []map[vm.Register]*ast.Literal{registers}, 29 | } 30 | assert.NoError(t, ins.Execute(nil, vm)) 31 | assert.Equal(t, test.expected, registers[ins.Result].Value) 32 | }) 33 | } 34 | } 35 | 36 | func TestConcat_String(t *testing.T) { 37 | ins := &vm.Concat{Left: "1", Right: "2", Result: "3"} 38 | assert.Equal(t, "$3 = concat($1, $2)", ins.String()) 39 | } 40 | -------------------------------------------------------------------------------- /vm/divide.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | "github.com/elliotchance/ok/number" 8 | ) 9 | 10 | // Divide will multiply two numbers. 11 | type Divide struct { 12 | Left, Right, Result Register 13 | } 14 | 15 | // Execute implements the Instruction interface for the VM. 16 | func (ins *Divide) Execute(_ *int, vm *VM) error { 17 | divide, err := number.Divide( 18 | number.NewNumber(vm.Get(ins.Left).Value), 19 | number.NewNumber(vm.Get(ins.Right).Value), 20 | ) 21 | if err != nil { 22 | // TODO(elliot): This needs to be the same precision of zero. 23 | vm.Set(ins.Result, asttest.NewLiteralNumber("0")) 24 | return err 25 | } 26 | 27 | vm.Set(ins.Result, asttest.NewLiteralNumber(divide.String())) 28 | 29 | return nil 30 | } 31 | 32 | // String is the human-readable description of the instruction. 33 | func (ins *Divide) String() string { 34 | return fmt.Sprintf("%s = %s / %s", ins.Result, ins.Left, ins.Right) 35 | } 36 | -------------------------------------------------------------------------------- /vm/doc.go: -------------------------------------------------------------------------------- 1 | // Package instruction contains all the instructions created by the compiler and 2 | // executed by the VM. 3 | package vm 4 | -------------------------------------------------------------------------------- /vm/error_scope.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // On is a pragma for the vm to handle errors. It can also be used to indicate 8 | // the end of the on's so that the VM can send the error up to the caller. 9 | type On struct { 10 | // Type will be the name of the error, or it should be ignored if Finished 11 | // is true to signal there are no more errors to check. 12 | Type TypeRegister 13 | 14 | // If the VM hits a Finished = true it will return and pass the error up to 15 | // the caller. 16 | Finished bool 17 | } 18 | 19 | // Execute implements the Instruction interface for the VM. 20 | func (ins *On) Execute(_ *int, _ *VM) error { 21 | // Nothing happens here because On is just a pragma for the VM to look 22 | // forward to find the error handler. 23 | return nil 24 | } 25 | 26 | // String is the human-readable description of the instruction. 27 | func (ins *On) String() string { 28 | return fmt.Sprintf("on %s", ins.Type) 29 | } 30 | -------------------------------------------------------------------------------- /vm/exit.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/elliotchance/ok/number" 8 | ) 9 | 10 | // Exit is the process exit with status code. 11 | type Exit struct { 12 | Status Register // In 13 | } 14 | 15 | // Execute implements the Instruction interface for the VM. 16 | func (ins *Exit) Execute(_ *int, vm *VM) error { 17 | os.Exit(number.Int(number.NewNumber(vm.Get(ins.Status).Value))) 18 | 19 | return nil 20 | } 21 | 22 | // String is the human-readable description of the instruction. 23 | func (ins *Exit) String() string { 24 | return fmt.Sprintf("runtime.Exit(%s)", ins.Status) 25 | } 26 | -------------------------------------------------------------------------------- /vm/finally.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Finally will activate or deactivate a finally block. 8 | type Finally struct { 9 | Index int 10 | Run bool 11 | } 12 | 13 | // Execute implements the Instruction interface for the VM. 14 | func (ins *Finally) Execute(_ *int, vm *VM) error { 15 | vm.FinallyBlocks[len(vm.FinallyBlocks)-1][ins.Index].Run = ins.Run 16 | 17 | return nil 18 | } 19 | 20 | // String is the human-readable description of the instruction. 21 | func (ins *Finally) String() string { 22 | return fmt.Sprintf("finally %d", ins.Index) 23 | } 24 | -------------------------------------------------------------------------------- /vm/from_unix.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/cockroachdb/apd/v2" 8 | "github.com/elliotchance/ok/number" 9 | ) 10 | 11 | // FromUnix returns a time components from a unix timestamp. 12 | type FromUnix struct { 13 | Seconds Register // in 14 | Year, Month, Day, Hour, Minute, Second Register // out 15 | } 16 | 17 | // Execute implements the Instruction interface for the VM. 18 | func (ins *FromUnix) Execute(_ *int, vm *VM) error { 19 | t := vm.Get(ins.Seconds) 20 | 21 | rawSeconds := number.NewNumber(t.Value) 22 | seconds := apd.New(1, 0) 23 | nanoseconds := apd.New(1, 0) 24 | rawSeconds.Modf(seconds, nanoseconds) 25 | nanoseconds = number.Multiply(nanoseconds, apd.New(1, 9)) 26 | 27 | tm := time.Unix(number.Int64(seconds), number.Int64(nanoseconds)).UTC() 28 | setTimeLiterals(vm, tm, ins.Year, ins.Month, ins.Day, ins.Hour, ins.Minute, ins.Second) 29 | 30 | return nil 31 | } 32 | 33 | // String is the human-readable description of the instruction. 34 | func (ins *FromUnix) String() string { 35 | return fmt.Sprintf("%s, %s, %s, %s, %s, %s = time.FromUnix(%s)", 36 | ins.Year, ins.Month, ins.Day, ins.Hour, ins.Minute, ins.Second, 37 | ins.Seconds) 38 | } 39 | -------------------------------------------------------------------------------- /vm/instruction.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // An Instruction can be executed by the VM. 8 | type Instruction interface { 9 | // Stringer provides human-readable descriptions of instructions. It's 10 | // helpful for debugging and used directly by "ok asm". 11 | fmt.Stringer 12 | 13 | Execute(i *int, vm *VM) error 14 | } 15 | -------------------------------------------------------------------------------- /vm/interface.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | ) 8 | 9 | // Interface assigns the runtime interface of a value to a string destination. 10 | type Interface struct { 11 | Value, Result Register 12 | } 13 | 14 | // Execute implements the Instruction interface for the VM. 15 | func (ins *Interface) Execute(_ *int, vm *VM) error { 16 | i := vm.Get(ins.Value).Kind.Interface() 17 | vm.Set(ins.Result, asttest.NewLiteralString(i)) 18 | 19 | return nil 20 | } 21 | 22 | // String is the human-readable description of the instruction. 23 | func (ins *Interface) String() string { 24 | return fmt.Sprintf("%s = reflect.Interface(%s)", ins.Result, ins.Value) 25 | } 26 | -------------------------------------------------------------------------------- /vm/is.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | ) 8 | 9 | // Is checks a type at runtime. 10 | type Is struct { 11 | Value Register // In any 12 | Type Register // In string 13 | Result Register // Out bool 14 | } 15 | 16 | // Execute implements the Instruction interface for the VM. 17 | func (ins *Is) Execute(_ *int, vm *VM) error { 18 | vm.Set(ins.Result, asttest.NewLiteralBool( 19 | vm.Get(ins.Value).Kind.String() == vm.Get(ins.Type).Value, 20 | )) 21 | 22 | return nil 23 | } 24 | 25 | // String is the human-readable description of the instruction. 26 | func (ins *Is) String() string { 27 | return fmt.Sprintf("%s = %s is %s", ins.Result, ins.Value, ins.Type) 28 | } 29 | -------------------------------------------------------------------------------- /vm/jump.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Jump will jump to the instruction. 8 | type Jump struct { 9 | To int 10 | } 11 | 12 | // Execute implements the Instruction interface for the VM. 13 | func (ins *Jump) Execute(i *int, vm *VM) error { 14 | *i = ins.To 15 | 16 | return nil 17 | } 18 | 19 | // String is the human-readable description of the instruction. 20 | func (ins *Jump) String() string { 21 | return fmt.Sprintf("jump to #%d", ins.To) 22 | } 23 | -------------------------------------------------------------------------------- /vm/jump_unless.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // JumpUnless will jump to the instruction if the expression is false. 8 | type JumpUnless struct { 9 | Condition Register 10 | To int 11 | } 12 | 13 | // Execute implements the Instruction interface for the VM. 14 | func (ins *JumpUnless) Execute(i *int, vm *VM) error { 15 | if vm.Get(ins.Condition).Value != "true" { 16 | *i = ins.To 17 | } 18 | 19 | return nil 20 | } 21 | 22 | // String is the human-readable description of the instruction. 23 | func (ins *JumpUnless) String() string { 24 | return fmt.Sprintf("if %s is false then jump to #%d", ins.Condition, ins.To) 25 | } 26 | -------------------------------------------------------------------------------- /vm/len.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | "github.com/elliotchance/ok/types" 8 | ) 9 | 10 | // Len is used to determine the size of an array or map. 11 | type Len struct { 12 | Argument, Result Register 13 | } 14 | 15 | // Execute implements the Instruction interface for the VM. 16 | func (ins *Len) Execute(_ *int, vm *VM) error { 17 | r := vm.Get(ins.Argument) 18 | var result int 19 | switch r.Kind.Kind { 20 | case types.KindArray: 21 | result = len(r.Array) 22 | 23 | case types.KindMap: 24 | result = len(r.Map) 25 | 26 | case types.KindString: 27 | result = len([]rune(r.Value)) 28 | 29 | default: 30 | result = len(r.Value) 31 | } 32 | 33 | vm.Set(ins.Result, asttest.NewLiteralNumber(fmt.Sprintf("%d", result))) 34 | 35 | return nil 36 | } 37 | 38 | // String is the human-readable description of the instruction. 39 | func (ins *Len) String() string { 40 | return fmt.Sprintf("%s = len(%s)", ins.Result, ins.Argument) 41 | } 42 | -------------------------------------------------------------------------------- /vm/len_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/ast/asttest" 8 | "github.com/elliotchance/ok/vm" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestLen_Execute(t *testing.T) { 14 | for testName, test := range map[string]struct { 15 | x *ast.Literal 16 | expected *ast.Literal 17 | }{ 18 | // TODO(elliot): Tests for array and map. 19 | "string": { 20 | asttest.NewLiteralString("foo bar"), 21 | asttest.NewLiteralNumber("7"), 22 | }, 23 | } { 24 | t.Run(testName, func(t *testing.T) { 25 | registers := map[vm.Register]*ast.Literal{ 26 | "0": test.x, 27 | } 28 | ins := &vm.Len{Argument: "0", Result: "1"} 29 | vm := &vm.VM{ 30 | Stack: []map[vm.Register]*ast.Literal{registers}, 31 | } 32 | assert.NoError(t, ins.Execute(nil, vm)) 33 | assert.Equal(t, test.expected, registers[ins.Result]) 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /vm/log.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | "github.com/elliotchance/ok/number" 8 | ) 9 | 10 | // Log is a natural logarithm (base e). 11 | type Log struct { 12 | X, Result Register 13 | } 14 | 15 | // Execute implements the Instruction interface for the VM. 16 | func (ins *Log) Execute(_ *int, vm *VM) error { 17 | vm.Set(ins.Result, asttest.NewLiteralNumber( 18 | number.Log( 19 | number.NewNumber(vm.Get(ins.X).Value), 20 | ).String(), 21 | )) 22 | 23 | return nil 24 | } 25 | 26 | // String is the human-readable description of the instruction. 27 | func (ins *Log) String() string { 28 | return fmt.Sprintf("%s = log(%s)", ins.Result, ins.X) 29 | } 30 | -------------------------------------------------------------------------------- /vm/map_alloc.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/number" 8 | ) 9 | 10 | // MapAlloc allocates a map of fixed size. 11 | type MapAlloc struct { 12 | Kind TypeRegister 13 | Size, Result Register 14 | } 15 | 16 | // Execute implements the Instruction interface for the VM. 17 | func (ins *MapAlloc) Execute(_ *int, vm *VM) error { 18 | size := number.Int64(number.NewNumber(vm.Get(ins.Size).Value)) 19 | 20 | vm.Set(ins.Result, &ast.Literal{ 21 | Kind: vm.Types[ins.Kind], 22 | 23 | // Array must also be allocated because it will contain the keys for the 24 | // map. 25 | Array: make([]*ast.Literal, 0, size), 26 | 27 | Map: make(map[string]*ast.Literal, size), 28 | }) 29 | 30 | return nil 31 | } 32 | 33 | // String is the human-readable description of the instruction. 34 | func (ins *MapAlloc) String() string { 35 | return fmt.Sprintf("%s = {}any with %s elements", ins.Result, ins.Size) 36 | } 37 | -------------------------------------------------------------------------------- /vm/map_get.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // MapGet gets a value from the map by its key. 8 | type MapGet struct { 9 | Map, Key, Result Register 10 | } 11 | 12 | // Execute implements the Instruction interface for the VM. 13 | func (ins *MapGet) Execute(_ *int, vm *VM) error { 14 | key := vm.Get(ins.Key).Value 15 | vm.Set(ins.Result, vm.Get(ins.Map).Map[key]) 16 | 17 | return nil 18 | } 19 | 20 | // String is the human-readable description of the instruction. 21 | func (ins *MapGet) String() string { 22 | return fmt.Sprintf("%s = %s[%s]", ins.Result, ins.Map, ins.Key) 23 | } 24 | -------------------------------------------------------------------------------- /vm/map_set.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // MapSet sets a number value to an index. 8 | type MapSet struct { 9 | Map, Key, Value Register 10 | } 11 | 12 | // Execute implements the Instruction interface for the VM. 13 | func (ins *MapSet) Execute(_ *int, vm *VM) error { 14 | key := vm.Get(ins.Key).Value 15 | vm.Get(ins.Map).Map[key] = vm.Get(ins.Value).Copy() 16 | vm.Get(ins.Map).Array = append(vm.Get(ins.Map).Array, vm.Get(ins.Key).Copy()) 17 | 18 | return nil 19 | } 20 | 21 | // String is the human-readable description of the instruction. 22 | func (ins *MapSet) String() string { 23 | return fmt.Sprintf("%s[%s] = %s", ins.Map, ins.Key, ins.Value) 24 | } 25 | -------------------------------------------------------------------------------- /vm/mkdir.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | // Mkdir creates a directory for Path and any directories needed along the way. 9 | type Mkdir struct { 10 | Path Register // In 11 | } 12 | 13 | // Execute implements the Instruction interface for the VM. 14 | func (ins *Mkdir) Execute(_ *int, vm *VM) error { 15 | path := vm.Get(ins.Path).Value 16 | err := os.MkdirAll(path, 0666) 17 | if err != nil { 18 | vm.Raise(err.Error()) 19 | } 20 | 21 | return nil 22 | } 23 | 24 | // String is the human-readable description of the instruction. 25 | func (ins *Mkdir) String() string { 26 | return fmt.Sprintf("os.CreateDirectory(%s)", ins.Path) 27 | } 28 | -------------------------------------------------------------------------------- /vm/multiply.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | "github.com/elliotchance/ok/number" 8 | ) 9 | 10 | // Multiply will multiply two numbers. 11 | type Multiply struct { 12 | Left, Right, Result Register 13 | } 14 | 15 | // Execute implements the Instruction interface for the VM. 16 | func (ins *Multiply) Execute(_ *int, vm *VM) error { 17 | vm.Set(ins.Result, asttest.NewLiteralNumber( 18 | number.Multiply( 19 | number.NewNumber(vm.Get(ins.Left).Value), 20 | number.NewNumber(vm.Get(ins.Right).Value), 21 | ).String(), 22 | )) 23 | 24 | return nil 25 | } 26 | 27 | // String is the human-readable description of the instruction. 28 | func (ins *Multiply) String() string { 29 | return fmt.Sprintf("%s = %s * %s", ins.Result, ins.Left, ins.Right) 30 | } 31 | -------------------------------------------------------------------------------- /vm/multiply_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/ast/asttest" 8 | "github.com/elliotchance/ok/number" 9 | "github.com/elliotchance/ok/vm" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestMultiply_Execute(t *testing.T) { 15 | for testName, test := range map[string]struct { 16 | left, right string 17 | expected string 18 | }{ 19 | "success": {"1.2200", "4.7", "5.734"}, 20 | } { 21 | t.Run(testName, func(t *testing.T) { 22 | registers := map[vm.Register]*ast.Literal{ 23 | "0": asttest.NewLiteralNumber(test.left), 24 | "1": asttest.NewLiteralNumber(test.right), 25 | } 26 | ins := &vm.Multiply{Left: "0", Right: "1", Result: "2"} 27 | vm := &vm.VM{ 28 | Stack: []map[vm.Register]*ast.Literal{registers}, 29 | } 30 | assert.NoError(t, ins.Execute(nil, vm)) 31 | actual := number.NewNumber(registers[ins.Result].Value) 32 | assert.Equal(t, test.expected, number.Format(actual, -1)) 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /vm/not.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | ) 8 | 9 | // Not is a logical NOT of a bool. 10 | type Not struct { 11 | Left, Result Register 12 | } 13 | 14 | // Execute implements the Instruction interface for the VM. 15 | func (ins *Not) Execute(_ *int, vm *VM) error { 16 | vm.Set(ins.Result, asttest.NewLiteralBool( 17 | !(vm.Get(ins.Left).Value == "true"), 18 | )) 19 | 20 | return nil 21 | } 22 | 23 | // String is the human-readable description of the instruction. 24 | func (ins *Not) String() string { 25 | return fmt.Sprintf("%s = not %s", ins.Result, ins.Left) 26 | } 27 | -------------------------------------------------------------------------------- /vm/not_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/ast/asttest" 8 | "github.com/elliotchance/ok/vm" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestNot_Execute(t *testing.T) { 14 | for testName, test := range map[string]struct { 15 | left bool 16 | expected string 17 | }{ 18 | "false": {false, "true"}, 19 | "true": {true, "false"}, 20 | } { 21 | t.Run(testName, func(t *testing.T) { 22 | registers := map[vm.Register]*ast.Literal{ 23 | "0": asttest.NewLiteralBool(test.left), 24 | } 25 | ins := &vm.Not{Left: "0", Result: "1"} 26 | vm := &vm.VM{ 27 | Stack: []map[vm.Register]*ast.Literal{registers}, 28 | } 29 | assert.NoError(t, ins.Execute(nil, vm)) 30 | assert.Equal(t, test.expected, registers[ins.Result].Value) 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vm/open.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/elliotchance/ok/ast" 8 | "github.com/elliotchance/ok/types" 9 | ) 10 | 11 | // Open opens a file. 12 | type Open struct { 13 | Path, Result Register 14 | } 15 | 16 | // Execute implements the Instruction interface for the VM. 17 | func (ins *Open) Execute(_ *int, vm *VM) error { 18 | f, err := os.OpenFile(vm.Get(ins.Path).Value, os.O_RDWR|os.O_CREATE, 0666) 19 | if err != nil { 20 | vm.Raise(err.Error()) 21 | } 22 | 23 | vm.Set(ins.Result, &ast.Literal{ 24 | Kind: types.Data, 25 | File: f, 26 | }) 27 | 28 | return nil 29 | } 30 | 31 | // String is the human-readable description of the instruction. 32 | func (ins *Open) String() string { 33 | return fmt.Sprintf("%s = os.Open(%s)", ins.Result, ins.Path) 34 | } 35 | -------------------------------------------------------------------------------- /vm/or.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | ) 8 | 9 | // Or is a logical OR between two bools. 10 | type Or struct { 11 | Left, Right, Result Register 12 | } 13 | 14 | // Execute implements the Instruction interface for the VM. 15 | func (ins *Or) Execute(_ *int, vm *VM) error { 16 | vm.Set(ins.Result, asttest.NewLiteralBool( 17 | (vm.Get(ins.Left).Value == "true") || 18 | (vm.Get(ins.Right).Value == "true"), 19 | )) 20 | 21 | return nil 22 | } 23 | 24 | // String is the human-readable description of the instruction. 25 | func (ins *Or) String() string { 26 | return fmt.Sprintf("%s = %s or %s", ins.Result, ins.Left, ins.Right) 27 | } 28 | -------------------------------------------------------------------------------- /vm/or_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/ast/asttest" 8 | "github.com/elliotchance/ok/vm" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestOr_Execute(t *testing.T) { 14 | for testName, test := range map[string]struct { 15 | left, right bool 16 | expected string 17 | }{ 18 | "false-false": {false, false, "false"}, 19 | "false-true": {false, true, "true"}, 20 | "true-false": {true, false, "true"}, 21 | "true-true": {true, true, "true"}, 22 | } { 23 | t.Run(testName, func(t *testing.T) { 24 | registers := map[vm.Register]*ast.Literal{ 25 | "0": asttest.NewLiteralBool(test.left), 26 | "1": asttest.NewLiteralBool(test.right), 27 | } 28 | ins := &vm.Or{Left: "0", Right: "1", Result: "2"} 29 | vm := &vm.VM{ 30 | Stack: []map[vm.Register]*ast.Literal{registers}, 31 | } 32 | assert.NoError(t, ins.Execute(nil, vm)) 33 | assert.Equal(t, test.expected, registers[ins.Result].Value) 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /vm/parent_scope.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // ParentScope sets the parent scope of a function literal. 8 | type ParentScope struct { 9 | X Register 10 | } 11 | 12 | // Execute implements the Instruction interface for the VM. 13 | func (ins *ParentScope) Execute(_ *int, vm *VM) error { 14 | vm.Stack[len(vm.Stack)-1][ins.X].Map = vm.Stack[len(vm.Stack)-1][StateRegister].Map 15 | 16 | return nil 17 | } 18 | 19 | func (ins *ParentScope) String() string { 20 | return fmt.Sprintf("%s", ins.X) 21 | } 22 | -------------------------------------------------------------------------------- /vm/path.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "path" 5 | "strings" 6 | ) 7 | 8 | // TODO(elliot): This should be configurable through an environment variable. 9 | const Directory = "/tmp/okc" 10 | 11 | // PathForPackage returns the absolute path of where the JSON file should exist 12 | // for a package name. However, the path returned may not exist. 13 | func PathForPackage(packageName string) string { 14 | return path.Join(Directory, 15 | strings.ReplaceAll(packageName, "/", "__")+".json") 16 | } 17 | -------------------------------------------------------------------------------- /vm/power.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | "github.com/elliotchance/ok/number" 8 | ) 9 | 10 | // Power is Left to the power of Right. 11 | type Power struct { 12 | Base, Power, Result Register 13 | } 14 | 15 | // Execute implements the Instruction interface for the VM. 16 | func (ins *Power) Execute(_ *int, vm *VM) error { 17 | vm.Set(ins.Result, asttest.NewLiteralNumber( 18 | number.Pow( 19 | number.NewNumber(vm.Get(ins.Base).Value), 20 | number.NewNumber(vm.Get(ins.Power).Value), 21 | ).String(), 22 | )) 23 | 24 | return nil 25 | } 26 | 27 | // String is the human-readable description of the instruction. 28 | func (ins *Power) String() string { 29 | return fmt.Sprintf("%s = power(%s, %s)", ins.Result, ins.Base, ins.Power) 30 | } 31 | -------------------------------------------------------------------------------- /vm/print.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Print will output a string to stdout. 8 | type Print struct { 9 | Arguments Registers 10 | } 11 | 12 | // Execute implements the Instruction interface for the VM. 13 | func (ins *Print) Execute(_ *int, vm *VM) error { 14 | for i, register := range ins.Arguments { 15 | if i > 0 { 16 | fmt.Fprint(vm.Stdout, " ") 17 | } 18 | 19 | fmt.Fprint(vm.Stdout, renderLiteral(vm.Get(register), false)) 20 | } 21 | 22 | fmt.Fprint(vm.Stdout, "\n") 23 | 24 | return nil 25 | } 26 | 27 | // String is the human-readable description of the instruction. 28 | func (ins *Print) String() string { 29 | return fmt.Sprintf("print%s", ins.Arguments) 30 | } 31 | -------------------------------------------------------------------------------- /vm/raise.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Raise put the VM into an error mode. The VM will look for an error handler. 8 | type Raise struct { 9 | // Err is the register containing the error. 10 | Err Register 11 | 12 | // Type is used to match the handler. 13 | Type TypeRegister 14 | 15 | // Pos is used at the top of the stack trace to show where the error 16 | // originated from. 17 | Pos string 18 | } 19 | 20 | // Execute implements the Instruction interface for the VM. 21 | func (ins *Raise) Execute(_ *int, vm *VM) error { 22 | vm.ErrType = vm.Types[ins.Type] 23 | vm.ErrValue = vm.Get(ins.Err) 24 | vm.ErrStack = vm.captureCallStack(ins.Pos) 25 | 26 | return nil 27 | } 28 | 29 | // String is the human-readable description of the instruction. 30 | func (ins *Raise) String() string { 31 | return fmt.Sprintf("raise %s (%s)", ins.Err, ins.Type) 32 | } 33 | -------------------------------------------------------------------------------- /vm/rand.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | ) 8 | 9 | // Rand returns a random number between 0 and 1. 10 | type Rand struct { 11 | Result Register 12 | } 13 | 14 | // Execute implements the Instruction interface for the VM. 15 | func (ins *Rand) Execute(_ *int, vm *VM) error { 16 | val := vm.rand.Float64() 17 | vm.Set(ins.Result, asttest.NewLiteralNumber( 18 | fmt.Sprintf("%f", val), 19 | )) 20 | 21 | return nil 22 | } 23 | 24 | // String is the human-readable description of the instruction. 25 | func (ins *Rand) String() string { 26 | return fmt.Sprintf("%s = math.Rand()", ins.Result) 27 | } 28 | -------------------------------------------------------------------------------- /vm/register_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/vm" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestRegister_String(t *testing.T) { 11 | t.Run("zero", func(t *testing.T) { 12 | assert.Equal(t, "()", vm.Registers{}.String()) 13 | }) 14 | 15 | t.Run("one", func(t *testing.T) { 16 | assert.Equal(t, "($1)", vm.Registers{"1"}.String()) 17 | }) 18 | 19 | t.Run("two", func(t *testing.T) { 20 | assert.Equal(t, "($123, a7)", vm.Registers{"123", "a7"}.String()) 21 | }) 22 | } 23 | 24 | func TestRegisters_String(t *testing.T) { 25 | t.Run("empty", func(t *testing.T) { 26 | assert.Equal(t, "_", vm.Register("").String()) 27 | }) 28 | 29 | t.Run("register", func(t *testing.T) { 30 | assert.Equal(t, "$0", vm.Register("0").String()) 31 | assert.Equal(t, "$123", vm.Register("123").String()) 32 | }) 33 | 34 | // TODO(elliot): Using variable names as registers will be removed in the 35 | // future. 36 | t.Run("variable-name", func(t *testing.T) { 37 | assert.Equal(t, "foo", vm.Register("foo").String()) 38 | assert.Equal(t, "r1", vm.Register("r1").String()) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /vm/remainder.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | "github.com/elliotchance/ok/number" 8 | ) 9 | 10 | // Remainder will return the remainder when dividing two numbers. This is not 11 | // the same as a modulo. A remainder may be negative. 12 | type Remainder struct { 13 | Left, Right, Result Register 14 | } 15 | 16 | // Execute implements the Instruction interface for the VM. 17 | func (ins *Remainder) Execute(_ *int, vm *VM) error { 18 | divide, err := number.Remainder( 19 | number.NewNumber(vm.Get(ins.Left).Value), 20 | number.NewNumber(vm.Get(ins.Right).Value), 21 | ) 22 | if err != nil { 23 | // TODO(elliot): This needs to be the same precision of zero. 24 | vm.Set(ins.Result, asttest.NewLiteralNumber("0")) 25 | return err 26 | } 27 | 28 | vm.Set(ins.Result, asttest.NewLiteralNumber(divide.String())) 29 | 30 | return nil 31 | } 32 | 33 | // String is the human-readable description of the instruction. 34 | func (ins *Remainder) String() string { 35 | return fmt.Sprintf("%s = %s %% %s", ins.Result, ins.Left, ins.Right) 36 | } 37 | -------------------------------------------------------------------------------- /vm/remainder_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/elliotchance/ok/ast" 8 | "github.com/elliotchance/ok/ast/asttest" 9 | "github.com/elliotchance/ok/vm" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestRemainder_Execute(t *testing.T) { 15 | for testName, test := range map[string]struct { 16 | left, right string 17 | expected string 18 | err error 19 | }{ 20 | "maintain-precision": {"1.2200", "4.7", "1.2200", nil}, 21 | "divide-zero": {"1.2200", "0", "0", errors.New("division by zero")}, 22 | } { 23 | t.Run(testName, func(t *testing.T) { 24 | registers := map[vm.Register]*ast.Literal{ 25 | "0": asttest.NewLiteralNumber(test.left), 26 | "1": asttest.NewLiteralNumber(test.right), 27 | } 28 | ins := &vm.Remainder{Left: "0", Right: "1", Result: "2"} 29 | vm := &vm.VM{ 30 | Stack: []map[vm.Register]*ast.Literal{registers}, 31 | } 32 | assert.Equal(t, test.err, ins.Execute(nil, vm)) 33 | assert.Equal(t, test.expected, registers[ins.Result].Value) 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /vm/remove.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | // Remove removes (unlinks) a file. 9 | type Remove struct { 10 | Path Register // In 11 | } 12 | 13 | // Execute implements the Instruction interface for the VM. 14 | func (ins *Remove) Execute(_ *int, vm *VM) error { 15 | err := os.Remove(vm.Get(ins.Path).Value) 16 | if err != nil { 17 | vm.Raise(err.Error()) 18 | } 19 | 20 | return nil 21 | } 22 | 23 | // String is the human-readable description of the instruction. 24 | func (ins *Remove) String() string { 25 | return fmt.Sprintf("os.Remove(%s)", ins.Path) 26 | } 27 | -------------------------------------------------------------------------------- /vm/rename.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | // Rename renames (moves) a file. 9 | type Rename struct { 10 | OldPath Register // In 11 | NewPath Register // In 12 | } 13 | 14 | // Execute implements the Instruction interface for the VM. 15 | func (ins *Rename) Execute(_ *int, vm *VM) error { 16 | err := os.Rename(vm.Get(ins.OldPath).Value, vm.Get(ins.NewPath).Value) 17 | if err != nil { 18 | vm.Raise(err.Error()) 19 | } 20 | 21 | return nil 22 | } 23 | 24 | // String is the human-readable description of the instruction. 25 | func (ins *Rename) String() string { 26 | return fmt.Sprintf("os.Rename(%s, %s)", ins.OldPath, ins.NewPath) 27 | } 28 | -------------------------------------------------------------------------------- /vm/return.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Return tells the VM to jump out of this function. 8 | type Return struct { 9 | Results Registers 10 | } 11 | 12 | // Execute implements the Instruction interface for the VM. 13 | func (ins *Return) Execute(_ *int, vm *VM) error { 14 | vm.Return = ins.Results 15 | 16 | return nil 17 | } 18 | 19 | // String is the human-readable description of the instruction. 20 | func (ins *Return) String() string { 21 | return fmt.Sprintf("return %s", ins.Results) 22 | } 23 | -------------------------------------------------------------------------------- /vm/seek.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/elliotchance/ok/ast/asttest" 8 | "github.com/elliotchance/ok/number" 9 | ) 10 | 11 | // Seek moves the cursor of a file handle. 12 | type Seek struct { 13 | Fd, Offset, Whence Register // in 14 | NewOffset Register // out 15 | } 16 | 17 | // Execute implements the Instruction interface for the VM. 18 | func (ins *Seek) Execute(_ *int, vm *VM) error { 19 | f := vm.Get(ins.Fd).File 20 | offset := number.Int64(number.NewNumber(vm.Get(ins.Offset).Value)) 21 | whence := number.Int(number.NewNumber(vm.Get(ins.Whence).Value)) 22 | pos, err := f.Seek(offset, whence) 23 | if err != nil { 24 | vm.Raise(err.Error()) 25 | return nil 26 | } 27 | 28 | vm.Set(ins.NewOffset, asttest.NewLiteralNumber(strconv.Itoa(int(pos)))) 29 | 30 | return nil 31 | } 32 | 33 | // String is the human-readable description of the instruction. 34 | func (ins *Seek) String() string { 35 | return fmt.Sprintf("%s = os.Seek(%s, %s, %s)", 36 | ins.NewOffset, ins.Fd, ins.Offset, ins.Whence) 37 | } 38 | -------------------------------------------------------------------------------- /vm/sleep.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/elliotchance/ok/number" 8 | ) 9 | 10 | // Sleep will sleep for a fractional amount of seconds. 11 | type Sleep struct { 12 | Seconds Register 13 | } 14 | 15 | // Execute implements the Instruction interface for the VM. 16 | func (ins *Sleep) Execute(_ *int, vm *VM) error { 17 | seconds := number.NewNumber(vm.Get(ins.Seconds).Value) 18 | duration := number.Multiply(seconds, number.NewNumber("1000000000")) 19 | time.Sleep(time.Duration(number.Int64(duration))) 20 | 21 | return nil 22 | } 23 | 24 | // String is the human-readable description of the instruction. 25 | func (ins *Sleep) String() string { 26 | return fmt.Sprintf("time.Sleep(%s)", ins.Seconds) 27 | } 28 | -------------------------------------------------------------------------------- /vm/stack.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/elliotchance/ok/ast/asttest" 8 | ) 9 | 10 | // Stack returns the current stack trace. 11 | type Stack struct { 12 | Stack Register // Out 13 | } 14 | 15 | func reverse(ss []string) { 16 | last := len(ss) - 1 17 | for i := 0; i < len(ss)/2; i++ { 18 | ss[i], ss[last-i] = ss[last-i], ss[i] 19 | } 20 | } 21 | 22 | // Execute implements the Instruction interface for the VM. 23 | func (ins *Stack) Execute(_ *int, vm *VM) error { 24 | elements := vm.captureCallStack("") 25 | 26 | // Exclude some elements to avoid including the internal __stack call. 27 | elements = elements[2 : len(elements)-1] 28 | 29 | vm.Set(ins.Stack, asttest.NewLiteralString(strings.Join(elements, "\n"))) 30 | 31 | return nil 32 | } 33 | 34 | // String is the human-readable description of the instruction. 35 | func (ins *Stack) String() string { 36 | return fmt.Sprintf("%s = runtime.Stack()", ins.Stack) 37 | } 38 | -------------------------------------------------------------------------------- /vm/string_index.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | "github.com/elliotchance/ok/number" 8 | ) 9 | 10 | // StringIndex returns a character from an index of a string. 11 | type StringIndex struct { 12 | Str, Index, Result Register 13 | } 14 | 15 | // Execute implements the Instruction interface for the VM. 16 | func (ins *StringIndex) Execute(_ *int, vm *VM) error { 17 | index := number.Int64(number.NewNumber(vm.Get(ins.Index).Value)) 18 | 19 | // TODO(elliot): This won't work with multibyte characters. 20 | vm.Set(ins.Result, asttest.NewLiteralChar([]rune(vm.Get(ins.Str).Value)[index])) 21 | 22 | return nil 23 | } 24 | 25 | // String is the human-readable description of the instruction. 26 | func (ins *StringIndex) String() string { 27 | return fmt.Sprintf("%s = %s[%s]", ins.Result, ins.Str, ins.Index) 28 | } 29 | -------------------------------------------------------------------------------- /vm/subtract.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | "github.com/elliotchance/ok/number" 8 | ) 9 | 10 | // Subtract will subtract two numbers. 11 | type Subtract struct { 12 | Left, Right, Result Register 13 | } 14 | 15 | // Execute implements the Instruction interface for the VM. 16 | func (ins *Subtract) Execute(_ *int, vm *VM) error { 17 | vm.Set(ins.Result, asttest.NewLiteralNumber( 18 | number.Subtract( 19 | number.NewNumber(vm.Get(ins.Left).Value), 20 | number.NewNumber(vm.Get(ins.Right).Value), 21 | ).String(), 22 | )) 23 | 24 | return nil 25 | } 26 | 27 | // String is the human-readable description of the instruction. 28 | func (ins *Subtract) String() string { 29 | return fmt.Sprintf("%s = %s - %s", ins.Result, ins.Left, ins.Right) 30 | } 31 | -------------------------------------------------------------------------------- /vm/subtract_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/ok/ast" 7 | "github.com/elliotchance/ok/ast/asttest" 8 | "github.com/elliotchance/ok/vm" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestSubtract_Execute(t *testing.T) { 14 | for testName, test := range map[string]struct { 15 | left, right string 16 | expected string 17 | }{ 18 | "maintain-precision": {"1.2200", "4.7", "-3.4800"}, 19 | } { 20 | t.Run(testName, func(t *testing.T) { 21 | registers := map[vm.Register]*ast.Literal{ 22 | "0": asttest.NewLiteralNumber(test.left), 23 | "1": asttest.NewLiteralNumber(test.right), 24 | } 25 | ins := &vm.Subtract{Left: "0", Right: "1", Result: "2"} 26 | vm := &vm.VM{ 27 | Stack: []map[vm.Register]*ast.Literal{registers}, 28 | } 29 | assert.NoError(t, ins.Execute(nil, vm)) 30 | assert.Equal(t, test.expected, registers[ins.Result].Value) 31 | }) 32 | } 33 | } 34 | 35 | func TestSubtract_String(t *testing.T) { 36 | ins := &vm.Subtract{Left: "1", Right: "2", Result: "3"} 37 | assert.Equal(t, "$3 = $1 - $2", ins.String()) 38 | } 39 | -------------------------------------------------------------------------------- /vm/symbol.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "github.com/elliotchance/ok/ast" 5 | "github.com/elliotchance/ok/types" 6 | ) 7 | 8 | // A Symbol contains a literal value that can be referenced by instructions. 9 | type Symbol struct { 10 | Type TypeRegister 11 | 12 | // Value can only be used when Func is nil. 13 | Value string `json:",omitempty"` 14 | 15 | Func *CompiledFunc `json:",omitempty"` 16 | 17 | // Interface will only be set when Func is provided. 18 | Interface string `json:",omitempty"` 19 | } 20 | 21 | func (s *Symbol) Literal(registry types.Registry) *ast.Literal { 22 | return &ast.Literal{ 23 | Kind: registry.Get(string(s.Type)), 24 | Value: s.Value, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vm/type.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/elliotchance/ok/ast/asttest" 7 | ) 8 | 9 | // Type assigns the runtime type of a value to a string destination. 10 | type Type struct { 11 | Value, Result Register 12 | } 13 | 14 | // Execute implements the Instruction interface for the VM. 15 | func (ins *Type) Execute(_ *int, vm *VM) error { 16 | vm.Set(ins.Result, asttest.NewLiteralString(vm.Get(ins.Value).Kind.String())) 17 | 18 | return nil 19 | } 20 | 21 | // String is the human-readable description of the instruction. 22 | func (ins *Type) String() string { 23 | return fmt.Sprintf("%s = reflect.Type(%s)", ins.Result, ins.Value) 24 | } 25 | -------------------------------------------------------------------------------- /vm/write.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Write writes data to a file. 8 | type Write struct { 9 | Data, Fd Register 10 | } 11 | 12 | // Execute implements the Instruction interface for the VM. 13 | func (ins *Write) Execute(_ *int, vm *VM) error { 14 | f := vm.Get(ins.Fd).File 15 | _, err := f.WriteString(vm.Get(ins.Data).Value) 16 | if err != nil { 17 | vm.Raise(err.Error()) 18 | } 19 | 20 | return nil 21 | } 22 | 23 | // String is the human-readable description of the instruction. 24 | func (ins *Write) String() string { 25 | return fmt.Sprintf("os.Write(%s, %s)", ins.Fd, ins.Data) 26 | } 27 | --------------------------------------------------------------------------------