├── docs ├── .nojekyll ├── favicon.ico ├── _sidebar.md ├── js │ ├── prism-nolol.js │ └── prism-yolol.js ├── nolol-stdlib-header.md └── index.html ├── vscode-yolol ├── bin │ ├── darwin │ │ └── .gitkeep │ ├── linux │ │ └── .gitkeep │ └── win32 │ │ └── .gitkeep ├── .gitignore ├── logo.png ├── tslint.json ├── .vscode │ ├── settings.json │ ├── tasks.json │ └── launch.json ├── testFixture │ ├── correct.yolol │ ├── has_errors.yolol │ ├── correct.nolol │ └── has_errors.nolol ├── tsconfig.json ├── syntaxes │ ├── yolol-config.json │ ├── nolol.tmGrammar.json │ └── yolol.tmGrammar.json ├── src │ └── test │ │ ├── runTest.ts │ │ ├── index.ts │ │ ├── helper.ts │ │ ├── unit.test.ts │ │ ├── diagnostics.test.ts │ │ └── debugadapter.test.ts ├── LICENSE ├── .eslintrc.js └── README.md ├── examples ├── yolol │ ├── operations5.yolol │ ├── unoptimized.opt.yolol │ ├── abuse_errors.yolol │ ├── operations5_test.yaml │ ├── abuse_errors_test.yaml │ ├── case_insensitive.yolol │ ├── case_insensitive_test.yaml │ ├── fizzbuzz.yolol │ ├── operations3_test.yaml │ ├── operations2_test.yaml │ ├── operations_test.yaml │ ├── operations4_test.yaml │ ├── operations.yolol │ ├── operations3.yolol │ ├── operations2.yolol │ ├── unoptimized.yolol │ ├── operations4.yolol │ └── fizzbuzz_test.yaml └── nolol │ ├── included_basic.nolol │ ├── included_advanced.nolol │ ├── var_renaming_test.yaml │ ├── definitions_test.yaml │ ├── functions.nolol │ ├── including_test.yaml │ ├── loops_advanced_test.yaml │ ├── state_two.nolol │ ├── goto_test.yaml │ ├── including_basic_test.yaml │ ├── stdlib_demo.nolol │ ├── including_advanced_test.yaml │ ├── loops_test.yaml │ ├── array_test.yaml │ ├── measuring_time_test.yaml │ ├── dead_code_test.yaml │ ├── state_one.nolol │ ├── functions_test.yaml │ ├── state_test.yaml │ ├── var_renaming.nolol │ ├── sequential.nolol │ ├── case_insensitive_test.yaml │ ├── macros_test.yaml │ ├── including.nolol │ ├── fizzbuzz.nolol │ ├── sequential_test.yaml │ ├── fizzbuzz_test.yaml │ ├── state_common.nolol │ ├── definitions.nolol │ ├── case_insensitive.nolol │ ├── measuring_time.nolol │ ├── loops_advanced.nolol │ ├── loops.nolol │ ├── array_user.nolol │ ├── ifelse_test.yaml │ ├── goto.nolol │ ├── dead_code.nolol │ ├── array.nolol │ ├── timing_control.nolol │ ├── ifelse.nolol │ └── macros.nolol ├── CODE_OF_CONDUCT.md ├── tools.go ├── .gitmodules ├── main.go ├── stdlib ├── src │ ├── math_professional.nolol │ ├── math_advanced.nolol │ ├── string.nolol │ ├── logic.nolol │ └── math_basic.nolol ├── generate.go ├── generate_test.go ├── stdlib_test.yaml ├── stdlib_advanced_test.yaml ├── stdlib_basic_test.yaml └── testcases.nolol ├── pkg ├── langserver │ ├── autotype.go │ ├── win32 │ │ ├── window.go │ │ ├── hotkey.go │ │ └── keyboard.go │ ├── cache.go │ ├── settings.go │ ├── stream.go │ └── formatting.go ├── vm │ ├── vm_test.go │ ├── coordinator_test.go │ ├── variable_test.go │ └── variable.go ├── nolol │ ├── nast │ │ └── tokenizer.go │ ├── filesystem.go │ ├── converter_interfaces.go │ ├── converter_builtins.go │ ├── converter_test.go │ ├── converter_definitions.go │ └── converter_includes.go ├── parser │ ├── printer_test.go │ ├── error.go │ ├── parser_test.go │ └── validate.go ├── lsp │ ├── diagnostics.go │ ├── doc.go │ ├── LICENSE.txt │ ├── window.go │ ├── registration.go │ ├── protocol.go │ ├── printers.go │ └── text.go ├── debug │ └── stdio.go ├── optimizers │ ├── comments.go │ ├── expression_inversion_test.go │ ├── static_expressions_test.go │ ├── optimizer.go │ ├── optimizer_test.go │ ├── variable_name_test.go │ └── expression_inversion.go ├── validators │ ├── code_length.go │ ├── code_length_test.go │ └── available_ops.go ├── jsonrpc2 │ ├── LICENSE.txt │ ├── log.go │ └── stream.go ├── testdata │ └── dataset.go ├── testing │ └── test_test.go └── util │ └── check_formatting.go ├── .github ├── ISSUE_TEMPLATE │ ├── other.md │ ├── bug_report copy.md │ ├── feature_request.md │ └── bug_report.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── ci ├── run-acid-tests.sh ├── build-changelog.sh └── build-docs.sh ├── cmd ├── util.go ├── version.go ├── langserv.go ├── test.go ├── debugadapter.go ├── verify.go ├── optimize.go ├── compile.go ├── root.go └── format.go ├── go.mod ├── LICENSE.txt ├── examples_test.go ├── .travis.yml ├── CONTRIBUTING.md └── Makefile /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vscode-yolol/bin/darwin/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vscode-yolol/bin/linux/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vscode-yolol/bin/win32/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vscode-yolol/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out/ -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbaumgarten/yodk/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /examples/yolol/operations5.yolol: -------------------------------------------------------------------------------- 1 | :testhex=0xFF==255 2 | :testsci=1.2e2==120 3 | :done=1 -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Rule 1: Don't be a dick. 4 | Rule 2: tbd. 5 | -------------------------------------------------------------------------------- /vscode-yolol/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbaumgarten/yodk/HEAD/vscode-yolol/logo.png -------------------------------------------------------------------------------- /examples/nolol/included_basic.nolol: -------------------------------------------------------------------------------- 1 | define stop=10 2 | 3 | while i++<=stop do 4 | :hello+="." 5 | end 6 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | // +build tools 2 | 3 | package main 4 | 5 | import _ "github.com/elazarl/go-bindata-assetfs" 6 | -------------------------------------------------------------------------------- /examples/nolol/included_advanced.nolol: -------------------------------------------------------------------------------- 1 | define stop=10 2 | 3 | while i++<=stop do 4 | :hello+="_" 5 | end 6 | -------------------------------------------------------------------------------- /vscode-yolol/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "indent": [true, "tabs"], 4 | "semicolon": [true, "always"] 5 | } 6 | } -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "acid-tests"] 2 | path = acid-tests 3 | url = https://github.com/martindevans/Yolol-Acid-Test.git 4 | -------------------------------------------------------------------------------- /examples/yolol/unoptimized.opt.yolol: -------------------------------------------------------------------------------- 1 | 2 | a="hello world" 3 | a+=:aglobal+b 4 | :x=2020 5 | :answ=not(:a or :b or :c or :d) 6 | :answ=not :answ -------------------------------------------------------------------------------- /examples/yolol/abuse_errors.yolol: -------------------------------------------------------------------------------- 1 | :a=123 x=1/0 :a=456 // last assignment is not executed 2 | :b=1 // execution continues on last line 3 | :done=1 -------------------------------------------------------------------------------- /examples/nolol/var_renaming_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - var_renaming.nolol 3 | cases: 4 | - name: TestRenaming 5 | outputs: 6 | out: "hallo welt123 2" 7 | -------------------------------------------------------------------------------- /examples/nolol/definitions_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - definitions.nolol 3 | cases: 4 | - name: TestDefinitions 5 | outputs: 6 | out: "Hello world and Peter" 7 | -------------------------------------------------------------------------------- /examples/nolol/functions.nolol: -------------------------------------------------------------------------------- 1 | :timeok=time() 2 | :absok=abs(-5)==ABS(5) 3 | if abs(-2)==abs(2) then 4 | :absinfiok=1 5 | end 6 | :sqrtok=SQRT(16)==4 7 | 8 | :done=1 9 | -------------------------------------------------------------------------------- /examples/nolol/including_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - including.nolol 3 | cases: 4 | - name: TestInclusion 5 | outputs: 6 | hello: "hello __________ daniel" 7 | -------------------------------------------------------------------------------- /examples/nolol/loops_advanced_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - loops_advanced.nolol 3 | cases: 4 | - name: Count up 5 | outputs: 6 | out: "|1.3.5.7.9.|1.3.5.7.9." 7 | -------------------------------------------------------------------------------- /examples/nolol/state_two.nolol: -------------------------------------------------------------------------------- 1 | include "state_common.nolol" 2 | 3 | SMBEGIN(STATE_PONG) 4 | 5 | :OUTPUT+="pong " 6 | 7 | SMEND(STATE_PING) 8 | 9 | :done=1 10 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 dbaumgarten 2 | 3 | package main 4 | 5 | import "github.com/dbaumgarten/yodk/cmd" 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /examples/yolol/operations5_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - operations5.yolol 3 | cases: 4 | - name: TestOperationSuccess 5 | outputs: 6 | testhex: 1 7 | testsci: 1 -------------------------------------------------------------------------------- /stdlib/src/math_professional.nolol: -------------------------------------------------------------------------------- 1 | // This file contains basic definitions and macros for math 2 | // Import using ' include "std/math" ' 3 | 4 | include "std/math_advanced" 5 | -------------------------------------------------------------------------------- /examples/nolol/goto_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - goto.nolol 3 | cases: 4 | - name: TestOutputstring 5 | outputs: 6 | out: "abcdef" 7 | text: "d is at line: 4" 8 | -------------------------------------------------------------------------------- /examples/yolol/abuse_errors_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - abuse_errors.yolol 3 | ignoreerrs: true 4 | cases: 5 | - name: TestIgnoreErr 6 | outputs: 7 | a: 123 8 | b: 1 -------------------------------------------------------------------------------- /pkg/langserver/autotype.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package langserver 4 | 5 | // ListenForHotkeys is only supported on windows 6 | func (ls *LangServer) ListenForHotkeys() { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /examples/nolol/including_basic_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - including.nolol 3 | chiptype: basic 4 | cases: 5 | - name: TestInclusion 6 | outputs: 7 | hello: "hello .......... daniel" 8 | -------------------------------------------------------------------------------- /examples/nolol/stdlib_demo.nolol: -------------------------------------------------------------------------------- 1 | // include the math-part of the standard-library 2 | include "std/math" 3 | // use the math_floor macro 4 | :number=math_floor(5.123) 5 | // :number now is 5 6 | -------------------------------------------------------------------------------- /examples/nolol/including_advanced_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - including.nolol 3 | chiptype: advanced 4 | cases: 5 | - name: TestInclusion 6 | outputs: 7 | hello: "hello __________ daniel" 8 | -------------------------------------------------------------------------------- /examples/nolol/loops_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - loops.nolol 3 | cases: 4 | - name: draw 5 | outputs: 6 | out: "XXXXXX\\nX0000X\\nX0000X\\nX0000X\\nX0000X\\nXXXXXX\\n" 7 | out2: 5 8 | -------------------------------------------------------------------------------- /vscode-yolol/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.insertSpaces": false, 3 | "tslint.enable": true, 4 | "typescript.tsc.autoDetect": "off", 5 | "typescript.preferences.quoteStyle": "single" 6 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other 3 | about: An issue that is neither a bug report nor a feature request 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/nolol/array_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - array.nolol 3 | - array_user.nolol 4 | cases: 5 | - name: WriteAndRead 6 | outputs: 7 | sum: "2,4,6,8," 8 | o: "table> is at line: 2" 9 | -------------------------------------------------------------------------------- /examples/nolol/measuring_time_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - measuring_time.nolol 3 | cases: 4 | - name: TestClosingTime 5 | outputs: 6 | door: "closed" 7 | out: "door closed after 10 cycles" 8 | -------------------------------------------------------------------------------- /examples/nolol/dead_code_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - dead_code.nolol 3 | cases: 4 | - name: TestOutput 5 | outputs: 6 | o1: 1 7 | o2: 4 8 | o3: 3 9 | o4: 2 10 | o5: 3 11 | err: 0 -------------------------------------------------------------------------------- /examples/nolol/state_one.nolol: -------------------------------------------------------------------------------- 1 | include "state_common.nolol" 2 | 3 | if :OUTPUT==0 then 4 | :OUTPUT="" 5 | end 6 | 7 | SMBEGIN(STATE_PING) 8 | 9 | :OUTPUT+="ping " 10 | :counter++ 11 | 12 | SMEND(STATE_PONG) 13 | -------------------------------------------------------------------------------- /examples/nolol/functions_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - functions.nolol 3 | cases: 4 | - name: TestFunctions 5 | outputs: 6 | timeok: 1 7 | absok: 1 8 | absinfiok: 1 9 | sqrtok: 1 10 | 11 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | * [Introduction](/) 4 | * [CLI](/cli) 5 | * [Vscode-yolol](/vscode-yolol) 6 | * [Vscode-yolol Instructions](/vscode-instructions) 7 | * [NOLOL](/nolol) 8 | * [NOLOL-stdlib](/nolol-stdlib) -------------------------------------------------------------------------------- /examples/nolol/state_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - state_one.nolol 3 | - state_two.nolol 4 | stopwhen: 5 | counter: 3 6 | cases: 7 | - name: TestInteraction 8 | outputs: 9 | out: "ping pong ping pong ping " 10 | -------------------------------------------------------------------------------- /examples/nolol/var_renaming.nolol: -------------------------------------------------------------------------------- 1 | // non-global variable names will be shortened automatically 2 | peter="hallo"; dieter="welt"; foo=123 3 | bar=foo 4 | when=time() 5 | later=when+time() 6 | :out=peter+" "+dieter+foo+" "+later 7 | 8 | :done=1 9 | -------------------------------------------------------------------------------- /examples/yolol/case_insensitive.yolol: -------------------------------------------------------------------------------- 1 | // this program checks if the language ise really case-insensitive 2 | Var=123 var=456 3 | :assignok=Var==456 4 | FoO="test" 5 | :derefok=Foo==FoO 6 | :funcOK=sin 3==sin 3 7 | iffoo==FOO then:fiok=1end 8 | :done=1 -------------------------------------------------------------------------------- /examples/yolol/case_insensitive_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - case_insensitive.yolol 3 | cases: 4 | - name: TestCaseInsensitiv 5 | outputs: 6 | assignOK: 1 7 | derefOK: 1 8 | funcok: 1 9 | fiok: 1 10 | 11 | -------------------------------------------------------------------------------- /examples/yolol/fizzbuzz.yolol: -------------------------------------------------------------------------------- 1 | if:out==0then:out=""end 2 | if:number>100thengoto7end 3 | if:number%3==0 and :number%5==0then:out+="fizzbuzz " goto6end 4 | if:number%3==0then:out+="fizz " goto6end 5 | if:number%5==0then:out+="buzz "end 6 | :number++ goto2 -------------------------------------------------------------------------------- /examples/nolol/sequential.nolol: -------------------------------------------------------------------------------- 1 | // This script demonstrates sequential testing. 2 | // By setting sequential: true in the test.yaml, state is preserved between test-cases 3 | :out=1 $ 4 | :out=2 $ 5 | here> if not :in then :foo++; goto here end $ 6 | :out=3 $ 7 | :out=4 $ 8 | :out=5 $ -------------------------------------------------------------------------------- /vscode-yolol/testFixture/correct.yolol: -------------------------------------------------------------------------------- 1 | if :out==0 then :out="" end 2 | if not (:number<=100) then goto 7 end 3 | if :number%3==0 and :number%5==0 then :out+="fizzbuzz " goto 6 end 4 | if :number%3==0 then :out+="fizz " end 5 | if :number%5==0 then :out+="buzz " end 6 | :number++ goto 2 7 | -------------------------------------------------------------------------------- /vscode-yolol/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "rootDir": "src", 7 | "sourceMap": true 8 | }, 9 | "include": ["src"], 10 | "exclude": ["node_modules", ".vscode-test"] 11 | } 12 | -------------------------------------------------------------------------------- /pkg/vm/vm_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/dbaumgarten/yodk/pkg/testdata" 7 | ) 8 | 9 | func TestOperators(t *testing.T) { 10 | err := testdata.ExecuteTestProgram(testdata.TestProgram) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /vscode-yolol/testFixture/has_errors.yolol: -------------------------------------------------------------------------------- 1 | if :out==0 then :out="" end 2 | if not (:number<=100) then then goto 7 end 3 | if :number%3==0 and :number%5==0 then :out+="fizzbuzz " goto 6 end 4 | if :number%3==0 then :out+="fizz " end 5 | iif :number%5==0 then :out+="buzz " end 6 | :number++ goto 2 7 | -------------------------------------------------------------------------------- /examples/nolol/case_insensitive_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - case_insensitive.nolol 3 | cases: 4 | - name: TestCaseInsensitiv 5 | outputs: 6 | assignOK: 1 7 | derefOK: 1 8 | funcok: 1 9 | fiok: 1 10 | defineok: 1 11 | loopok: 1 12 | macrook: 1 13 | 14 | -------------------------------------------------------------------------------- /examples/nolol/macros_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - macros.nolol 3 | cases: 4 | - name: TestGreetings 5 | outputs: 6 | out1: "Hello.....world" 7 | out2: "Hello_____you" 8 | out3: 0 9 | out4: "foo" 10 | sum: 378 11 | sum2: 278 12 | timer: 11 13 | go: 1 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | *.out 3 | yodk 4 | yodk.exe 5 | yodk.exe~ 6 | yodk-darwin 7 | yodk-* 8 | lslog 9 | dalog 10 | *.vsix 11 | !vscode-yolol/.vscode 12 | vscode-yolol/.vscode-test/ 13 | docs/README.md 14 | docs/vscode-yolol.md 15 | docs/generated/* 16 | docs/nolol-stdlib.md 17 | docs/sitemap_new.xml 18 | examples/nolol/*.yolol 19 | CHANGELOG.md 20 | yodk*.zip 21 | acid_test.yaml -------------------------------------------------------------------------------- /ci/run-acid-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | for FILE in acid-tests/conformance/*.yolol; do 5 | 6 | echo Testing $FILE 7 | cat << EOF > acid_test.yaml 8 | scripts: 9 | - $FILE 10 | ignoreerrs: true 11 | cases: 12 | - name: TestOutput 13 | outputs: 14 | OUTPUT: "ok" 15 | EOF 16 | ./yodk test acid_test.yaml 17 | rm acid_test.yaml 18 | 19 | done 20 | 21 | -------------------------------------------------------------------------------- /examples/nolol/including.nolol: -------------------------------------------------------------------------------- 1 | define name="daniel" 2 | :hello="hello " 3 | // other nolol files can be included into nolol files 4 | // the content of the included file will be pasted right where the include instruction was placed in the code 5 | // this happens during compilation. Therefore the included path can not be dynamically created 6 | include "included" 7 | :hello+=" "+name 8 | 9 | :done=1 10 | -------------------------------------------------------------------------------- /examples/yolol/operations3_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - operations3.yolol 3 | cases: 4 | - name: TestOperationSuccess 5 | outputs: 6 | testnestedfi: 1 7 | testnested: 1 8 | testnestedop: 1 9 | testautoconv: 1 10 | testaadd: 1 11 | testasub: 1 12 | testamul: 1 13 | testadiv: 1 14 | testamod: 1 15 | testmxor: 1 16 | testfloor: 1 17 | testprec: 1 -------------------------------------------------------------------------------- /examples/yolol/operations2_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - operations2.yolol 3 | cases: 4 | - name: TestOperationSuccess 5 | outputs: 6 | testi: 1 7 | testhw: 1 8 | testssub: 1 9 | testsin: 1 10 | testcos: 1 11 | testtan: 1 12 | testasin: 1 13 | testacos: 1 14 | testatan: 1 15 | testsq: 1 16 | testab: 1 17 | testz: 1 18 | testor: 1 19 | testfi: 1 -------------------------------------------------------------------------------- /examples/nolol/fizzbuzz.nolol: -------------------------------------------------------------------------------- 1 | define fizz="fizz" 2 | define buzz="buzz" 3 | define sep=" " 4 | define upto=100 5 | 6 | if :out==0 then 7 | :out="" 8 | end 9 | // main loop 10 | while :number<=upto do 11 | if :number%3==0 and :number%5==0 then 12 | :out+=fizz+buzz+sep 13 | else 14 | if :number%3==0 then 15 | :out+=fizz+sep 16 | else if :number%5==0 then 17 | :out+=buzz+sep 18 | end 19 | end 20 | :number++ 21 | end 22 | -------------------------------------------------------------------------------- /examples/nolol/sequential_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - sequential.nolol 3 | sequential: true 4 | maxlines: 100 5 | cases: 6 | - name: Test1 7 | stopwhen: 8 | out: 2 9 | outputs: 10 | out: 2 11 | - name: Test2 12 | maxlines: 10 13 | outputs: 14 | out: 2 15 | foo: 10 16 | - name: Test3 17 | maxlines: 2 18 | inputs: 19 | in: 1 20 | outputs: 21 | out: 3 22 | foo: 10 23 | -------------------------------------------------------------------------------- /examples/yolol/operations_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - operations.yolol 3 | cases: 4 | - name: TestOperationSuccess 5 | outputs: 6 | testsum: 1 7 | testsub: 1 8 | testmul: 1 9 | testdiv: 1 10 | testmod: 1 11 | testgot: 1 12 | testgotvar: 1 13 | testexp: 1 14 | testeq: 1 15 | testneq: 1 16 | testgt: 1 17 | testgte: 1 18 | testlt: 1 19 | testlte: 1 20 | 21 | -------------------------------------------------------------------------------- /cmd/util.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | ) 8 | 9 | var inputFile string 10 | 11 | func exitOnError(err error, operation string) { 12 | if err != nil { 13 | fmt.Printf("Error when %s:\n\n%s\n", operation, err.Error()) 14 | os.Exit(1) 15 | } 16 | } 17 | 18 | func loadInputFile(file string) string { 19 | f, err := ioutil.ReadFile(file) 20 | exitOnError(err, "Loading input file") 21 | return string(f) 22 | } 23 | -------------------------------------------------------------------------------- /stdlib/src/math_advanced.nolol: -------------------------------------------------------------------------------- 1 | // This file contains basic definitions and macros for math 2 | // Import using ' include "std/math" ' 3 | 4 | include "std/math_basic" 5 | 6 | // Returns the next lower integer to x 7 | macro math_floor(x) expr 8 | x-x%1 9 | end 10 | 11 | // Returns the absolute value of x. 12 | macro math_abs(x) expr 13 | abs(x) 14 | end 15 | 16 | // Returns x%m (even on basic chips) 17 | macro math_mod(x,m) expr 18 | x%m 19 | end 20 | -------------------------------------------------------------------------------- /ci/build-changelog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | TAGS=`git tag | sort -V -r | head -n 10` 4 | TAG1="" 5 | TAG2="" 6 | echo -e "# Auto-generated changelog\n\n" > CHANGELOG.md 7 | while read line; do 8 | TAG1=$TAG2 9 | TAG2=$line 10 | if [ "$TAG1" != "" ] ; then 11 | echo "## $TAG1" >> CHANGELOG.md 12 | git log '--format=format: - %s' $TAG2..$TAG1 >> CHANGELOG.md 13 | echo -e "\n" >> CHANGELOG.md 14 | fi 15 | done <<< "$TAGS" 16 | 17 | cat CHANGELOG.md -------------------------------------------------------------------------------- /vscode-yolol/testFixture/correct.nolol: -------------------------------------------------------------------------------- 1 | define fizz = "fizz" 2 | define buzz = "buzz" 3 | define sep = " " 4 | define upto = 100 5 | 6 | if :out==0 then 7 | :out="" 8 | end 9 | // main loop 10 | while :number<=upto do 11 | if :number%3==0 and :number%5==0 then 12 | :out+=fizz+buzz+sep 13 | goto next //skip other cases 14 | end 15 | if :number%3==0 then 16 | :out+=fizz+sep 17 | end 18 | if :number%5==0 then 19 | :out+=buzz+sep 20 | end 21 | next> 22 | :number++ 23 | end 24 | -------------------------------------------------------------------------------- /examples/yolol/operations4_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - operations4.yolol 3 | cases: 4 | - name: TestOperationSuccess 5 | outputs: 6 | testi: 1 7 | testd: 1 8 | testsum: 1 9 | testsub: 1 10 | testeq: 1 11 | testneq: 1 12 | testgt: 1 13 | testgte: 1 14 | testlt: 1 15 | testlte: 1 16 | testra: 1 17 | testfact: 1 18 | textexpass: 1 19 | testbullshit: 1 20 | testnegsqrt: 1 21 | testdec: 1 22 | 23 | -------------------------------------------------------------------------------- /docs/js/prism-nolol.js: -------------------------------------------------------------------------------- 1 | Prism.languages.nolol = { 2 | 'comment': /\/\/.+/, 3 | 'string': /"[^"]*"/, 4 | 'tag': /^\s*[a-zA-Z]+[a-zA-Z0-9_]*>/, 5 | 'keyword': /\b(if|then|else|end|goto|define|while|do|include|macro|break|continue|block|line|expr)\b/i, 6 | 'operator': /\b(and|or|not)\b/i, 7 | 'function': /[a-z0-9_]+(?=\()/i, 8 | 'variable': /:[a-zA-Z0-9_:.]+|^[a-zA-Z]+[a-zA-Z0-9_.]*/, 9 | 'constant': /(([0-9]+(\.[0-9]+)?)e[0-9]+)|(0x[0-9a-fA-F]+)|(([0-9]+(\.[0-9]+)?))/ 10 | }; 11 | -------------------------------------------------------------------------------- /vscode-yolol/testFixture/has_errors.nolol: -------------------------------------------------------------------------------- 1 | define fizz = "fizz" 2 | define buzz = "buzz" 3 | define sep = " " 4 | define upto = 100 5 | 6 | if :out==0 then 7 | :out="" 8 | end 9 | // main loop 10 | while :number<=upto do do 11 | if :number%3==0 and :number%5==0 then 12 | :out+=fizz+buzz+sep 13 | goto next //skip other cases 14 | end 15 | if :number%3==0 then 16 | :out+=fizz+sep 17 | end 18 | if :number%5==0 then 19 | :out+=buzz+sep 20 | goto next 21 | end 22 | next> 23 | :number++ 24 | end 25 | -------------------------------------------------------------------------------- /docs/js/prism-yolol.js: -------------------------------------------------------------------------------- 1 | Prism.languages.yolol = { 2 | 'comment': /\/\/.+/, 3 | 'string': /"[^"]*"/, 4 | 'keyword': /((?<=^|\\s|[^a-zA-Z_:])(if|then|else|end|goto)+)|(?<=^|\\s|[^a-zA-Z0-9_:.])(not|abs|sqrt|sin|cos|tan|asin|acos|atan)(?=[^a-zA-Z0-9_:.]|$)/i, 5 | 'operator': /(?<=[^a-zA-Z0-9_:.])(and|or)(?=[^a-zA-Z0-9_:.])/i, 6 | 'function': /[a-z0-9_]+(?=\()/i, 7 | 'variable': /:[a-zA-Z0-9_:.]+|^[a-zA-Z]+[a-zA-Z0-9_.]*/, 8 | 'constant': /(([0-9]+(\.[0-9]+)?)e[0-9]+)|(0x[0-9a-fA-F]+)|(([0-9]+(\.[0-9]+)?))/ 9 | }; 10 | -------------------------------------------------------------------------------- /examples/yolol/operations.yolol: -------------------------------------------------------------------------------- 1 | :testsum=(1+2)==3 2 | :testsub=(3-1)==2 3 | :testmul=2*5==10 // whatever 4 | :testdiv=20/10==2 5 | :testmod=11%10==1 6 | counter=0 7 | counter++ 8 | ifcounter<20thengoto7end 9 | :testgot=counter==20 10 | :testgotvar=1 11 | gotocounter-8 :testgtvar=0 12 | // comment1 13 | :testexp=10^2==100 14 | :testeq=42==42 and not 41==24 15 | :testneq=1!=42 and not 1!=1 16 | :testgt=2>1 and not 1>2 and 5>-5 17 | :testgte=2>=1 and not 1>=2 and 2>=2 18 | :testlt=1<2 and not 2<1 and -5<5 19 | :testlte=1<=2 and not 2<=1 and 2>=2 20 | :done=1 -------------------------------------------------------------------------------- /vscode-yolol/syntaxes/yolol-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "//" 4 | }, 5 | "brackets": [ 6 | [ 7 | "(", 8 | ")" 9 | ] 10 | ], 11 | "autoClosingPairs": [ 12 | { 13 | "open": "(", 14 | "close": ")" 15 | }, 16 | { 17 | "open": "\"", 18 | "close": "\"", 19 | "notIn": [ 20 | "string" 21 | ] 22 | } 23 | ], 24 | "wordPattern": "[A-Za-z0-9_:]+" 25 | } -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // YodkVersion contains the version of this binary and is set on build time 10 | var YodkVersion = "UNVERSIONED BUILD" 11 | 12 | // versionCmd represents the version command 13 | var versionCmd = &cobra.Command{ 14 | Use: "version", 15 | Short: "Show version of the yodk binary", 16 | Run: func(cmd *cobra.Command, args []string) { 17 | fmt.Println(YodkVersion) 18 | }, 19 | } 20 | 21 | func init() { 22 | rootCmd.AddCommand(versionCmd) 23 | } 24 | -------------------------------------------------------------------------------- /pkg/nolol/nast/tokenizer.go: -------------------------------------------------------------------------------- 1 | package nast 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/dbaumgarten/yodk/pkg/parser/ast" 7 | ) 8 | 9 | // NewNololTokenizer creates a Yolol-Tokenizer that is modified to also accept Nolol-specific tokens 10 | func NewNololTokenizer() *ast.Tokenizer { 11 | tok := ast.NewTokenizer() 12 | tok.KeywordRegexes = []*regexp.Regexp{regexp.MustCompile("(?i)^\\b(if|else|end|then|goto|and|or|not|define|while|do|wait|include|macro|insert|break|continue|block|line|expr)\\b")} 13 | tok.Symbols = append(tok.Symbols, []string{";", "$"}...) 14 | return tok 15 | } 16 | -------------------------------------------------------------------------------- /pkg/parser/printer_test.go: -------------------------------------------------------------------------------- 1 | package parser_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/dbaumgarten/yodk/pkg/parser" 7 | "github.com/dbaumgarten/yodk/pkg/testdata" 8 | ) 9 | 10 | func TestGenerator(t *testing.T) { 11 | p := parser.NewParser() 12 | gen := parser.Printer{} 13 | parsed, err := p.Parse(testdata.TestProgram) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | generated, err := gen.Print(parsed) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | 22 | err = testdata.ExecuteTestProgram(generated) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/nolol/fizzbuzz_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - fizzbuzz.nolol 3 | stopwhen: 4 | number: 101 5 | cases: 6 | - name: TestOutput 7 | inputs: 8 | number: 0 9 | outputs: 10 | out: "fizzbuzz fizz buzz fizz fizz buzz fizz fizzbuzz fizz buzz fizz fizz buzz fizz fizzbuzz fizz buzz fizz fizz buzz fizz fizzbuzz fizz buzz fizz fizz buzz fizz fizzbuzz fizz buzz fizz fizz buzz fizz fizzbuzz fizz buzz fizz fizz buzz fizz fizzbuzz fizz buzz fizz fizz buzz " 11 | number: 101 12 | - name: TestOutput2 13 | inputs: 14 | number: 99 15 | outputs: 16 | out: "fizz buzz " -------------------------------------------------------------------------------- /examples/nolol/state_common.nolol: -------------------------------------------------------------------------------- 1 | // This is an example on how to implement a state-machine spanning multiple chips 2 | // This file defines constants and macros for the other scripts 3 | 4 | include "std/logic" 5 | 6 | // define possible states 7 | define STATE_PING=0 8 | define STATE_PONG=1 9 | 10 | // the shared state-var 11 | define :STATEVAR=:state 12 | 13 | // the output-var we act on 14 | define :OUTPUT=:out 15 | 16 | // define some macros 17 | macro SMBEGIN(waitfor) line 18 | logic_wait(:STATEVAR!=waitfor) 19 | end 20 | 21 | macro SMEND(newstate) line 22 | :STATEVAR=newstate 23 | end 24 | 25 | -------------------------------------------------------------------------------- /examples/yolol/operations3.yolol: -------------------------------------------------------------------------------- 1 | if:testfi!=1000thenif1==1then:testnestedfi=1endend 2 | :testnested=(3+(1+1)*5)==13 3 | k=2 4 | :testnestedop=(k+5)*k++==24 5 | :testautoconv=("test "+123)=="test 123" 6 | :constexp=1+2*5+sin(3.141*2/2) 7 | :testaadd=10 :testaadd+=2 8 | :testasub=10 :testasub-=2 9 | :testamul=10 :testamul*=2 10 | :testadiv=10 :testadiv/=2 11 | :testamod=10 :testamod%=2 12 | :testaadd=:testaadd==12 13 | :testasub=:testasub==8 14 | :testamul=:testamul==20 15 | :testadiv=:testadiv==5 16 | :testamod=:testamod==0 17 | :testmxor=1!=1!=1 18 | :testfloor=100.123/1000*1000==100 19 | x=3 y=1 z=0 :testprec=x>y==y>z 20 | :done=1 -------------------------------------------------------------------------------- /pkg/lsp/diagnostics.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This file contains the corresponding structures to the 6 | // "Diagnostics" part of the LSP specification. 7 | 8 | package lsp 9 | 10 | type PublishDiagnosticsParams struct { 11 | /** 12 | * The URI for which diagnostic information is reported. 13 | */ 14 | URI DocumentURI `json:"uri"` 15 | 16 | /** 17 | * An array of diagnostic information items. 18 | */ 19 | Diagnostics []Diagnostic `json:"diagnostics"` 20 | } 21 | -------------------------------------------------------------------------------- /examples/nolol/definitions.nolol: -------------------------------------------------------------------------------- 1 | // define greeting to be hello 2 | define greeting="Hello" 3 | 4 | :name="Peter" 5 | 6 | // definitions can be any expression you like and even contain other definitions, variables or functions 7 | define message=greeting+" "+"world and "+:name 8 | 9 | // you can define a new name for a variable 10 | // if the defined value is a variable name (and nothing else), you can assign to it 11 | // this is practically aliasing :out to :output 12 | define :output=:out 13 | 14 | // :output is replaced by ":out" and message is replaced by the expression declared in it's definition 15 | :output=message 16 | 17 | :done=1 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report copy.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ingame compatibility 3 | about: Report yodk not behaving like the game 4 | title: '' 5 | labels: compatibility 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description 12 | 13 | **Example code** 14 | Code that demonstrates the different behaviour. Keep the example as short as possible. 15 | 16 | ``` 17 | a=1 b=2 18 | goto 1 19 | ``` 20 | 21 | **Ingame results** 22 | What the example-code is doing ingame 23 | 24 | **Yodk results** 25 | What the example-code is doing with yodk 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /examples/nolol/case_insensitive.nolol: -------------------------------------------------------------------------------- 1 | // this program checks if the language is really case-insensitive 2 | // despit the really excentric naming (of even the keywords) all global variables should have the value 1 at the end 3 | Var=123; var=456 4 | :assignok=Var==456 5 | 6 | FoO="test" 7 | :derefok=Foo==FoO 8 | 9 | :funcOK=sin(3)==SIN(3) 10 | 11 | if foo==FOO then 12 | :fiok=1 13 | end 14 | 15 | define TEST="FOO" 16 | :defineOK=test=="FOO" 17 | 18 | NUM=1 19 | while num++<4 do 20 | :loopok=num==NUM 21 | end 22 | 23 | macro fooBar(bar) line 24 | :macroOK=bar==BAR and bar=="FOO" 25 | end 26 | 27 | FOOBAR(TEST) 28 | :x=-1 29 | :y=++a 30 | 31 | :done=1 32 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What this PR does / why we need it**: 2 | 3 | ## Which issue(s) this PR fixes**: 4 | Fixes #YOUR_ISSUE_NUMBER_HERE 5 | 6 | ## Sources: 7 | If this PR aims to improve compatibility to the game's implementation (or another existing YOLOL-implementation), please state why you know that the game does it this way. 8 | (E.g. you tested it ingame yourself, you read it in the wiki etc.) 9 | 10 | ## Checklist: 11 | 15 | - [ ] PR is done against develop-branch 16 | - [ ] includes tests for everything that changed 17 | - [ ] updated documentation (if necessary) -------------------------------------------------------------------------------- /examples/yolol/operations2.yolol: -------------------------------------------------------------------------------- 1 | i=0 j=i++ k=++i :testi=i==2 and j==1 and k==2 2 | hw="hello" hw++ 3 | hw+="world" 4 | :testhw=hw=="hello world" //comment2 5 | abc="abc" 6 | :testssub=(hw-"world")=="hello " and abc=="a" and abc--=="a" and --abc=="ab" 7 | :testsin=abs(sin 90-1)<0.01 8 | :testcos=abs cos(90-0)<0.01 9 | :testtan=abs tan(0-0)<0.01 10 | :testasin=abs asin sin(90-90)<0.111 11 | :testacos=abs acos cos(90-90)<0.1 12 | :testatan=abs atan tan(0-0)<0.1 13 | :testsq=sqrt 16==4 14 | :testab=abs-5==5 and abs 5==5 and abs 5==abs 5 15 | :testz=((not 1) and not 10 and not(not 0))==0 16 | :testor=20 or 0 17 | :testfi=0 18 | pi=3.141 19 | ifpi>3then:testfi=1else:testfi=0end 20 | :done=1 -------------------------------------------------------------------------------- /vscode-yolol/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "compile", 7 | "group": "build", 8 | "presentation": { 9 | "panel": "dedicated", 10 | "reveal": "never" 11 | }, 12 | "problemMatcher": [ 13 | "$tsc" 14 | ] 15 | }, 16 | { 17 | "type": "npm", 18 | "script": "watch", 19 | "isBackground": true, 20 | "group": { 21 | "kind": "build", 22 | "isDefault": true 23 | }, 24 | "presentation": { 25 | "panel": "dedicated", 26 | "reveal": "never" 27 | }, 28 | "problemMatcher": [ 29 | "$tsc-watch" 30 | ] 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /pkg/debug/stdio.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | // StdioReadWriteCloser is a ReadWriteCloser for reading from stdin and writing to stdout 8 | // If Log is true, all incoming and outgoing data is logged 9 | type StdioReadWriteCloser struct { 10 | } 11 | 12 | func (s StdioReadWriteCloser) Read(p []byte) (n int, err error) { 13 | return os.Stdin.Read(p) 14 | } 15 | 16 | func (s StdioReadWriteCloser) Write(p []byte) (n int, err error) { 17 | return os.Stdout.Write(p) 18 | } 19 | 20 | // Close closes stdin and stdout 21 | func (s StdioReadWriteCloser) Close() error { 22 | err := os.Stdin.Close() 23 | if err != nil { 24 | return err 25 | } 26 | return os.Stdout.Close() 27 | } 28 | -------------------------------------------------------------------------------- /vscode-yolol/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "name": "Launch Client", 9 | "runtimeExecutable": "${execPath}", 10 | "args": [ 11 | "--extensionDevelopmentPath=${workspaceRoot}" 12 | ], 13 | "outFiles": [ 14 | "${workspaceRoot}/out/**/*.js" 15 | ], 16 | "preLaunchTask": { 17 | "type": "npm", 18 | "script": "watch" 19 | }, 20 | //"env": { 21 | // "YODK_EXECUTABLE": "/home/daniel/go/src/github.com/dbaumgarten/yodk/yodk" 22 | //} 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /stdlib/generate.go: -------------------------------------------------------------------------------- 1 | package stdlib 2 | 3 | import "strings" 4 | 5 | // Prefix is how all files of the standard library start 6 | const Prefix = "std/" 7 | 8 | // Is returns true if the given package-name belongs to the standard-library 9 | func Is(name string) bool { 10 | return strings.HasPrefix(name, Prefix) 11 | } 12 | 13 | // Get returns the wanted file from the standard-library (or an error) 14 | func Get(name string) (string, error) { 15 | name = trim(name) 16 | by, err := Asset(name) 17 | if err != nil { 18 | return "", err 19 | } 20 | return string(by), err 21 | } 22 | 23 | // Trim removes the prefix from the path 24 | func trim(name string) string { 25 | return strings.TrimPrefix(name, Prefix) 26 | } 27 | -------------------------------------------------------------------------------- /docs/nolol-stdlib-header.md: -------------------------------------------------------------------------------- 1 | # NOLOL-stdlib 2 | 3 | This is the documentation for the NOLOL standard library. All macros and definitions listed here can simply be used by just inlcuding the right "std/*" file inside your own nolol file. 4 | 5 | Depending on for which chip-type you compile your script, the stdlib will provide different implementations for some of the macros. For example the math_floor macro uses the %-Operator on advanced and professional chips, but falls-back to another implementation for basic-chips. 6 | 7 | For example: 8 | 9 | [stdlib_demo.yolol](generated/code/nolol/stdlib_demo.nolol ':include') 10 | 11 | **As every part of NOLOL, the standard-library is still subject to change and may be changed in backwards-incompatible ways any time!** 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /pkg/lsp/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package protocol contains the structs that map directly to the wire format 6 | // of the "Language Server Protocol". 7 | // 8 | // It is a literal transcription, with unmodified comments, and only the changes 9 | // required to make it go code. 10 | // Names are uppercased to export them. 11 | // All fields have JSON tags added to correct the names. 12 | // Fields marked with a ? are also marked as "omitempty" 13 | // Fields that are "|| null" are made pointers 14 | // Fields that are string or number are left as string 15 | // Fields that are type "number" are made float64 16 | package lsp 17 | -------------------------------------------------------------------------------- /examples/nolol/measuring_time.nolol: -------------------------------------------------------------------------------- 1 | // nolol provides a built-in funtion time() that returns the amount of lines 2 | // that have been executed in this script 3 | 4 | include "std/logic" 5 | 6 | // open door and set close-time to now (1) plus 10 cycles 7 | // also take note of the start time so we can check the result later 8 | // as all statements are on the same line (separated by ;) they are executed at the same time 9 | :door="open"; closewhen=time()+10; start=time() 10 | // the logic_wait macro blocks while the given condition (time() line 6 | ignore=str; out=ignore-str-- 7 | end 8 | 9 | // Returns 1 if str contains x 10 | macro string_contains(str, x) expr 11 | (str-x)!=str 12 | end 13 | 14 | // Adds the lenght of str to out 15 | // str is set to "" in the process 16 | macro string_len(str, out) line 17 | here> if str!="" then str-- ; out++ ; goto here end 18 | end 19 | 20 | // Appends the reverse of str to out. 21 | // str is set to "" in the process 22 | macro string_reverse(str, out) line 23 | here> if str!="" then ignore=str; out+=ignore-str-- ; goto here end 24 | end -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Example Code/Screenshots** 24 | If applicable, add examples or screenshots to help explain your problem. 25 | 26 | **Platform:** 27 | - OS: windows/linux 28 | - Version of vscode-yolol/yodk you are using 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /examples/nolol/loops.nolol: -------------------------------------------------------------------------------- 1 | define width=5 2 | define height=5 3 | 4 | // nolol features loops 5 | // currenty only while-loops are suppored 6 | 7 | :out="" 8 | while y<=height do 9 | // loops can of course be nested 10 | while x<=width do 11 | if x==0 or y==0 or x==width or y==height then 12 | :out+="X" 13 | else 14 | :out+="0" 15 | end 16 | x++ 17 | end 18 | x=0 19 | y++ 20 | :out+="\n" 21 | end 22 | 23 | // After execution :out will contain the text: 24 | // XXXXXX 25 | // X0000X 26 | // X0000X 27 | // X0000X 28 | // X0000X 29 | // XXXXXX 30 | 31 | // the compiler will always try to choose the best possible compilation 32 | // this loop-body for example is small enough to be compiled as a single-line-loop 33 | foo=6 34 | while foo-- do 35 | :out2+=1 36 | end 37 | 38 | 39 | 40 | :done=1 41 | -------------------------------------------------------------------------------- /vscode-yolol/src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from '@vscode/test-electron'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to the extension test runner script 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error(err); 19 | console.error('Failed to run tests'); 20 | process.exit(1); 21 | } 22 | } 23 | 24 | main(); -------------------------------------------------------------------------------- /stdlib/generate_test.go: -------------------------------------------------------------------------------- 1 | package stdlib 2 | 3 | import ( 4 | "io/ioutil" 5 | "path/filepath" 6 | "testing" 7 | ) 8 | 9 | func TestGeneratedCode(t *testing.T) { 10 | 11 | files := AssetNames() 12 | filesOnDisk, err := ioutil.ReadDir("src") 13 | if err != nil { 14 | t.Error(err) 15 | } 16 | 17 | if len(files) != len(filesOnDisk) { 18 | t.Fatal("Amount of files in bindata does not match files on disk. You need to re-run go-bindata-assetfs") 19 | } 20 | 21 | for _, name := range files { 22 | bindataContent, _ := Asset(name) 23 | fileContent, err := ioutil.ReadFile(filepath.Join("src", name)) 24 | if err != nil { 25 | t.Error(err) 26 | } 27 | if string(bindataContent) != string(fileContent) { 28 | t.Fatalf("File stdlib/src/%s has changed. You need to re-run go-bindata-assetfs", name) 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dbaumgarten/yodk 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/abiosoft/ishell v2.0.0+incompatible 7 | github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect 8 | github.com/atotto/clipboard v0.1.4 9 | github.com/elazarl/go-bindata-assetfs v1.0.1 10 | github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect 11 | github.com/google/go-dap v0.3.0 12 | github.com/jinzhu/copier v0.3.5 13 | github.com/magiconair/properties v1.8.6 // indirect 14 | github.com/mitchellh/go-homedir v1.1.0 15 | github.com/pmezard/go-difflib v1.0.0 16 | github.com/spf13/afero v1.8.2 // indirect 17 | github.com/spf13/cobra v1.4.0 18 | github.com/spf13/viper v1.10.1 19 | golang.org/x/sys v0.0.0-20220412071739-889880a91fd5 // indirect 20 | gopkg.in/ini.v1 v1.66.4 // indirect 21 | gopkg.in/yaml.v2 v2.4.0 22 | ) 23 | -------------------------------------------------------------------------------- /examples/nolol/array_user.nolol: -------------------------------------------------------------------------------- 1 | // This script uses array.nolol to store some values and retrieve themm again 2 | 3 | include "std/logic" 4 | 5 | define data=:d 6 | define addr=:a 7 | define write=:m 8 | 9 | // store value at idx in array 10 | macro set(idx, value) block 11 | write=1 12 | data=value 13 | addr=idx 14 | // this will block until addr is 0. 15 | // the array will set addr to 0 once it stored the value 16 | logic_wait(addr) 17 | end 18 | 19 | // retrieve value from idx 20 | macro get(idx, value) block 21 | write=0 22 | addr=idx 23 | // this will block until addr is 0. 24 | // the array will set addr to 0 once it retrieved the value 25 | logic_wait(addr) 26 | value=data 27 | end 28 | 29 | while ++i<5 do 30 | set(i,i*2) 31 | end 32 | 33 | :sum="" 34 | while ++j<5 do 35 | get(j,retrieved) 36 | :sum+=retrieved+"," 37 | end 38 | 39 | :done=1 40 | -------------------------------------------------------------------------------- /stdlib/src/logic.nolol: -------------------------------------------------------------------------------- 1 | // This file contains basic definitions and macros for logic-operations 2 | // Import using ' include "std/logic" ' 3 | 4 | // Returns a if condition is true, otherwise b 5 | // condition, a and b must be numbers. condition must be 0 or 1 6 | macro logic_ternary(condition, a, b) expr 7 | b + (a-b)*condition 8 | end 9 | 10 | // If condition is 0, produces a runtime-error that will skip the remaining line 11 | // Usage: "logic_continue_line(var); do=1; stuff=2 $" 12 | // The $ is important to not skip too much 13 | macro logic_continue_line(condition) line 14 | ignore/=condition 15 | end 16 | 17 | // Returns 1 if a or b is true, but not both 18 | // a and b must be 0 or 1 19 | macro logic_xor(a,b) expr 20 | a != b 21 | end 22 | 23 | // Blocks as long as condition is true 24 | macro logic_wait(condition) line 25 | here> if condition then goto here end 26 | end -------------------------------------------------------------------------------- /examples/yolol/operations4.yolol: -------------------------------------------------------------------------------- 1 | i="abc" j=i++ k=++i :testi=i=="abc " and j=="abc " and k=="abc " 2 | i="abc" j=i-- k=--i :testd=i=="a" and j=="ab" and k=="a" 3 | :testsum=("abc"+"xyz")=="abcxyz" 4 | :testsub=("abcdecddc"-"cd")=="abcdedc" 5 | :testeq="abc"=="abc" and not"mno"=="pqr" 6 | :testneq="abc"!="xyz" and not"def"!="def" 7 | :testgt="jkl">"aaa" and not"aaa">"jkl" and "a">"" 8 | :testgte="jkl">="aaa" and not"aaa">="jkl" and "jkl">="jkl" 9 | :testlt="qqq"<"xyz" and not"xyz"<"qqq" and ""<"a" 10 | :testlte="qqq"<="xyz" and not"xyz"<="qqq" and "qqq">="qqq" 11 | :testra=2*3^4==162 and (2*3)^4==1296 12 | :testfact=2!==2 and 3!==6 and 3!!=5 and 4!/4==3! and sqrt 3!==2.449 13 | :testfact=:testfact and -2!==-2 and -2!==-2 and 2.5!==6 14 | foo=2 foo^=10 15 | :textexpass=foo==1024 16 | :::boo:::=123 17 | :testbullshit=:::boo:::==123 18 | :testnegsqrt=-sqrt 4==-2 19 | :testdec=.5==0.5 and -.5==-0.5 and abs .2==0.2 20 | :done=1 -------------------------------------------------------------------------------- /examples/nolol/ifelse_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - ifelse.nolol 3 | cases: 4 | - name: day1 5 | inputs: 6 | day: monday 7 | outputs: 8 | msg: "fuck" 9 | - name: day2 10 | inputs: 11 | day: tuesday 12 | outputs: 13 | msg: "still stupid" 14 | - name: day3 15 | inputs: 16 | day: wednesday 17 | outputs: 18 | msg: "half time!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" 19 | - name: day4 20 | inputs: 21 | day: thursday 22 | outputs: 23 | msg: "almost..." 24 | - name: day5 25 | inputs: 26 | day: friday 27 | outputs: 28 | msg: "yay" 29 | - name: day6 30 | inputs: 31 | day: saturday 32 | outputs: 33 | msg: "Weekend!!! PARTY!" 34 | - name: day7 35 | inputs: 36 | day: sunday 37 | outputs: 38 | msg: "Weekend!!! is over :(" 39 | foo: 1 40 | - name: yeah 41 | outputs: 42 | yeah: 1 43 | -------------------------------------------------------------------------------- /stdlib/stdlib_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - testcases.nolol 3 | ignoreerrs: true 4 | cases: 5 | - name: TestStdlib 6 | outputs: 7 | pi: 1 8 | e: 1 9 | abs1: 1 10 | abs2: 1 11 | abs3: 1 12 | sign1: 1 13 | sign2: 1 14 | sign3: 1 15 | floor1: 1 16 | floor2: 1 17 | mod1: 1 18 | mod2: 1 19 | mod3: 1 20 | pop1: 1 21 | pop2: 1 22 | cont1: 1 23 | cont2: 1 24 | rev1: 1 25 | rev2: 1 26 | tern1: 1 27 | tern2: 1 28 | abort: 1 29 | xor1: 1 30 | xor2: 1 31 | xor3: 1 32 | min1: 1 33 | min2: 1 34 | min3: 1 35 | max1: 1 36 | max2: 1 37 | max3: 1 38 | clamp1: 1 39 | clamp2: 1 40 | clamp3: 1 41 | clamp4: 1 42 | lerp1: 1 43 | lerp2: 1 44 | lerp3: 1 45 | lerp4: 1 46 | invlerp1: 1 47 | invlerp2: 1 48 | invlerp3: 1 49 | invlerp4: 1 50 | -------------------------------------------------------------------------------- /vscode-yolol/src/test/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'bdd', 9 | }); 10 | 11 | const testsRoot = path.resolve(__dirname, '..'); 12 | 13 | return new Promise((c, e) => { 14 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 15 | if (err) { 16 | return e(err); 17 | } 18 | 19 | // Add files to the test suite 20 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 21 | 22 | try { 23 | // Run the mocha test 24 | mocha.run(failures => { 25 | if (failures > 0) { 26 | e(new Error(`${failures} tests failed.`)); 27 | } else { 28 | c(); 29 | } 30 | }); 31 | } catch (err) { 32 | e(err); 33 | } 34 | }); 35 | }); 36 | } -------------------------------------------------------------------------------- /stdlib/stdlib_advanced_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - testcases.nolol 3 | ignoreerrs: true 4 | chiptype: advanced 5 | cases: 6 | - name: TestStdlib 7 | outputs: 8 | pi: 1 9 | e: 1 10 | abs1: 1 11 | abs2: 1 12 | abs3: 1 13 | sign1: 1 14 | sign2: 1 15 | sign3: 1 16 | floor1: 1 17 | floor2: 1 18 | mod1: 1 19 | mod2: 1 20 | mod3: 1 21 | pop1: 1 22 | pop2: 1 23 | cont1: 1 24 | cont2: 1 25 | rev1: 1 26 | rev2: 1 27 | tern1: 1 28 | tern2: 1 29 | abort: 1 30 | xor1: 1 31 | xor2: 1 32 | xor3: 1 33 | min1: 1 34 | min2: 1 35 | min3: 1 36 | max1: 1 37 | max2: 1 38 | max3: 1 39 | clamp1: 1 40 | clamp2: 1 41 | clamp3: 1 42 | clamp4: 1 43 | lerp1: 1 44 | lerp2: 1 45 | lerp3: 1 46 | lerp4: 1 47 | invlerp1: 1 48 | invlerp2: 1 49 | invlerp3: 1 50 | invlerp4: 1 -------------------------------------------------------------------------------- /stdlib/stdlib_basic_test.yaml: -------------------------------------------------------------------------------- 1 | scripts: 2 | - testcases.nolol 3 | ignoreerrs: true 4 | chiptype: basic 5 | cases: 6 | - name: TestStdlib 7 | outputs: 8 | pi: 1 9 | e: 1 10 | abs1: 1 11 | abs2: 1 12 | abs3: 1 13 | sign1: 1 14 | sign2: 1 15 | sign3: 1 16 | floor1: 1 17 | floor2: 1 18 | mod1: 1 19 | mod2: 1 20 | mod3: 1 21 | pop1: 1 22 | pop2: 1 23 | cont1: 1 24 | cont2: 1 25 | rev1: 1 26 | rev2: 1 27 | tern1: 1 28 | tern2: 1 29 | abort: 1 30 | xor1: 1 31 | xor2: 1 32 | xor3: 1 33 | min1: 1 34 | min2: 1 35 | min3: 1 36 | max1: 1 37 | max2: 1 38 | max3: 1 39 | clamp1: 1 40 | clamp2: 1 41 | clamp3: 1 42 | clamp4: 1 43 | lerp1: 1 44 | lerp2: 1 45 | lerp3: 1 46 | lerp4: 1 47 | invlerp1: 1 48 | invlerp2: 1 49 | invlerp3: 1 50 | invlerp4: 1 51 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /cmd/langserv.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "github.com/dbaumgarten/yodk/pkg/langserver" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var logfile string 12 | var hotkeys bool 13 | 14 | // langservCmd represents the langserv command 15 | var langservCmd = &cobra.Command{ 16 | Use: "langserv", 17 | Short: "Start language server", 18 | Run: func(cmd *cobra.Command, args []string) { 19 | 20 | stream := langserver.NewStdioStream() 21 | configureFileLogging() 22 | stream.Log = debugLog 23 | err := langserver.Run(context.Background(), stream, hotkeys) 24 | if err != nil { 25 | log.Println(err) 26 | } 27 | }, 28 | } 29 | 30 | func init() { 31 | rootCmd.AddCommand(langservCmd) 32 | langservCmd.Flags().StringVar(&logfile, "logfile", "", "Name of the file to log into. Defaults to stderr") 33 | langservCmd.Flags().BoolVarP(&debugLog, "debug", "d", false, "Enable verbose debug-logging") 34 | langservCmd.Flags().BoolVar(&hotkeys, "hotkeys", false, "Enable system-wide hotkeys for auto-typing") 35 | } 36 | -------------------------------------------------------------------------------- /examples/nolol/goto.nolol: -------------------------------------------------------------------------------- 1 | // this example shows the use of line-labels for gotos 2 | // but be cautious when using goto. It is bad style, error prone and hinders optimization 3 | :out="a" 4 | // go to the line that is labeled with b> 5 | goto b 6 | // labels can be on the same line as a statement 7 | c> :out+="c" 8 | goto d 9 | b> :out+="b" 10 | goto c 11 | d> // or on an own line. code automatically falls through empty lines 12 | 13 | // you can use line-labels as (read-only) variables in calculations. They will have the line they end up in in the compiled yolol as value 14 | :text="d is at line: "+d 15 | :out+="d"; goto e 16 | 17 | if :a==1 then 18 | // you can even jump into ifs 19 | e> :out+="e" 20 | // and out again 21 | goto f 22 | end 23 | 24 | // You can still use any expression for goto and calculate jumps. 25 | // BUT BE CAREFUL! Using calculated jumps prevents the compiler from performing many optimizations 26 | // and if you mess up, your jumps end up going whereever 27 | f> :out+="f"; goto g+1 28 | g> :out="skip this" $ 29 | 30 | :done=1 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Daniel Baumgarten 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /examples/nolol/dead_code.nolol: -------------------------------------------------------------------------------- 1 | :err=0 2 | define foo=1 3 | if foo==1 then 4 | :o1=1 5 | else if foo==2 then 6 | :o1=2 7 | :err++ 8 | else if foo==3 then 9 | :o1=3 10 | :err++ 11 | else 12 | :o1=4 13 | :err++ 14 | end 15 | $ 16 | define foo=5 17 | if foo==1 then 18 | :o2=1 19 | :err++ 20 | else if foo==2 then 21 | :o2=2 22 | :err++ 23 | else if foo==3 then 24 | :o2=3 25 | :err++ 26 | else 27 | :o2=4 28 | end 29 | $ 30 | define foo=5 31 | bar=4 32 | if foo==1 then 33 | :o3=1 34 | :err++ 35 | else if foo==2 then 36 | :o3=2 37 | :err++ 38 | else if bar==4 then 39 | :o3=3 40 | else 41 | :o3=4 42 | :err++ 43 | end 44 | $ 45 | define foo=5 46 | bar=2 47 | if bar==1 then 48 | :o4=1 49 | :err++ 50 | else if bar==2 then 51 | :o4=2 52 | else if bar==4 then 53 | :o4=3 54 | :err++ 55 | else 56 | :o4=4 57 | :err++ 58 | end 59 | $ 60 | define foo=5 61 | if bar==1 then 62 | :o5=1 63 | :err++ 64 | else if foo==2 then 65 | :o5=2 66 | :err++ 67 | else if foo==5 then 68 | :o5=3 69 | else 70 | :o5=4 71 | :err++ 72 | end 73 | 74 | :done=1 75 | -------------------------------------------------------------------------------- /vscode-yolol/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Daniel Baumgarten 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /pkg/optimizers/comments.go: -------------------------------------------------------------------------------- 1 | package optimizers 2 | 3 | import ( 4 | "github.com/dbaumgarten/yodk/pkg/parser/ast" 5 | ) 6 | 7 | // CommentOptimizer removes all comments from the code 8 | type CommentOptimizer struct { 9 | } 10 | 11 | // Optimize is needed to implement Optimizer 12 | func (o *CommentOptimizer) Optimize(prog ast.Node) error { 13 | err := prog.Accept(o) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | // remove trailing empty lines 19 | if prog, is := prog.(*ast.Program); is { 20 | for i := len(prog.Lines) - 1; i >= 0; i-- { 21 | hasStatements := len(prog.Lines[i].Statements) > 0 22 | if !hasStatements { 23 | prog.Lines = prog.Lines[:i] 24 | } else { 25 | break 26 | } 27 | } 28 | } 29 | 30 | return nil 31 | } 32 | 33 | // Visit is needed to implement Visitor 34 | func (o *CommentOptimizer) Visit(node ast.Node, visitType int) error { 35 | if visitType == ast.SingleVisit || visitType == ast.PreVisit { 36 | switch n := node.(type) { 37 | case *ast.Line: 38 | n.Comment = "" 39 | break 40 | } 41 | } 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /cmd/test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/dbaumgarten/yodk/pkg/testing" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // testCmd represents the format command 13 | var testCmd = &cobra.Command{ 14 | Use: "test [testfile] [testfile] ...", 15 | Short: "Run tests", 16 | 17 | Run: func(cmd *cobra.Command, args []string) { 18 | for _, arg := range args { 19 | file := loadInputFile(arg) 20 | absolutePath, _ := filepath.Abs(arg) 21 | test, err := testing.Parse([]byte(file), absolutePath) 22 | exitOnError(err, "loading test case") 23 | fmt.Println("Running file: " + arg) 24 | fails := test.Run(func(c testing.Case) { 25 | fmt.Println("- Running case: " + c.Name) 26 | }) 27 | if len(fails) == 0 { 28 | fmt.Println("Tests OK") 29 | } else { 30 | fmt.Println("There were errors when running the tests:") 31 | for _, err := range fails { 32 | fmt.Println(err) 33 | } 34 | os.Exit(1) 35 | } 36 | } 37 | }, 38 | } 39 | 40 | func init() { 41 | rootCmd.AddCommand(testCmd) 42 | } 43 | -------------------------------------------------------------------------------- /pkg/validators/code_length.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/dbaumgarten/yodk/pkg/parser" 7 | "github.com/dbaumgarten/yodk/pkg/parser/ast" 8 | ) 9 | 10 | // ValidateCodeLength checks if the given code (in text-format) does fit yolol's boundaries 11 | func ValidateCodeLength(code string) error { 12 | lines := strings.Split(code, "\n") 13 | if len(lines) > 20 { 14 | return &parser.Error{ 15 | Message: "Program has more than 20 lines", 16 | StartPosition: ast.Position{ 17 | Line: 21, 18 | Coloumn: 1, 19 | }, 20 | EndPosition: ast.Position{ 21 | Line: len(lines), 22 | Coloumn: 70, 23 | }, 24 | } 25 | } 26 | for i, line := range lines { 27 | line = strings.TrimSpace(line) 28 | if len(line) > 70 { 29 | return &parser.Error{ 30 | Message: "The line has more than 70 characters", 31 | StartPosition: ast.Position{ 32 | Line: i + 1, 33 | Coloumn: 70, 34 | }, 35 | EndPosition: ast.Position{ 36 | Line: i + 1, 37 | Coloumn: len(line), 38 | }, 39 | } 40 | } 41 | } 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /pkg/nolol/filesystem.go: -------------------------------------------------------------------------------- 1 | package nolol 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "path/filepath" 7 | ) 8 | 9 | // FileSystem defines an interface to get the content of files by name 10 | // used so the converter can query for the content of files mentioned in include-directives 11 | type FileSystem interface { 12 | Get(name string) (string, error) 13 | } 14 | 15 | // DiskFileSystem retrieves files from a directory on the disk 16 | type DiskFileSystem struct { 17 | Dir string 18 | } 19 | 20 | // Get implements FileSystem 21 | func (f DiskFileSystem) Get(name string) (string, error) { 22 | name = filepath.FromSlash(name) 23 | path := filepath.Join(f.Dir, name) 24 | content, err := ioutil.ReadFile(path) 25 | if err != nil { 26 | return "", err 27 | } 28 | return string(content), nil 29 | } 30 | 31 | // MemoryFileSystem serves files from Memory 32 | type MemoryFileSystem map[string]string 33 | 34 | // Get implements FileSystem 35 | func (f MemoryFileSystem) Get(name string) (string, error) { 36 | file, exists := f[name] 37 | if !exists { 38 | return "", fmt.Errorf("File not found") 39 | } 40 | return file, nil 41 | } 42 | -------------------------------------------------------------------------------- /pkg/vm/coordinator_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/dbaumgarten/yodk/pkg/vm" 7 | ) 8 | 9 | func TestCoordinatedExecution(t *testing.T) { 10 | prog1 := ` 11 | :result = "" 12 | :result += "b" 13 | :result += "d" 14 | :result += "f" 15 | :result += "h" 16 | ` 17 | prog2 := ` 18 | :result += "a" 19 | :result += "c" 20 | :result += "e" 21 | :result += "g" 22 | ` 23 | coord := vm.NewCoordinator() 24 | vm1, _ := vm.CreateFromSource(prog1) 25 | vm2, _ := vm.CreateFromSource(prog2) 26 | 27 | vm1.SetCoordinator(coord) 28 | vm2.SetCoordinator(coord) 29 | 30 | vm1.SetMaxExecutedLines(10) 31 | vm2.SetMaxExecutedLines(10) 32 | 33 | vm1.Resume() 34 | vm2.Resume() 35 | 36 | coord.Run() 37 | coord.WaitForTermination() 38 | 39 | r1, _ := vm1.GetVariable(":result") 40 | r2, _ := vm1.GetVariable(":result") 41 | 42 | result1 := r1.String() 43 | result2 := r2.String() 44 | 45 | if result1 != result2 { 46 | t.Fatal("Results are not identical", result1, result2) 47 | } 48 | 49 | if result1 != "abcdefgh" { 50 | t.Fatalf("Wrong result for computation, wanted %s but got %s", "abcdefgh", result1) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/nolol/array.nolol: -------------------------------------------------------------------------------- 1 | // This script implements a simple array data-structure that can be used by other chips 2 | // Set :d to the value you want to storem :a to the address to store it into and :m to 1 in order to store a value 3 | // Set :m to 0 and :a to the index to retrieve a value into :d 4 | 5 | // is good 6 | // is best 7 | define data=:d 8 | define addr=:a 9 | define write=:m 10 | 11 | // you can use a line-lable as (read-only) variable 12 | :o="table> is at line: "+table 13 | 14 | // goto current line plus the value of offset. 15 | table> $ goto table+addr $ 16 | 17 | // By using "$ stmt1; stmt2 $" we make sure, that the resulting yolol-code will have the EXACT same line-layout 18 | // If you need an if inside a single line, that is also possible. But using this style of ifs gives you a hard length-limit 19 | $ if write then mem1=data else data=mem1 end; addr=0; goto table $ 20 | $ if write then mem2=data else data=mem2 end; addr=0; goto table $ 21 | $ if write then mem3=data else data=mem3 end; addr=0; goto table $ 22 | $ if write then mem4=data else data=mem4 end; addr=0; goto table $ 23 | $ if write then mem5=data else data=mem5 end; addr=0; goto table $ 24 | $ if write then mem6=data else data=mem6 end; addr=0; goto table $ 25 | -------------------------------------------------------------------------------- /cmd/debugadapter.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/dbaumgarten/yodk/pkg/debug" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | // debugAdapterCmd represents the debugadapter command 12 | var debugAdapterCmd = &cobra.Command{ 13 | Use: "debugadapter", 14 | Short: "Start a vscode debugadapter", 15 | Run: func(cmd *cobra.Command, args []string) { 16 | stream := debug.StdioReadWriteCloser{} 17 | configureFileLogging() 18 | debug.StartSession(stream, debug.NewYODKHandler(), debugLog) 19 | }, 20 | } 21 | 22 | func init() { 23 | rootCmd.AddCommand(debugAdapterCmd) 24 | debugAdapterCmd.Flags().StringVar(&logfile, "logfile", "", "Name of the file to log into. Defaults to stderr") 25 | debugAdapterCmd.Flags().BoolVarP(&debugLog, "debug", "d", false, "Enable verbose debug-logging") 26 | debugLog = true 27 | } 28 | 29 | // configures logging according to provided flags 30 | func configureFileLogging() { 31 | log.SetFlags(log.Ltime | log.Lshortfile) 32 | if logfile != "" { 33 | f, err := os.OpenFile(logfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 34 | if err != nil { 35 | log.Println("Could not open log-file", err.Error()) 36 | // well. Nothing we can do about it... 37 | return 38 | } 39 | 40 | log.SetOutput(f) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "path/filepath" 6 | "strings" 7 | "testing" 8 | 9 | yodktesting "github.com/dbaumgarten/yodk/pkg/testing" 10 | ) 11 | 12 | func TestExamples(t *testing.T) { 13 | runTestfiles(filepath.Join("examples", "yolol"), t) 14 | runTestfiles(filepath.Join("examples", "nolol"), t) 15 | runTestfiles("stdlib", t) 16 | } 17 | 18 | func runTestfiles(path string, t *testing.T) { 19 | files, err := ioutil.ReadDir(path) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | testfiles := make([]string, 0, len(files)) 24 | for _, file := range files { 25 | if !file.IsDir() && strings.HasSuffix(file.Name(), "_test.yaml") { 26 | testfiles = append(testfiles, filepath.Join(path, file.Name())) 27 | } 28 | } 29 | 30 | for _, testfile := range testfiles { 31 | t.Run(testfile, func(t *testing.T) { 32 | file, err := ioutil.ReadFile(testfile) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | absolutePath, _ := filepath.Abs(testfile) 37 | test, err := yodktesting.Parse([]byte(file), absolutePath) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | fails := test.Run(nil) 42 | if len(fails) != 0 { 43 | for _, fail := range fails { 44 | t.Log(fail) 45 | } 46 | t.FailNow() 47 | } 48 | }) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pkg/langserver/win32/window.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package win32 4 | 5 | import ( 6 | "context" 7 | "syscall" 8 | "time" 9 | "unsafe" 10 | ) 11 | 12 | var ( 13 | forgrWindow = user32.MustFindProc("GetForegroundWindow") 14 | windowText = user32.MustFindProc("GetWindowTextW") 15 | windowTextLen = user32.MustFindProc("GetWindowTextLengthW") 16 | ) 17 | 18 | // GetForegroundWindow returns the name of the window that currently has the user-focus 19 | func GetForegroundWindow() string { 20 | hwnd, _, _ := forgrWindow.Call() 21 | 22 | if hwnd != 0 { 23 | textlen, _, _ := windowTextLen.Call(hwnd) 24 | buf := make([]uint16, textlen+1) 25 | windowText.Call(hwnd, uintptr(unsafe.Pointer(&buf[0])), uintptr(textlen+1)) 26 | return syscall.UTF16ToString(buf) 27 | } 28 | 29 | return "" 30 | } 31 | 32 | // WaitForWindowChange blocks until the window with the user-focus changes (or ctx is cancelled). Returns the title of the new active window 33 | func WaitForWindowChange(ctx context.Context) string { 34 | oldWindow := GetForegroundWindow() 35 | for { 36 | time.Sleep(200 * time.Millisecond) 37 | newWindow := GetForegroundWindow() 38 | if newWindow != oldWindow { 39 | return newWindow 40 | } 41 | if ctx != nil && ctx.Err() != nil { 42 | return oldWindow 43 | } 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /pkg/parser/error.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dbaumgarten/yodk/pkg/parser/ast" 7 | ) 8 | 9 | // Error represents an error encountered during parsing 10 | type Error struct { 11 | // The human-readable error-message 12 | Message string 13 | // Where the error started 14 | StartPosition ast.Position 15 | // Where the error ends 16 | EndPosition ast.Position 17 | // Machine-Readable error-code 18 | Code string 19 | // A token that was expected here (optional) 20 | ExpectedToken *ast.Token 21 | } 22 | 23 | // Predefined constants for Error.Code 24 | const ( 25 | ErrExpectedExpression = "ErrExpectedExpression" 26 | ErrExpectedStatement = "ErrExpectedStatement" 27 | ErrExpectedToken = "ErrExpectedToken" 28 | ErrExpectedAssignop = "ErrExpectedAssignop" 29 | ) 30 | 31 | func (e Error) Error() string { 32 | if e.StartPosition != e.EndPosition { 33 | return fmt.Sprintf("Parser error at %s (up to %s): %s", e.StartPosition.String(), e.EndPosition.String(), e.Message) 34 | } 35 | return fmt.Sprintf("Parser error at %s: %s", e.StartPosition.String(), e.Message) 36 | } 37 | 38 | // Errors represents multiple Errors 39 | type Errors []*Error 40 | 41 | func (e Errors) Error() string { 42 | str := "" 43 | for _, err := range e { 44 | str += err.Error() + "\n\n" 45 | } 46 | return str 47 | } 48 | -------------------------------------------------------------------------------- /vscode-yolol/src/test/helper.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as path from 'path'; 3 | 4 | export let doc: vscode.TextDocument; 5 | export let editor: vscode.TextEditor; 6 | export let documentEol: string; 7 | export let platformEol: string; 8 | 9 | /** 10 | * Activates the vscode.lsp-sample extension 11 | */ 12 | export async function activate(docUri?: vscode.Uri) { 13 | // The extensionId is `publisher.name` from package.json 14 | const ext = vscode.extensions.getExtension('dbaumgarten.vscode-yolol')!; 15 | await ext.activate(); 16 | if (docUri !== undefined){ 17 | try { 18 | doc = await vscode.workspace.openTextDocument(docUri); 19 | editor = await vscode.window.showTextDocument(doc); 20 | } catch (e) { 21 | console.error(e); 22 | } 23 | } 24 | return ext 25 | } 26 | 27 | async function sleep(ms: number) { 28 | return new Promise(resolve => setTimeout(resolve, ms)); 29 | } 30 | 31 | export const getDocPath = (p: string) => { 32 | return path.resolve(__dirname, '../../testFixture', p); 33 | }; 34 | 35 | export const getDocUri = (p: string) => { 36 | return vscode.Uri.file(getDocPath(p)); 37 | }; 38 | 39 | export async function setTestContent(content: string): Promise { 40 | const all = new vscode.Range( 41 | doc.positionAt(0), 42 | doc.positionAt(doc.getText().length) 43 | ); 44 | return editor.edit(eb => eb.replace(all, content)); 45 | } -------------------------------------------------------------------------------- /examples/nolol/timing_control.nolol: -------------------------------------------------------------------------------- 1 | // statements on continous lines are merged, until the yolol line is full 2 | // then a new yolol line will be started 3 | :a=123 4 | :b=456 5 | :c=789 6 | :d="this is too long, so it will start a new line" 7 | 8 | :e=0 9 | goto jumplbl 10 | jumplbl> :f=123 // lines that have jump labels (that are in-use) can not be merged with previous lines 11 | :g="xyz" // lines following jump labels however can be merged with the previous line 12 | 13 | 14 | // Statements seperate by ; are ALWAYS placed in a single yolol line 15 | // if this is not possible, an error is thrown 16 | :x=0; :y=1; :z=2 17 | // preceding and following lines of a ;-line can be merged with the ;-line (if there is enough space) 18 | :q=666 19 | 20 | 21 | // auto-merging of lines can be prohibited with the $ sign 22 | // a line starting with $ will not be appended o previous lines 23 | :foo="a" 24 | $ :bar="b" 25 | // following lines however will be merged 26 | :baz="c" 27 | 28 | // if you want to prevent merging with following lines, place a $ at the end of the line 29 | :foo="d" $ 30 | :bar="e" 31 | 32 | // all this features can be combined 33 | :foo="a" 34 | // the following three statements and ONLY the three will be on one yolol line 35 | $ :a=0; b=1; c=2 $ 36 | :bar="b" 37 | 38 | // a line only containing a $ and nothing else will be translated to an empty line on yolol 39 | :a=123 40 | $ 41 | :b=456 42 | 43 | :done=1 44 | -------------------------------------------------------------------------------- /stdlib/src/math_basic.nolol: -------------------------------------------------------------------------------- 1 | // This file contains basic definitions and macros for math 2 | // Import using ' include "std/math" ' 3 | 4 | // The mathematical constant pi 5 | define math_pi=3.141 6 | 7 | // The mathematical constant e 8 | define math_e=2.718 9 | 10 | // Returns the absolute value of x. 11 | macro math_abs(x) expr 12 | (x>=0)*x-(x<0)*x 13 | end 14 | 15 | // If x is >0, returns 1 if <0 return -1, otherwise returns 0 16 | macro math_sign(x) expr 17 | (x>=0)-(x<=0) 18 | end 19 | 20 | // Returns the next lower integer to x 21 | macro math_floor(x) expr 22 | x/1000*1000 23 | end 24 | 25 | // Linearly interpolates between a and b based on f (100% of a when f=0 and 100% of b when f=1) 26 | macro math_lerp(a, b, f) expr 27 | (a * (1 - f)) + (b * f) 28 | end 29 | 30 | // Inverse linear interpolation 31 | // Returns a number between 0 and 1 based on f (0.0 when f=a and 1.0 when f=b) 32 | macro math_invlerp(a, b, f) expr 33 | (f - a) / (b - a) 34 | end 35 | 36 | // Returns x%m (even on basic chips) 37 | macro math_mod(x,m) expr 38 | x-x/m/1000*1000*m 39 | end 40 | 41 | // Returns the smaller number of x and y 42 | macro math_min(x, y) expr 43 | x + (y-x)*(x>y) 44 | end 45 | 46 | // Returns the larger number of x and y 47 | macro math_max(x, y) expr 48 | x + (y-x)*(x { 7 | 8 | before(async()=>{ 9 | await activate(getDocUri("correct.yolol")) 10 | }) 11 | 12 | it('Find on linux', async () => { 13 | let path = getExePath("linux") 14 | assert.equal(path,getContext().asAbsolutePath("./bin/linux/yodk")) 15 | }) 16 | 17 | it('Find on mac', async () => { 18 | let path = getExePath("darwin") 19 | assert.equal(path,getContext().asAbsolutePath("./bin/darwin/yodk")) 20 | }) 21 | 22 | it('Find on Windows', async () => { 23 | let path = getExePath("win32") 24 | assert.equal(path,getContext().asAbsolutePath("./bin/win32/yodk.exe")) 25 | }) 26 | 27 | it('Find with env var', async () => { 28 | process.env["YODK_EXECUTABLE"] = "/test/path/yodk" 29 | let path = getExePath("win32") 30 | process.env["YODK_EXECUTABLE"] = "" 31 | assert.equal(path,"/test/path/yodk") 32 | }) 33 | 34 | it('Answers on version', async () =>{ 35 | let result = await runYodkCommand(["version"]) 36 | assert.equal(result["code"],0) 37 | }) 38 | 39 | it('Errors on unknown', async () =>{ 40 | let result = await runYodkCommand(["unknown","command"]) 41 | assert.equal(result["code"],1) 42 | }) 43 | 44 | }) -------------------------------------------------------------------------------- /pkg/optimizers/expression_inversion_test.go: -------------------------------------------------------------------------------- 1 | package optimizers 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var inversionCases = map[string]string{ 8 | "a=123": "a=123", 9 | "a=not true": "a=not true", 10 | "x=a or b": "x=a or b", 11 | "x=not (a or b)": "x=not(a or b)", 12 | "x=(not a) and not b": "x=not(a or b)", 13 | "x=not (a==b)": "x=a!=b", 14 | "x=not (a > b)": "x=a<=b", 15 | "x=not (a >= b)": "x=a=5", 25 | "y=c++ >=5": "y=c++>=5", 26 | } 27 | 28 | func TestInversionOptimization(t *testing.T) { 29 | o := &ExpressionInversionOptimizer{} 30 | optimizationTesting(t, o, inversionCases) 31 | } 32 | 33 | func TestInversionOptimizationExp(t *testing.T) { 34 | o := &ExpressionInversionOptimizer{} 35 | expressionOptimizationTesting(t, o, inversionCases) 36 | } 37 | -------------------------------------------------------------------------------- /cmd/verify.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dbaumgarten/yodk/pkg/parser" 7 | "github.com/dbaumgarten/yodk/pkg/validators" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var debugLog bool 12 | var chipType string 13 | 14 | // verifyCmd represents the verify command 15 | var verifyCmd = &cobra.Command{ 16 | Use: "verify [file]+", 17 | Short: "Check if a yolol programm is valid", 18 | Long: `Tries to parse a yolol file`, 19 | Run: func(cmd *cobra.Command, args []string) { 20 | for _, filepath := range args { 21 | p := parser.NewParser() 22 | p.SetDebugLog(debugLog) 23 | file := loadInputFile(filepath) 24 | parsed, errs := p.Parse(file) 25 | exitOnError(errs, "parsing file '"+filepath+"'") 26 | 27 | err := validators.ValidateCodeLength(file) 28 | exitOnError(err, "validating code") 29 | 30 | chip, err := validators.AutoChooseChipType(chipType, filepath) 31 | exitOnError(err, "determining chip-type") 32 | 33 | err = validators.ValidateAvailableOperations(parsed, chip) 34 | exitOnError(err, "validating code") 35 | 36 | fmt.Println(filepath, "is valid") 37 | } 38 | }, 39 | Args: cobra.MinimumNArgs(1), 40 | } 41 | 42 | func init() { 43 | rootCmd.AddCommand(verifyCmd) 44 | verifyCmd.Flags().BoolVarP(&debugLog, "debug", "d", false, "Print debug logs while parsing") 45 | verifyCmd.Flags().StringVarP(&chipType, "chip", "c", "auto", "Chip-type to validate for. (auto|professional|advanced|basic)") 46 | } 47 | -------------------------------------------------------------------------------- /cmd/optimize.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "path" 7 | "strings" 8 | 9 | "github.com/dbaumgarten/yodk/pkg/optimizers" 10 | "github.com/dbaumgarten/yodk/pkg/parser" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var outputFile string 15 | 16 | // optimizeCmd represents the compile command 17 | var optimizeCmd = &cobra.Command{ 18 | Use: "optimize [file]+", 19 | Short: "Optimize yolo programs", 20 | Long: `Perform optimizations on yolol-programs`, 21 | Run: func(cmd *cobra.Command, args []string) { 22 | for _, file := range args { 23 | fmt.Println("Optimizing file:", file) 24 | optimize(file) 25 | } 26 | 27 | }, 28 | Args: cobra.MinimumNArgs(1), 29 | } 30 | 31 | func optimize(filepath string) { 32 | var outfile string 33 | if outputFile != "" { 34 | outfile = outputFile 35 | } else { 36 | outfile = strings.Replace(filepath, path.Ext(filepath), "", -1) + ".opt" + path.Ext(filepath) 37 | } 38 | p := parser.NewParser() 39 | file := loadInputFile(filepath) 40 | parsed, errs := p.Parse(file) 41 | if errs != nil { 42 | exitOnError(errs, "parsing file") 43 | } 44 | opt := optimizers.NewCompoundOptimizer() 45 | err := opt.Optimize(parsed) 46 | exitOnError(err, "performing optimisation") 47 | gen := parser.Printer{} 48 | generated, err := gen.Print(parsed) 49 | exitOnError(err, "generating code") 50 | ioutil.WriteFile(outfile, []byte(generated), 0700) 51 | } 52 | 53 | func init() { 54 | rootCmd.AddCommand(optimizeCmd) 55 | optimizeCmd.Flags().StringVarP(&outputFile, "out", "o", "", "The output file") 56 | } 57 | -------------------------------------------------------------------------------- /pkg/lsp/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /pkg/jsonrpc2/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /examples/nolol/ifelse.nolol: -------------------------------------------------------------------------------- 1 | // example demonstrating the use of if-else 2 | // nolol ifs are multi-line 3 | // when compiling thei are converted to yolol inline ifs if possible 4 | // if the body of the if is too large for an inline if, the if will be implemented using a goto 5 | if :day=="monday" then 6 | :msg="fuck" 7 | else if :day=="tuesday" then 8 | :msg="still stupid" 9 | else if :day=="wednesday" then 10 | // especially long string to force a multiline if 11 | :msg="half time!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" 12 | else if :day=="thursday" then 13 | :msg="almost..." 14 | else if :day=="friday" then 15 | :msg="yay" 16 | else 17 | :msg="Weekend!!!" 18 | // ifs can be nested 19 | if :day=="saturday" then 20 | :msg+=" PARTY!" 21 | else 22 | :msg+=" is over :(" 23 | end 24 | end 25 | 26 | // You can still use the plain old yolol-inline-ifs. You usually dont need them, as the compiler will figure out if he can use an inline-ip on its own. 27 | // However, if you ABSOLUTELY need an inline if (or an error if thats not possible), it can still be usefull. 28 | a=1; if 1==1 then :foo=1; lala=123 else :foo=2 end; b=2 29 | 30 | // If the condition of a branch can be evaluated at compile time (because it consists only of constants) 31 | // then the compiler will automatically remove any unreachable code (and if possible completely replace the if-statement with one of the cases 32 | define ENABLE_BONUS = 1 33 | if ENABLE_BONUS == 1 then 34 | :yeah=1 35 | else 36 | // this is unreachable and will not be included in the generated yolol 37 | :yeah=0 38 | end 39 | 40 | :other="done" 41 | :done=1 42 | -------------------------------------------------------------------------------- /pkg/optimizers/static_expressions_test.go: -------------------------------------------------------------------------------- 1 | package optimizers 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/dbaumgarten/yodk/pkg/parser" 7 | "github.com/dbaumgarten/yodk/pkg/testdata" 8 | ) 9 | 10 | var staticCases = map[string]string{ 11 | "a=123+100": "a=223", 12 | "a=b+100": "a=b+100", 13 | "a=123+100+a": "a=223+a", 14 | "a=a+(123+100)+b": "a=a+223+b", 15 | "a=a+(123+100)+b*(10*10)": "a=a+223+b*100", 16 | "a=0+b": "a=b", 17 | "a=b+0": "a=b", 18 | "a=b-0": "a=b", 19 | "a=0-b": "a=-b", 20 | "a=0*b": "a=0", 21 | "a=b*0": "a=0", 22 | "a=1*b": "a=b", 23 | "a=b*1": "a=b", 24 | "a=b/1": "a=b", 25 | "a=b^0": "a=1", 26 | "a=b^1": "a=b", 27 | "a=\"a\"*1": "a=\"a\"*1", 28 | } 29 | 30 | func TestStaticExpressions(t *testing.T) { 31 | p := parser.NewParser() 32 | parsed, err := p.Parse(testdata.TestProgram) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | opt := StaticExpressionOptimizer{} 37 | err = opt.Optimize(parsed) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | 42 | gen := parser.Printer{} 43 | generated, err := gen.Print(parsed) 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | 48 | err = testdata.ExecuteTestProgram(generated) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | } 53 | 54 | func TestStaticExpressions2(t *testing.T) { 55 | optimizationTesting(t, &StaticExpressionOptimizer{}, staticCases) 56 | } 57 | -------------------------------------------------------------------------------- /vscode-yolol/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | 👋 Hi! This file was autogenerated by tslint-to-eslint-config. 3 | https://github.com/typescript-eslint/tslint-to-eslint-config 4 | 5 | It represents the closest reasonable ESLint configuration to this 6 | project's original TSLint configuration. 7 | 8 | We recommend eventually switching this configuration to extend from 9 | the recommended rulesets in typescript-eslint. 10 | https://github.com/typescript-eslint/tslint-to-eslint-config/blob/master/docs/FAQs.md 11 | 12 | Happy linting! 💖 13 | */ 14 | module.exports = { 15 | "env": { 16 | "browser": true, 17 | "es6": true, 18 | "node": true 19 | }, 20 | "parser": "@typescript-eslint/parser", 21 | "parserOptions": { 22 | "project": "tsconfig.json", 23 | "sourceType": "module" 24 | }, 25 | "plugins": [ 26 | "@typescript-eslint" 27 | ], 28 | "rules": { 29 | "@typescript-eslint/indent": [ 30 | "error", 31 | "tab" 32 | ], 33 | "@typescript-eslint/member-delimiter-style": [ 34 | "error", 35 | { 36 | "multiline": { 37 | "delimiter": "semi", 38 | "requireLast": true 39 | }, 40 | "singleline": { 41 | "delimiter": "semi", 42 | "requireLast": false 43 | } 44 | } 45 | ], 46 | "@typescript-eslint/semi": [ 47 | "error", 48 | "always" 49 | ], 50 | "indent": "error", 51 | "semi": "error" 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /pkg/langserver/cache.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/dbaumgarten/yodk/pkg/lsp" 8 | "github.com/dbaumgarten/yodk/pkg/nolol" 9 | ) 10 | 11 | var NotFoundError = fmt.Errorf("File not found in cache") 12 | 13 | type Cache struct { 14 | Files map[lsp.DocumentURI]string 15 | Diagnostics map[lsp.DocumentURI]DiagnosticResults 16 | Lock *sync.Mutex 17 | LastOpenedYololFile lsp.DocumentURI 18 | } 19 | 20 | type DiagnosticResults struct { 21 | Variables []string 22 | AnalysisReport *nolol.AnalysisReport 23 | } 24 | 25 | func NewCache() *Cache { 26 | return &Cache{ 27 | Files: make(map[lsp.DocumentURI]string), 28 | Diagnostics: make(map[lsp.DocumentURI]DiagnosticResults), 29 | Lock: &sync.Mutex{}, 30 | } 31 | } 32 | 33 | func (c *Cache) Get(uri lsp.DocumentURI) (string, error) { 34 | c.Lock.Lock() 35 | defer c.Lock.Unlock() 36 | f, found := c.Files[uri] 37 | if !found { 38 | return "", NotFoundError 39 | } 40 | return f, nil 41 | } 42 | 43 | func (c *Cache) Set(uri lsp.DocumentURI, content string) { 44 | c.Lock.Lock() 45 | defer c.Lock.Unlock() 46 | c.Files[uri] = content 47 | } 48 | 49 | func (c *Cache) GetDiagnostics(uri lsp.DocumentURI) (*DiagnosticResults, error) { 50 | c.Lock.Lock() 51 | defer c.Lock.Unlock() 52 | f, found := c.Diagnostics[uri] 53 | if !found { 54 | return nil, NotFoundError 55 | } 56 | return &f, nil 57 | } 58 | 59 | func (c *Cache) SetDiagnostics(uri lsp.DocumentURI, content DiagnosticResults) { 60 | c.Lock.Lock() 61 | defer c.Lock.Unlock() 62 | c.Diagnostics[uri] = content 63 | } 64 | -------------------------------------------------------------------------------- /pkg/testdata/dataset.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/dbaumgarten/yodk/pkg/number" 8 | "github.com/dbaumgarten/yodk/pkg/vm" 9 | ) 10 | 11 | var TestProgram = `:testsum = (1 + 2) == 3 12 | :testsub = (3 - 1) == 2 13 | :testmul = 2*5 == 10 14 | :testdiv = 20 / 10 == 2 15 | :testmod = 11 % 10 == 1 16 | counter=0 17 | counter++ 18 | if counter < 20 then goto 7 end 19 | :testgot = counter == 20 20 | // comment1 21 | :testexp = 10^2 == 100 22 | :testeq = 42 == 42 and not (41 == 24) 23 | :testneq = 1 != 42 and not (1!=1) 24 | :testgt = 2 > 1 and not (1>2) and 5 > -5 25 | :testgte = 2 >= 1 and not (1 >= 2) and 2 >= 2 26 | :testlt = "a" < "b" and not ("b" < "a") and "z" > "a" 27 | :done = 1 28 | ` 29 | 30 | func ExecuteTestProgram(prog string) error { 31 | var err error 32 | 33 | v, err := vm.CreateFromSource(prog) 34 | if err != nil { 35 | return err 36 | } 37 | v.SetErrorHandler(vm.ErrorHandlerFunc(func(v *vm.VM, e error) bool { 38 | err = e 39 | return true 40 | })) 41 | 42 | v.SetLineExecutedHandler(vm.TerminateOnDoneVar) 43 | v.Resume() 44 | v.WaitForTermination() 45 | 46 | if err != nil { 47 | return err 48 | } 49 | 50 | if len(v.GetVariables()) == 0 { 51 | return fmt.Errorf("Program not executed") 52 | } 53 | 54 | for name, value := range v.GetVariables() { 55 | if strings.HasPrefix(name, ":test") { 56 | if !value.IsNumber() { 57 | return fmt.Errorf("Operator-test %s returend string '%s' instead of 1", name, value.String()) 58 | } 59 | if value.Number() != number.One { 60 | return fmt.Errorf("Operator-test %s failed", name) 61 | } 62 | } 63 | } 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/lsp/window.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This file contains the corresponding structures to the 6 | // "Window" messages part of the LSP specification. 7 | 8 | package lsp 9 | 10 | type ShowMessageParams struct { 11 | /** 12 | * The message type. See {@link MessageType}. 13 | */ 14 | Type MessageType `json:"type"` 15 | 16 | /** 17 | * The actual message. 18 | */ 19 | Message string `json:"message"` 20 | } 21 | 22 | type MessageType float64 23 | 24 | const ( 25 | /** 26 | * An error message. 27 | */ 28 | Error MessageType = 1 29 | /** 30 | * A warning message. 31 | */ 32 | Warning MessageType = 2 33 | /** 34 | * An information message. 35 | */ 36 | Info MessageType = 3 37 | /** 38 | * A log message. 39 | */ 40 | Log MessageType = 4 41 | ) 42 | 43 | type ShowMessageRequestParams struct { 44 | /** 45 | * The message type. See {@link MessageType}. 46 | */ 47 | Type MessageType `json:"type"` 48 | 49 | /** 50 | * The actual message. 51 | */ 52 | Message string `json:"message"` 53 | 54 | /** 55 | * The message action items to present. 56 | */ 57 | Actions []MessageActionItem `json:"actions,omitempty"` 58 | } 59 | 60 | type MessageActionItem struct { 61 | /** 62 | * A short title like 'Retry', 'Open Log' etc. 63 | */ 64 | Title string 65 | } 66 | 67 | type LogMessageParams struct { 68 | /** 69 | * The message type. See {@link MessageType}. 70 | */ 71 | Type MessageType `json:"type"` 72 | 73 | /** 74 | * The actual message. 75 | */ 76 | Message string `json:"message"` 77 | } 78 | -------------------------------------------------------------------------------- /pkg/lsp/registration.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This file contains the corresponding structures to the 6 | // "Client" part of the LSP specification. 7 | 8 | package lsp 9 | 10 | /** 11 | * General parameters to register for a capability. 12 | */ 13 | type Registration struct { 14 | /** 15 | * The id used to register the request. The id can be used to deregister 16 | * the request again. 17 | */ 18 | ID string `json:"id"` 19 | 20 | /** 21 | * The method / capability to register for. 22 | */ 23 | Method string `json:"method"` 24 | 25 | /** 26 | * Options necessary for the registration. 27 | */ 28 | RegisterOptions interface{} `json:"registerOptions,omitempty"` 29 | } 30 | 31 | type RegistrationParams struct { 32 | Registrations []Registration `json:"registrations"` 33 | } 34 | 35 | type TextDocumentRegistrationOptions struct { 36 | /** 37 | * A document selector to identify the scope of the registration. If set to null 38 | * the document selector provided on the client side will be used. 39 | */ 40 | DocumentSelector *DocumentSelector `json:"documentSelector"` 41 | } 42 | 43 | /** 44 | * General parameters to unregister a capability. 45 | */ 46 | type Unregistration struct { 47 | /** 48 | * The id used to unregister the request or notification. Usually an id 49 | * provided during the register request. 50 | */ 51 | ID string `json:"id"` 52 | 53 | /** 54 | * The method / capability to unregister for. 55 | */ 56 | Method string `json:"method"` 57 | } 58 | 59 | type UnregistrationParams struct { 60 | Unregisterations []Unregistration `json:"unregisterations"` 61 | } 62 | -------------------------------------------------------------------------------- /pkg/parser/parser_test.go: -------------------------------------------------------------------------------- 1 | package parser_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/dbaumgarten/yodk/pkg/parser" 7 | "github.com/dbaumgarten/yodk/pkg/parser/ast" 8 | "github.com/dbaumgarten/yodk/pkg/testdata" 9 | ) 10 | 11 | func TestParser(t *testing.T) { 12 | 13 | p := parser.NewParser() 14 | 15 | result, err := p.Parse(testdata.TestProgram) 16 | 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | if len(result.Lines) == 0 { 22 | t.Fatal("Parsed programm is empty") 23 | } 24 | } 25 | 26 | func TestParserMultipleErrors(t *testing.T) { 27 | 28 | prog := ` 29 | a = b + c c++ x=sin(x) 30 | a = b++c c-- b+- 31 | x = y + z 32 | y = if x then y=1 else z=1 end 33 | if x then y=1 else z=1 end 34 | if x then y=1 else z=1 35 | ` 36 | 37 | p := parser.NewParser() 38 | 39 | result, errs := p.Parse(prog) 40 | 41 | if errs != nil && len(errs.(parser.Errors)) != 3 { 42 | for _, err := range errs.(parser.Errors) { 43 | t.Log(err) 44 | } 45 | t.Fatalf("Found %d errors instead of %d", len(errs.(parser.Errors)), 3) 46 | } 47 | 48 | if result != nil && len(result.Lines) == 0 { 49 | t.Fatal("Parsed programm is empty") 50 | } 51 | } 52 | 53 | type nodePositionTester struct { 54 | *testing.T 55 | } 56 | 57 | func (o *nodePositionTester) Visit(node ast.Node, visitType int) error { 58 | if visitType == ast.PreVisit || visitType == ast.SingleVisit { 59 | startPos := node.Start() 60 | if startPos.Line == 0 && startPos.Coloumn == 0 { 61 | o.Fatalf("Empty position for %T", node) 62 | } 63 | } 64 | return nil 65 | } 66 | 67 | func TestNodePositions(t *testing.T) { 68 | tester := nodePositionTester{t} 69 | 70 | p := parser.NewParser() 71 | 72 | result, err := p.Parse(testdata.TestProgram) 73 | if err != nil { 74 | t.Fatal(err) 75 | } 76 | 77 | result.Accept(&tester) 78 | } 79 | -------------------------------------------------------------------------------- /stdlib/testcases.nolol: -------------------------------------------------------------------------------- 1 | // This file defines test-cases for macros of the standard-library 2 | 3 | include "std/math" 4 | include "std/string" 5 | include "std/logic" 6 | 7 | :pi=math_pi==3.141 8 | :e=math_e==2.718 9 | 10 | :abs1=math_abs(10)==10 11 | :abs2=math_abs(-10)==10 12 | :abs3=math_abs(0)==0 13 | 14 | :sign1=math_sign(10)==1 15 | :sign2=math_sign(-10)==-1 16 | :sign3=math_sign(0)==0 17 | 18 | :floor1=math_floor(5.5)==5 19 | :floor2=math_floor(5)==5 20 | 21 | :mod1=math_mod(11,10)==1 22 | :mod2=math_mod(5,-3)==2 23 | :mod3=math_mod(-5,3)==-2 24 | 25 | :min1=math_min(-5,5)==-5 26 | :min2=math_min(5,-5)==-5 27 | :min3=math_min(3,7)==3 28 | 29 | :max1=math_max(-5,5)==5 30 | :max2=math_max(5,-5)==5 31 | :max3=math_max(3,7)==7 32 | 33 | :clamp1=math_clamp(3,-5,5)==3 34 | :clamp2=math_clamp(3,0,5)==3 35 | :clamp3=math_clamp(-3,1,5)==1 36 | :clamp4=math_clamp(7,1,5)==5 37 | 38 | :lerp1=math_lerp(0,10,0.0)==0 39 | :lerp2=math_lerp(0,10,0.5)==5 40 | :lerp3=math_lerp(0,10,1.0)==10 41 | :lerp4=math_lerp(0,10,2.0)==20 42 | 43 | :invlerp1=math_invlerp(0,10,0)==0 44 | :invlerp2=math_invlerp(0,10,5)==0.5 45 | :invlerp3=math_invlerp(0,10,10)==1 46 | :invlerp4=math_invlerp(0,10,20)==2 47 | 48 | string="abc" 49 | string_pop(string,popout); :pop1=popout=="c" 50 | :pop2=string=="ab" 51 | 52 | string="World" 53 | :cont1=string_contains(string,"orl") 54 | :cont2=not string_contains(string,"lol") 55 | 56 | string="abcde" 57 | out="" 58 | string_reverse(string,out) 59 | :rev1=out=="edcba" 60 | :rev2=string=="" 61 | 62 | string="abcde" 63 | string_len(string,strlen); :len=strlen==5 64 | 65 | :tern1=logic_ternary(1,5,7)==5 66 | :tern2=logic_ternary(0,5,7)==7 67 | 68 | :abort=1; logic_continue_line(0); :abort=0 $ 69 | 70 | :xor1=logic_xor(1,0) 71 | :xor2=logic_xor(0,1) 72 | :xor3=logic_xor(1,1)==0 73 | :xor4=logic_xor(0,0)==0 74 | 75 | :done=1 -------------------------------------------------------------------------------- /pkg/optimizers/optimizer.go: -------------------------------------------------------------------------------- 1 | package optimizers 2 | 3 | import "github.com/dbaumgarten/yodk/pkg/parser/ast" 4 | 5 | // Optimizer is the common interface for all optimizers 6 | type Optimizer interface { 7 | // Optimize optimizes the given ast. The ast is mutated (obviously) 8 | Optimize(prog ast.Node) error 9 | } 10 | 11 | // ExpressionOptimizer is an additional optimizer interface, for optimizing single expressions 12 | type ExpressionOptimizer interface { 13 | // OptimizeExpression optimizes the given expression. The expression is mutated 14 | OptimizeExpression(prog ast.Expression) ast.Expression 15 | } 16 | 17 | // CompoundOptimizer wraps all other optimizers and executes them 18 | type CompoundOptimizer struct { 19 | seopt *StaticExpressionOptimizer 20 | varopt *VariableNameOptimizer 21 | comopt *CommentOptimizer 22 | expinv *ExpressionInversionOptimizer 23 | hasBeenInitialized bool 24 | } 25 | 26 | // NewCompoundOptimizer creates a new compound optimizer 27 | func NewCompoundOptimizer() *CompoundOptimizer { 28 | return &CompoundOptimizer{ 29 | seopt: &StaticExpressionOptimizer{}, 30 | varopt: NewVariableNameOptimizer(), 31 | comopt: &CommentOptimizer{}, 32 | expinv: &ExpressionInversionOptimizer{}, 33 | } 34 | } 35 | 36 | // Optimize is required to implement Optimizer 37 | func (co *CompoundOptimizer) Optimize(prog *ast.Program) error { 38 | 39 | if !co.hasBeenInitialized { 40 | co.varopt.InitializeByFrequency(prog, nil) 41 | co.hasBeenInitialized = true 42 | } 43 | 44 | err := co.seopt.Optimize(prog) 45 | if err != nil { 46 | return err 47 | } 48 | err = co.comopt.Optimize(prog) 49 | if err != nil { 50 | return err 51 | } 52 | err = co.expinv.Optimize(prog) 53 | if err != nil { 54 | return err 55 | } 56 | return co.varopt.Optimize(prog) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/langserver/settings.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | 7 | "github.com/dbaumgarten/yodk/pkg/validators" 8 | ) 9 | 10 | const ( 11 | FormatModeSpaceless = "Spaceless" 12 | FormatModeCompact = "Compact" 13 | FormatModeReadale = "Readable" 14 | LengthCheckModeStrict = "Strict" 15 | LengthCheckModeOptimize = "Optimize" 16 | LengthCheckModeOff = "Off" 17 | ) 18 | 19 | // Settings contains settings for the language-server 20 | type Settings struct { 21 | Yolol YololSettings `json:"yolol"` 22 | } 23 | 24 | // YololSettings contains settings specific to yolol 25 | type YololSettings struct { 26 | Formatting FormatSettings `json:"formatting"` 27 | LengthChecking LengthCheckSettings `json:"lengthChecking"` 28 | ChipType string `json:"chipType"` 29 | } 30 | 31 | // FormatSettings contains formatting settings 32 | type FormatSettings struct { 33 | Mode string `json:"mode"` 34 | } 35 | 36 | // LengthCheckSettings contains settings for the lenght-validation 37 | type LengthCheckSettings struct { 38 | Mode string `json:"mode"` 39 | } 40 | 41 | func (s *Settings) Read(inp interface{}) error { 42 | by, err := json.Marshal(inp) 43 | if err != nil { 44 | return err 45 | } 46 | err = json.Unmarshal(by, s) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | s.Yolol.ChipType = strings.ToLower(s.Yolol.ChipType) 52 | 53 | return nil 54 | } 55 | 56 | // DefaultSettings returns the default-settings for the server 57 | func DefaultSettings() *Settings { 58 | return &Settings{ 59 | Yolol: YololSettings{ 60 | Formatting: FormatSettings{ 61 | Mode: FormatModeCompact, 62 | }, 63 | LengthChecking: LengthCheckSettings{ 64 | Mode: LengthCheckModeStrict, 65 | }, 66 | ChipType: validators.ChipTypeAuto, 67 | }, 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /vscode-yolol/syntaxes/nolol.tmGrammar.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "nolol", 3 | "patterns": [ 4 | { 5 | "include": "#expression" 6 | } 7 | ], 8 | "repository": { 9 | "expression": { 10 | "patterns": [ 11 | { 12 | "include": "#keyword" 13 | }, 14 | { 15 | "include": "#label" 16 | }, 17 | { 18 | "include": "#string" 19 | }, 20 | { 21 | "include": "#comment" 22 | }, 23 | { 24 | "include": "#constant" 25 | }, 26 | { 27 | "include": "#function" 28 | }, 29 | { 30 | "include": "#operator" 31 | }, 32 | { 33 | "include": "#extvariable" 34 | }, 35 | { 36 | "include": "#variable" 37 | } 38 | ] 39 | }, 40 | "keyword": { 41 | "match": "(?i)\\b(if|then|else|end|define|while|do|goto|include|macro|break|continue|block|line|expr)\\b", 42 | "name": "keyword.control" 43 | }, 44 | "label": { 45 | "match": "^\\s*[a-zA-Z]+[a-zA-Z0-9_]*>", 46 | "name": "storage.type.string.go" 47 | }, 48 | "string": { 49 | "match": "\"[^\"]*\"", 50 | "name": "string" 51 | }, 52 | "constant": { 53 | "match": "(([0-9]+(\\.[0-9]+)?)e[0-9]+)|(0x[0-9a-fA-F]+)|(([0-9]+(\\.[0-9]+)?))", 54 | "name": "constant.numeric" 55 | }, 56 | "function": { 57 | "match": "([a-zA-Z]+)\\(", 58 | "captures": { 59 | "1": { 60 | "name": "support.function" 61 | } 62 | } 63 | }, 64 | "operator": { 65 | "match": "(?i)\\b(and|or|not)\\b", 66 | "name": "keyword.operator" 67 | }, 68 | "extvariable": { 69 | "match": ":[a-zA-Z0-9_:]+", 70 | "name": "variable.language" 71 | }, 72 | "variable": { 73 | "match": "[a-zA-Z]+[a-zA-Z0-9_]*", 74 | "name": "variable" 75 | }, 76 | "comment": { 77 | "begin": "//", 78 | "beginCaptures": { 79 | "0": { 80 | "name": "punctuation.definition.comment.go.mod" 81 | } 82 | }, 83 | "end": "$", 84 | "name": "comment.line" 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /cmd/compile.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "path" 8 | "strings" 9 | 10 | "github.com/dbaumgarten/yodk/pkg/nolol" 11 | "github.com/dbaumgarten/yodk/pkg/parser" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | // compileCmd represents the compile command 17 | var compileCmd = &cobra.Command{ 18 | Use: "compile [file]+", 19 | Short: "Compile nolol programms to yolol", 20 | Run: func(cmd *cobra.Command, args []string) { 21 | for _, file := range args { 22 | fmt.Println("Compiling file:", file) 23 | compileFile(file) 24 | } 25 | }, 26 | Args: cobra.MinimumNArgs(1), 27 | } 28 | 29 | func compileFile(fpath string) { 30 | var outfile string 31 | if outputFile != "" { 32 | outfile = outputFile 33 | } else { 34 | outfile = strings.Replace(fpath, path.Ext(fpath), ".yolol", -1) 35 | } 36 | converter := nolol.NewConverter() 37 | converter.SetDebug(debugLog) 38 | converter.SetChipType(chipType) 39 | 40 | converted, compileerr := converter.LoadFile(fpath).Convert() 41 | 42 | // compilation failed completely. Fail now! 43 | if converted == nil { 44 | exitOnError(compileerr, "converting '"+fpath+"' to yolol") 45 | } 46 | 47 | gen := parser.Printer{} 48 | generated, err := gen.Print(converted) 49 | exitOnError(err, "generating code") 50 | err = ioutil.WriteFile(outfile, []byte(generated), 0700) 51 | exitOnError(err, "writing file") 52 | 53 | if compileerr != nil { 54 | fmt.Println("Compilation succeeded with errors. Please check the output:", compileerr) 55 | os.Exit(1) 56 | } 57 | 58 | } 59 | 60 | func init() { 61 | rootCmd.AddCommand(compileCmd) 62 | compileCmd.Flags().StringVarP(&outputFile, "out", "o", "", "The output file") 63 | compileCmd.Flags().BoolVarP(&debugLog, "debug", "d", false, "Print debug logs while parsing") 64 | compileCmd.Flags().StringVarP(&chipType, "chip", "c", "auto", "Chip-type to validate for. (auto|professional|advanced|basic)") 65 | } 66 | -------------------------------------------------------------------------------- /pkg/testing/test_test.go: -------------------------------------------------------------------------------- 1 | package testing_test 2 | 3 | import ( 4 | "testing" 5 | 6 | thistesting "github.com/dbaumgarten/yodk/pkg/testing" 7 | ) 8 | 9 | func TestTestcase2(t *testing.T) { 10 | testcase := `scripts: 11 | - fizbuzz.yolol 12 | - fizbuzz.yolol 13 | cases: 14 | - name: TestOutput 15 | inputs: 16 | number: 0 17 | outputs: 18 | out: "fizzbuzz fizzbuzz fizz fizz buzz buzz fizz fizz fizz fizz buzz buzz fizz fizz fizzbuzz fizzbuzz fizz fizz buzz buzz fizz fizz fizz fizz buzz buzz fizz fizz fizzbuzz fizzbuzz fizz fizz buzz buzz fizz fizz fizz fizz buzz buzz fizz fizz fizzbuzz fizzbuzz fizz fizz buzz buzz " 19 | number: 102 20 | - name: TestOutput2 21 | inputs: 22 | number: 99 23 | outputs: 24 | out: "fizz fizz " 25 | ` 26 | script := `if :out==0 then :out="" end 27 | if not (:number<=100) then goto 7 end 28 | if :number%3==0 and :number%5==0 then :out+="fizzbuzz " goto 6 end 29 | if :number%3==0 then :out+="fizz " end 30 | if :number%5==0 then :out+="buzz " end 31 | :number++ goto 2 32 | ` 33 | 34 | script2 := `if :out==0 then :out="" end 35 | if not (:number<=100) then goto 7 end 36 | if :number%3==0 and :number%5==0 then :out+="fizzbuzz " goto 6 end 37 | if :number%3==0 then :out+="fizz " end 38 | if :number%5==0 then :out+="buzz " end 39 | :number++ goto 2 40 | ` 41 | test, err := thistesting.Parse([]byte(testcase), "") 42 | if err != nil { 43 | t.Error(err) 44 | } 45 | test.ScriptContents = make([]string, 2) 46 | test.ScriptContents[0] = script 47 | test.ScriptContents[1] = script2 48 | fails := test.Run(nil) 49 | if len(fails) > 0 { 50 | t.Log("Testcase had errors but should not") 51 | for _, f := range fails { 52 | t.Log(f) 53 | } 54 | t.FailNow() 55 | } 56 | 57 | test.Cases[0].Outputs["number"] = 1337 58 | fails = test.Run(nil) 59 | if len(fails) != 1 { 60 | t.Fatalf("Testcase should have 1 error, but had: %d", len(fails)) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | branches: 3 | except: 4 | - latest 5 | go: 1.14 6 | dist: bionic 7 | services: 8 | - xvfb 9 | env: 10 | - NODE_VERSION="16.1" GO111MODULE=on 11 | before_install: 12 | - nvm install $NODE_VERSION 13 | install: 14 | - make setup 15 | before_script: 16 | - export DISPLAY=:99.0; 17 | script: 18 | - make all VERSION=${TRAVIS_BRANCH} 19 | - ls -al 20 | before_deploy: | 21 | if [ "${TRAVIS_BRANCH}" == "develop" ] && [ "${TRAVIS_TAG}" == "" ]; then 22 | git tag -f latest 23 | git remote add gh https://${TRAVIS_REPO_SLUG%/*}:${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git 24 | git push -f gh latest 25 | git remote remove gh 26 | else 27 | echo Not updating latest tag 28 | fi 29 | deploy: 30 | - provider: releases 31 | name: Automated build of develop-branch 32 | api-key: $GH_TOKEN 33 | file: 34 | - yodk-win.zip 35 | - yodk-linux.zip 36 | - yodk-darwin.zip 37 | - vscode-yolol.vsix 38 | skip_cleanup: true 39 | edge: true 40 | overwrite: true 41 | prerelease: true 42 | target_commitish: $TRAVIS_COMMIT 43 | tag_name: latest 44 | body: "Use this release ONLY to test not-yet-released changes from the develop-branch. Use a versioned release (see below) for anything else!!!" 45 | on: 46 | tags: false 47 | branch: develop 48 | - provider: releases 49 | api-key: $GH_TOKEN 50 | file: 51 | - yodk-win.zip 52 | - yodk-linux.zip 53 | - yodk-darwin.zip 54 | - vscode-yolol.vsix 55 | skip_cleanup: true 56 | edge: true 57 | release_notes_file: "CHANGELOG.md" 58 | on: 59 | tags: true 60 | - provider: script 61 | script: make publish-vsix VERSION=${TRAVIS_TAG} TOKEN=${VSCODE_MARKETPLACE_TOKEN} 62 | skip_cleanup: true 63 | on: 64 | tags: true 65 | - provider: pages 66 | local_dir: docs 67 | skip_cleanup: true 68 | github_token: $GH_TOKEN 69 | on: 70 | branch: master -------------------------------------------------------------------------------- /vscode-yolol/syntaxes/yolol.tmGrammar.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "yolol", 3 | "patterns": [ 4 | { 5 | "include": "#expression" 6 | } 7 | ], 8 | "repository": { 9 | "expression": { 10 | "patterns": [ 11 | { 12 | "include": "#keyword" 13 | }, 14 | { 15 | "include": "#keyword2" 16 | }, 17 | { 18 | "include": "#string" 19 | }, 20 | { 21 | "include": "#comment" 22 | }, 23 | { 24 | "include": "#constant" 25 | }, 26 | { 27 | "include": "#function" 28 | }, 29 | { 30 | "include": "#operator" 31 | }, 32 | { 33 | "include": "#extvariable" 34 | }, 35 | { 36 | "include": "#variable" 37 | } 38 | ] 39 | }, 40 | "keyword": { 41 | "match": "(?i)(?<=^|\\s|[^a-zA-Z0-9_:])(not|abs|sqrt|sin|cos|tan|asin|acos|atan)(?=[^a-zA-Z0-9_:.]|$)", 42 | "name": "keyword.control" 43 | }, 44 | "keyword2": { 45 | "match": "(?i)(?<=^|\\s|[^a-zA-Z_:])(if|then|else|end|goto)+", 46 | "name": "keyword.control" 47 | }, 48 | "string": { 49 | "match": "\"[^\"]*\"", 50 | "name": "string" 51 | }, 52 | "constant": { 53 | "match": "(([0-9]+(\\.[0-9]+)?)e[0-9]+)|(0x[0-9a-fA-F]+)|(([0-9]+(\\.[0-9]+)?))", 54 | "name": "constant.numeric" 55 | }, 56 | "function": { 57 | "match": "([a-zA-Z]+)\\(", 58 | "captures": { 59 | "1": { 60 | "name": "support.function" 61 | } 62 | } 63 | }, 64 | "operator": { 65 | "match": "(?i)(?<=[^a-zA-Z0-9_:])(and|or)(?=[^a-zA-Z0-9_:])", 66 | "name": "keyword.operator" 67 | }, 68 | "extvariable": { 69 | "match": ":[a-zA-Z0-9_:]+", 70 | "name": "variable.language" 71 | }, 72 | "variable": { 73 | "match": "[a-zA-Z]+[a-zA-Z0-9_]*", 74 | "name": "variable" 75 | }, 76 | "comment": { 77 | "begin": "//", 78 | "beginCaptures": { 79 | "0": { 80 | "name": "punctuation.definition.comment.go.mod" 81 | } 82 | }, 83 | "end": "$", 84 | "name": "comment.line" 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /pkg/nolol/converter_interfaces.go: -------------------------------------------------------------------------------- 1 | package nolol 2 | 3 | import ( 4 | "github.com/dbaumgarten/yodk/pkg/nolol/nast" 5 | "github.com/dbaumgarten/yodk/pkg/parser/ast" 6 | ) 7 | 8 | // ConverterEmpty is part of the Sequenced-Builder-Pattern of the Converter 9 | type ConverterEmpty interface { 10 | Load(prog *nast.Program, files FileSystem) ConverterIncludes 11 | LoadFile(mainfile string) ConverterIncludes 12 | LoadFileEx(mainfile string, files FileSystem) ConverterIncludes 13 | SetDebug(b bool) ConverterEmpty 14 | SetChipType(chip string) ConverterEmpty 15 | } 16 | 17 | // ConverterIncludes is part of the Sequenced-Builder-Pattern of the Converter 18 | type ConverterIncludes interface { 19 | ProcessIncludes() ConverterExpansions 20 | Convert() (*ast.Program, error) 21 | RunConversion() ConverterDone 22 | Error() error 23 | GetIntermediateProgram() *nast.Program 24 | } 25 | 26 | // ConverterExpansions is part of the Sequenced-Builder-Pattern of the Converter 27 | type ConverterExpansions interface { 28 | ProcessCodeExpansion() ConverterNodes 29 | Error() error 30 | GetIntermediateProgram() *nast.Program 31 | } 32 | 33 | // ConverterNodes is part of the Sequenced-Builder-Pattern of the Converter 34 | type ConverterNodes interface { 35 | ProcessNodes() ConverterLines 36 | Error() error 37 | GetIntermediateProgram() *nast.Program 38 | } 39 | 40 | // ConverterLines is part of the Sequenced-Builder-Pattern of the Converter 41 | type ConverterLines interface { 42 | ProcessLineNumbers() ConverterFinal 43 | Error() error 44 | GetIntermediateProgram() *nast.Program 45 | } 46 | 47 | // ConverterFinal is part of the Sequenced-Builder-Pattern of the Converter 48 | type ConverterFinal interface { 49 | ProcessFinalize() ConverterDone 50 | Error() error 51 | GetIntermediateProgram() *nast.Program 52 | } 53 | 54 | // ConverterDone is part of the Sequenced-Builder-Pattern of the Converter 55 | type ConverterDone interface { 56 | Get() (*ast.Program, error) 57 | GetVariableTranslations() map[string]string 58 | Error() error 59 | GetIntermediateProgram() *nast.Program 60 | } 61 | -------------------------------------------------------------------------------- /pkg/langserver/stream.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "strconv" 12 | "strings" 13 | "sync" 14 | ) 15 | 16 | type StdioStream struct { 17 | in io.Reader 18 | out io.Writer 19 | bufin *bufio.Reader 20 | lock *sync.Mutex 21 | Log bool 22 | } 23 | 24 | func NewStdioStream() *StdioStream { 25 | return &StdioStream{ 26 | in: os.Stdin, 27 | out: os.Stdout, 28 | bufin: bufio.NewReader(os.Stdin), 29 | lock: &sync.Mutex{}, 30 | } 31 | } 32 | 33 | func (s *StdioStream) Write(ctx context.Context, data []byte) error { 34 | s.lock.Lock() 35 | defer s.lock.Unlock() 36 | if s.Log { 37 | log.Printf("Sent: %s", string(data)) 38 | } 39 | if _, err := fmt.Fprintf(s.out, "Content-Length: %d\r\n\r\n", len(data)); err != nil { 40 | return err 41 | } 42 | if _, err := s.out.Write(data); err != nil { 43 | return err 44 | } 45 | return nil 46 | } 47 | 48 | // ReadObject implements ObjectCodec. 49 | func (s *StdioStream) Read(ctx context.Context) ([]byte, error) { 50 | var contentLength uint64 51 | for { 52 | line, err := s.bufin.ReadString('\r') 53 | if err != nil { 54 | return nil, err 55 | } 56 | b, err := s.bufin.ReadByte() 57 | if err != nil { 58 | return nil, err 59 | } 60 | if b != '\n' { 61 | return nil, fmt.Errorf(`jsonrpc2: line endings must be \r\n`) 62 | } 63 | if line == "\r" { 64 | break 65 | } 66 | if strings.HasPrefix(line, "Content-Length: ") { 67 | line = strings.TrimPrefix(line, "Content-Length: ") 68 | line = strings.TrimSpace(line) 69 | var err error 70 | contentLength, err = strconv.ParseUint(line, 10, 32) 71 | if err != nil { 72 | return nil, err 73 | } 74 | } 75 | } 76 | if contentLength == 0 { 77 | return nil, fmt.Errorf("jsonrpc2: no Content-Length header found") 78 | } 79 | 80 | result, err := ioutil.ReadAll(io.LimitReader(s.bufin, int64(contentLength))) 81 | if err != nil { 82 | return nil, err 83 | } 84 | if s.Log { 85 | log.Printf("Received: %s", string(result)) 86 | } 87 | return result, err 88 | } 89 | -------------------------------------------------------------------------------- /pkg/lsp/protocol.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package lsp 6 | 7 | import ( 8 | "context" 9 | "log" 10 | 11 | "github.com/dbaumgarten/yodk/pkg/jsonrpc2" 12 | ) 13 | 14 | func canceller(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) { 15 | conn.Notify(context.Background(), "$/cancelRequest", &CancelParams{ID: *req.ID}) 16 | } 17 | 18 | func RunClient(ctx context.Context, stream jsonrpc2.Stream, client Client, opts ...interface{}) (*jsonrpc2.Conn, Server) { 19 | opts = append([]interface{}{clientHandler(client), jsonrpc2.Canceler(canceller)}, opts...) 20 | conn := jsonrpc2.NewConn(ctx, stream, opts...) 21 | return conn, &serverDispatcher{Conn: conn} 22 | } 23 | 24 | func RunServer(ctx context.Context, stream jsonrpc2.Stream, server Server, opts ...interface{}) (*jsonrpc2.Conn, Client) { 25 | opts = append([]interface{}{serverHandler(server), jsonrpc2.Canceler(canceller)}, opts...) 26 | conn := jsonrpc2.NewConn(ctx, stream, opts...) 27 | return conn, &clientDispatcher{Conn: conn} 28 | } 29 | 30 | func sendParseError(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request, err error) { 31 | if _, ok := err.(*jsonrpc2.Error); !ok { 32 | err = jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) 33 | } 34 | unhandledError(conn.Reply(ctx, req, nil, err)) 35 | } 36 | 37 | // unhandledError is used in places where an error may occur that cannot be handled. 38 | // This occurs in things like rpc handlers that are a notify, where we cannot 39 | // reply to the caller, or in a call when we are actually attempting to reply. 40 | // In these cases, there is nothing we can do with the error except log it, so 41 | // we do that in this function, and the presence of this function acts as a 42 | // useful reminder of why we are effectively dropping the error and also a 43 | // good place to hook in when debugging those kinds of errors. 44 | func unhandledError(err error) { 45 | if err == nil { 46 | return 47 | } 48 | log.Printf("%v", err) 49 | } 50 | -------------------------------------------------------------------------------- /pkg/jsonrpc2/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package jsonrpc2 6 | 7 | import ( 8 | "encoding/json" 9 | "log" 10 | "time" 11 | ) 12 | 13 | // Logger is an option you can pass to NewConn which is invoked for 14 | // all messages flowing through a Conn. 15 | // direction indicates if the message being recieved or sent 16 | // id is the message id, if not set it was a notification 17 | // elapsed is the time between a call being seen and the response, and is 18 | // negative for anything that is not a response. 19 | // method is the method name specified in the message 20 | // payload is the parameters for a call or notification, and the result for a 21 | // response 22 | type Logger = func(direction Direction, id *ID, elapsed time.Duration, method string, payload *json.RawMessage, err *Error) 23 | 24 | // Direction is used to indicate to a logger whether the logged message was being 25 | // sent or received. 26 | type Direction bool 27 | 28 | const ( 29 | // Send indicates the message is outgoing. 30 | Send = Direction(true) 31 | // Receive indicates the message is incoming. 32 | Receive = Direction(false) 33 | ) 34 | 35 | func (d Direction) String() string { 36 | switch d { 37 | case Send: 38 | return "send" 39 | case Receive: 40 | return "receive" 41 | default: 42 | panic("unreachable") 43 | } 44 | } 45 | 46 | // Log is an implementation of Logger that outputs using log.Print 47 | // It is not used by default, but is provided for easy logging in users code. 48 | func Log(direction Direction, id *ID, elapsed time.Duration, method string, payload *json.RawMessage, err *Error) { 49 | switch { 50 | case err != nil: 51 | log.Printf("%v failure [%v] %s %v", direction, id, method, err) 52 | case id == nil: 53 | log.Printf("%v notification %s %s", direction, method, *payload) 54 | case elapsed >= 0: 55 | log.Printf("%v response in %v [%v] %s %s", direction, elapsed, id, method, *payload) 56 | default: 57 | log.Printf("%v call [%v] %s %s", direction, id, method, *payload) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | homedir "github.com/mitchellh/go-homedir" 8 | "github.com/spf13/cobra" 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | var cfgFile string 13 | 14 | // rootCmd represents the base command when called without any subcommands 15 | var rootCmd = &cobra.Command{ 16 | Use: "yodk", 17 | Short: "YOLOL development kit", 18 | Long: `A toolkit to make starbase's ingame-language YOLOL less painful to use`, 19 | } 20 | 21 | // Execute adds all child commands to the root command and sets flags appropriately. 22 | // This is called by main.main(). It only needs to happen once to the rootCmd. 23 | func Execute() { 24 | if err := rootCmd.Execute(); err != nil { 25 | fmt.Println(err) 26 | os.Exit(1) 27 | } 28 | } 29 | 30 | func init() { 31 | cobra.OnInitialize(initConfig) 32 | 33 | // Here you will define your flags and configuration settings. 34 | // Cobra supports persistent flags, which, if defined here, 35 | // will be global for your application. 36 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.yodk.yaml)") 37 | // Cobra also supports local flags, which will only run 38 | // when this action is called directly. 39 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 40 | } 41 | 42 | // initConfig reads in config file and ENV variables if set. 43 | func initConfig() { 44 | if cfgFile != "" { 45 | // Use config file from the flag. 46 | viper.SetConfigFile(cfgFile) 47 | } else { 48 | // Find home directory. 49 | home, err := homedir.Dir() 50 | if err != nil { 51 | fmt.Println(err) 52 | os.Exit(1) 53 | } 54 | 55 | // Search config in home directory with name ".yodk" (without extension). 56 | viper.AddConfigPath(home) 57 | viper.SetConfigName(".yodk") 58 | } 59 | 60 | viper.BindPFlags(rootCmd.PersistentFlags()) 61 | viper.SetEnvPrefix("YODK") 62 | viper.AutomaticEnv() // read in environment variables that match 63 | 64 | // If a config file is found, read it in. 65 | if err := viper.ReadInConfig(); err == nil { 66 | fmt.Println("Using config file:", viper.ConfigFileUsed()) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ci/build-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # This script automatically creates the content of the docs-folder (at least some of it) from other files in this repo 5 | # It is automatically run by the ci-tool 6 | # This way, duplicatin between repo and documentation is reduced massively and the documentation is automatically kept up-to-date. 7 | 8 | rm -rf docs/generated || true 9 | mkdir -p docs/generated/code/yolol 10 | mkdir -p docs/generated/code/nolol 11 | mkdir -p docs/generated/cli/ 12 | mkdir -p docs/generated/tests 13 | 14 | cp examples/yolol/*.yolol docs/generated/code/yolol 15 | cp examples/nolol/*.nolol docs/generated/code/nolol 16 | cp examples/yolol/fizzbuzz_test.yaml docs/generated/tests 17 | 18 | ./yodk compile docs/generated/code/nolol/*.nolol 19 | ./yodk format docs/generated/code/nolol/*.nolol 20 | ./yodk format docs/generated/code/yolol/*.yolol 21 | ./yodk optimize docs/generated/code/yolol/unoptimized.yolol 22 | 23 | cp vscode-yolol/README.md docs/vscode-yolol.md 24 | cp README.md docs/README.md 25 | sed -i 's/https:\/\/dbaumgarten.github.io\/yodk\/#//g' docs/README.md 26 | echo "help" | ./yodk debug examples/yolol/fizzbuzz.yolol | grep -v EOF > docs/generated/cli/debug-help.txt 27 | 28 | cat docs/nolol-stdlib-header.md > docs/nolol-stdlib.md 29 | ./yodk docs stdlib/src/*.nolol -n 'std/$1' -r 'stdlib/src/([^_]*)(_.*)?.nolol' -c professional >> docs/nolol-stdlib.md 30 | 31 | echo Generating sitemap 32 | ROOTURL="https://dbaumgarten.github.io/yodk/#/" 33 | IGNORE_IN_SITEMAP="README.md,nolol-stdlib-header.md,_sidebar.md" 34 | 35 | cd docs 36 | cat << EOF > sitemap_new.xml 37 | 38 | 39 | 40 | ${ROOTURL} 41 | $(date -r README.md "+%Y-%m-%dT%H:%M:%S%:z") 42 | 43 | EOF 44 | 45 | for FILE in *.md; do 46 | if ! echo ${IGNORE_IN_SITEMAP} | grep -q ${FILE}; then 47 | CHANGEDATE=$(date -r ${FILE} "+%Y-%m-%dT%H:%M:%S%:z") 48 | cat << EOF >> sitemap_new.xml 49 | 50 | ${ROOTURL}${FILE} 51 | ${CHANGEDATE} 52 | 53 | EOF 54 | fi 55 | done 56 | 57 | cat << EOF >> sitemap_new.xml 58 | 59 | EOF 60 | 61 | cd .. 62 | 63 | 64 | chmod -R a+r docs 65 | -------------------------------------------------------------------------------- /vscode-yolol/README.md: -------------------------------------------------------------------------------- 1 | # VSCODE-YOLOL 2 | 3 | This vscode extension adds support for [YOLOL](https://wiki.starbasegame.com/index.php/YOLOL) to vscode. 4 | 5 | [YOLOL](https://wiki.starbasegame.com/index.php/YOLOL) is the ingame programming language of the upcoming game starbase. 6 | 7 | It is a part of the [yolol development kit](https://github.com/dbaumgarten/yodk). 8 | 9 | If you like this extension, please consider reviewing/rating it. 10 | If you experience any issues or would like to request a feature, please [open an issue](https://github.com/dbaumgarten/yodk/issues/new/choose). 11 | 12 | # Features 13 | - Syntax highlighting 14 | - Syntax validation 15 | - Automatic formatting 16 | - Auto-completion 17 | - Commands for optimizing yolol 18 | - Interactively debug YOLOL-code in vscode 19 | - Auto-type your code into Starbase (see [here](https://dbaumgarten.github.io/yodk/#/vscode-instructions?id=auto-typing-into-starbase) for the Shortcuts) 20 | - Also supports nolol 21 | 22 | # Instructions 23 | There are detailed instructions about the features of this extension and how to use them [here](https://dbaumgarten.github.io/yodk/#/vscode-instructions). 24 | 25 | # Installation 26 | This extension is available from the [vscode marketplace](https://marketplace.visualstudio.com/items?itemName=dbaumgarten.vscode-yolol). 27 | 28 | ## Dependencies 29 | This extension comes bundled with the yodk executable. If you open a terminal in vscode, the bundled yodk-binary will already be inside the PATH. 30 | 31 | You can however set the environment variable YODK_EXECUTABLE to a path to your own yodk binary. This is helpfull for development. 32 | 33 | ## Manual install 34 | You can find all versions of the extension for manual install [here](https://github.com/dbaumgarten/yodk/releases). 35 | 36 | ## From Source / For Devs 37 | Clone repository. 38 | Copy the directory vscode-yolol to your vscode extension directory. 39 | Run ```npm install``` in the copied directory. 40 | Place the yodk executable (or a symlink to it) in the bin folder or set the environment variable YODK_EXECUTABLE to the path to your yodk binary. 41 | 42 | # ATTENTION 43 | This extension is still work-in-progress, does contain bugs and may break any time. 44 | If you find bugs or want to contribute to the development please open an issue. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guidelines 2 | 3 | If you found a bug, need a feature or whatever, just open an issue. 4 | If you are willing to implement it yourself: great, but you probably should wait with implementing anything until it has been discussed in the issue. 5 | (Otherwise it could happen, that your PR is (for whatever reason) rejected and all your hard work is moot). 6 | 7 | All PRs are done against the **develop** branch and will then be released with the next release. 8 | 9 | ## Build-environment 10 | 11 | This whole project is usually built on Windows with WSL or on Linux. While building is probably possible on plain Windows, it would require a whole lot of messing with cygwin or similar. 12 | 13 | If all you want to do are changes to the go-binary or the nolol stdlib, pure windows is fine. 14 | 15 | ## Full Dev-Setup 16 | ### Tools 17 | - bash 18 | - git 19 | - GNU make 20 | - go >= 1.14 21 | - nodejs >= v16.1.0 22 | - npm >= 7.11.2 23 | - vscode (to debug the vscode-yolol extension) 24 | 25 | ### Setup 26 | - Clone the repo 27 | - cd into the directory 28 | - ```make setup``` will checkout git-submodules, download go-dependencies and npm-dependencies 29 | - ```make``` will build and test everything 30 | - Check the makefile for possible make-commands (like ```make test```, ```make binaries```) 31 | 32 | 33 | ## Windows Dev-Setup 34 | (For simple changes to the go-code or the stdlib) 35 | 36 | ### Tools 37 | - go >= 1.14 38 | - https://github.com/elazarl/go-bindata-assetfs 39 | 40 | ### Setup 41 | - Clone the repo 42 | - cd into the directory 43 | - ```go build``` builds the yodk-binary for your current plattform 44 | - ```go test ./...``` will run all the go-tests 45 | 46 | ## Changes to stdlib 47 | Whenever files in stdlib/src are changed, the go-bindata assetfs tool needs to be run, to include the changes into the auto-generated code. 48 | ```make setup``` will install this tool automatically. On windows you need to run the following commands to install it: 49 | 50 | ``` 51 | go get github.com/go-bindata/go-bindata/... 52 | go get github.com/elazarl/go-bindata-assetfs/... 53 | ``` 54 | 55 | After installing, you can run the tool: 56 | - On linux/wsl: ```make stdlib``` 57 | - On windows: Go to the stdlib directory and run ```go-bindata-assetfs.exe -pkg stdlib -prefix src/ ./src``` -------------------------------------------------------------------------------- /cmd/format.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "strings" 8 | 9 | "github.com/dbaumgarten/yodk/pkg/nolol" 10 | "github.com/dbaumgarten/yodk/pkg/parser" 11 | "github.com/dbaumgarten/yodk/pkg/util" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var formatMode string 16 | 17 | // formatCmd represents the format command 18 | var formatCmd = &cobra.Command{ 19 | Use: "format [file]+", 20 | Short: "Format yolol/nolol files", 21 | 22 | Run: func(cmd *cobra.Command, args []string) { 23 | for _, file := range args { 24 | fmt.Println("Formatting file:", file) 25 | format(file) 26 | } 27 | }, 28 | } 29 | 30 | func format(filepath string) { 31 | 32 | if formatMode != "readable" && formatMode != "compact" { 33 | fmt.Println("Fomatting mode must be one of: readable|compact|spaceless") 34 | os.Exit(1) 35 | } 36 | 37 | file := loadInputFile(filepath) 38 | generated := "" 39 | var err error 40 | if strings.HasSuffix(filepath, ".yolol") { 41 | p := parser.NewParser() 42 | parsed, errs := p.Parse(file) 43 | if errs != nil { 44 | exitOnError(errs, "parsing file") 45 | } 46 | gen := parser.Printer{} 47 | switch formatMode { 48 | case "readable": 49 | gen.Mode = parser.PrintermodeReadable 50 | break 51 | case "compact": 52 | gen.Mode = parser.PrintermodeCompact 53 | break 54 | } 55 | generated, err = gen.Print(parsed) 56 | exitOnError(err, "generating code") 57 | err = util.CheckForFormattingErrorYolol(parsed, generated) 58 | exitOnError(err, "formatting code") 59 | } else if strings.HasSuffix(filepath, ".nolol") { 60 | p := nolol.NewParser() 61 | parsed, errs := p.Parse(file) 62 | if errs != nil { 63 | exitOnError(errs, "parsing file") 64 | } 65 | printer := nolol.NewPrinter() 66 | generated, err = printer.Print(parsed) 67 | exitOnError(err, "generating code") 68 | err = util.CheckForFormattingErrorNolol(parsed, generated) 69 | exitOnError(err, "formatting code") 70 | } else { 71 | exitOnError(fmt.Errorf("Unsupported file-type"), "opening file") 72 | } 73 | 74 | ioutil.WriteFile(filepath, []byte(generated), 0700) 75 | } 76 | 77 | func init() { 78 | rootCmd.AddCommand(formatCmd) 79 | formatCmd.Flags().StringVarP(&formatMode, "mode", "m", "compact", "Formatting mode [readable,compact,spaceless]") 80 | } 81 | -------------------------------------------------------------------------------- /examples/yolol/fizzbuzz_test.yaml: -------------------------------------------------------------------------------- 1 | # required: list of scripts to run in parallel. Can be as many as you like 2 | scripts: 3 | - fizzbuzz.yolol 4 | # optional: stop execution once one of the listed global variables has the given value. 5 | # this is checked after every executed line, which means the line which sets the done-variable is executed completely before stopping the VM 6 | # default is "done: 1" 7 | stopwhen: 8 | number: 101 9 | # optional: if true, ignore runtime-errors during script-execution. Default: false 10 | ignoreerrs: false 11 | # optional: Stop execution after running set amount of lines (per script) 12 | # default value is 2000. Set to -1 for unlimited 13 | maxlines: 2000 14 | # the type of yolol-chip to use for executing the tests (auto, basic, advanced or professional). Default: auto 15 | chiptype: auto 16 | # optional, defaults to false. If true, keep state (variables, current line etc.) between cases, if false every cases starts from a fresh state 17 | sequential: false 18 | # required: list of testcases 19 | cases: 20 | - name: TestOutput 21 | # optional: global variables to set before running. ':' can be omitted 22 | inputs: 23 | number: 0 24 | 25 | # optional: expected value for global variables after running 26 | # it he values after execution the scipts do not match the values here, the test fails 27 | outputs: 28 | out: "fizzbuzz fizz buzz fizz fizz buzz fizz fizzbuzz fizz buzz fizz fizz buzz fizz fizzbuzz fizz buzz fizz fizz buzz fizz fizzbuzz fizz buzz fizz fizz buzz fizz fizzbuzz fizz buzz fizz fizz buzz fizz fizzbuzz fizz buzz fizz fizz buzz fizz fizzbuzz fizz buzz fizz fizz buzz " 29 | number: 101 30 | - name: TestOutput2 31 | inputs: 32 | number: 0 33 | # optional: the global "stopwhen" value from above can be overriden on a per test-case basis 34 | stopwhen: 35 | number: 10 36 | outputs: 37 | out: "fizzbuzz fizz buzz fizz fizz " 38 | - name: TestOutput2 39 | inputs: 40 | number: 0 41 | # optional: Amount of lines to run for this specific test-case. The case finishes once this (or the global maxlines) is reached 42 | # this number is relative to the start of the test-case, so it does not matter if previous cases have already run lines (even when sequential: true is set). 43 | maxlines: 10 44 | outputs: 45 | out: "fizzbuzz " -------------------------------------------------------------------------------- /pkg/optimizers/optimizer_test.go: -------------------------------------------------------------------------------- 1 | package optimizers 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/dbaumgarten/yodk/pkg/parser" 8 | "github.com/dbaumgarten/yodk/pkg/parser/ast" 9 | "github.com/dbaumgarten/yodk/pkg/testdata" 10 | ) 11 | 12 | func TestOptimizers(t *testing.T) { 13 | p := parser.NewParser() 14 | parsed, err := p.Parse(testdata.TestProgram) 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | opt := NewCompoundOptimizer() 19 | err = opt.Optimize(parsed) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | gen := parser.Printer{} 25 | generated, err := gen.Print(parsed) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | err = testdata.ExecuteTestProgram(generated) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | } 35 | 36 | func optimizationTesting(t *testing.T, o Optimizer, cases map[string]string) { 37 | p := parser.NewParser() 38 | prin := parser.Printer{} 39 | for in, expected := range cases { 40 | parsed, err := p.Parse(in) 41 | if err != nil { 42 | t.Fatalf("Error when parsing test-case '%s':%s", in, err.Error()) 43 | } 44 | err = o.Optimize(parsed) 45 | if err != nil { 46 | t.Fatalf("Error when optimizing test-case '%s':%s", in, err.Error()) 47 | } 48 | optimized, err := prin.Print(parsed) 49 | if err != nil { 50 | t.Fatalf("Error when printing optimized code for '%s':%s", in, err.Error()) 51 | } 52 | optimized = strings.Trim(optimized, " \n") 53 | if optimized != expected { 54 | t.Fatalf("Wrong optimized output for '%s'. Wanted '%s' but got '%s'", in, expected, optimized) 55 | } 56 | } 57 | } 58 | 59 | func expressionOptimizationTesting(t *testing.T, o ExpressionOptimizer, cases map[string]string) { 60 | p := parser.NewParser() 61 | prin := parser.Printer{} 62 | for in, expected := range cases { 63 | parsed, err := p.Parse(in) 64 | if err != nil { 65 | t.Fatalf("Error when parsing test-case '%s':%s", in, err.Error()) 66 | } 67 | parsed.Lines[0].Statements[0].(*ast.Assignment).Value = o.OptimizeExpression(parsed.Lines[0].Statements[0].(*ast.Assignment).Value) 68 | 69 | optimized, err := prin.Print(parsed) 70 | if err != nil { 71 | t.Fatalf("Error when printing optimized code for '%s':%s", in, err.Error()) 72 | } 73 | optimized = strings.Trim(optimized, " \n") 74 | if optimized != expected { 75 | t.Fatalf("Wrong optimized output for '%s'. Wanted '%s' but got '%s'", in, expected, optimized) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /pkg/util/check_formatting.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "regexp" 7 | 8 | "github.com/dbaumgarten/yodk/pkg/nolol" 9 | "github.com/dbaumgarten/yodk/pkg/nolol/nast" 10 | "github.com/dbaumgarten/yodk/pkg/parser" 11 | "github.com/dbaumgarten/yodk/pkg/parser/ast" 12 | ) 13 | 14 | var positionRegex = regexp.MustCompile(`("Line":\d+)|("Coloumn":\d+)`) 15 | var errmsg = "Formatting failed because of a bug (%s). Please open an issue containing the yolol-code that caused this at https://github.com/dbaumgarten/yodk/issues\n\nResult of formatting:\n\n%s\n" 16 | 17 | func getErrmsgCompile(formatted string) error { 18 | return fmt.Errorf(errmsg, "Formatted output is no valid program", formatted) 19 | } 20 | 21 | func getErrmsgEqual(formatted string) error { 22 | return fmt.Errorf(errmsg, "Formatted output is not semantically equivalent", formatted) 23 | } 24 | 25 | // CheckForFormattingErrorYolol checks if the ast before and after formatting are identical 26 | func CheckForFormattingErrorYolol(unformatted *ast.Program, formatted string) error { 27 | parser := parser.NewParser() 28 | formattedProg, err := parser.Parse(formatted) 29 | if err != nil { 30 | return getErrmsgCompile(formatted) 31 | } 32 | checkstring1 := ComputeASTCheckstring(unformatted) 33 | checkstring2 := ComputeASTCheckstring(formattedProg) 34 | 35 | if checkstring1 != checkstring2 { 36 | return getErrmsgEqual(formatted) 37 | } 38 | 39 | return nil 40 | } 41 | 42 | // CheckForFormattingErrorNolol checks if the ast before and after formatting are identical 43 | func CheckForFormattingErrorNolol(unformatted *nast.Program, formatted string) error { 44 | parser := nolol.NewParser() 45 | formattedProg, err := parser.Parse(formatted) 46 | if err != nil { 47 | return getErrmsgCompile(formatted) 48 | } 49 | checkstring1 := ComputeASTCheckstring(unformatted) 50 | checkstring2 := ComputeASTCheckstring(formattedProg) 51 | 52 | if checkstring1 != checkstring2 { 53 | fmt.Println(checkstring1) 54 | fmt.Println("------------------------") 55 | fmt.Println(checkstring2) 56 | return getErrmsgEqual(formatted) 57 | } 58 | 59 | return nil 60 | } 61 | 62 | // ComputeASTCheckstring computes a string-representation of an ast. 63 | // Two asts are identical, if their checkstrings are identical 64 | func ComputeASTCheckstring(prog ast.Node) string { 65 | chkstr, _ := json.Marshal(prog) 66 | // remove position dependent substrings 67 | chkstr = positionRegex.ReplaceAll(chkstr, []byte("")) 68 | return string(chkstr) 69 | } 70 | -------------------------------------------------------------------------------- /pkg/lsp/printers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This file contains formatting functions for types that 6 | // are commonly printed in debugging information. 7 | // They are separated from their types and gathered here as 8 | // they are hand written and not generated from the spec. 9 | // They should not be relied on for programmatic use (their 10 | // results should never be parsed for instance) but are meant 11 | // for temporary debugging and error messages. 12 | 13 | package lsp 14 | 15 | import ( 16 | "fmt" 17 | ) 18 | 19 | func (p Position) Format(f fmt.State, c rune) { 20 | fmt.Fprintf(f, "%d", int(p.Line)+1) 21 | if p.Character >= 0 { 22 | fmt.Fprintf(f, ":%d", int(p.Character)+1) 23 | } 24 | } 25 | 26 | func (r Range) Format(f fmt.State, c rune) { 27 | switch { 28 | case r.Start == r.End || r.End.Line < 0: 29 | fmt.Fprintf(f, "%v", r.Start) 30 | case r.End.Line == r.Start.Line: 31 | fmt.Fprintf(f, "%v¦%d", r.Start, int(r.End.Character)+1) 32 | default: 33 | fmt.Fprintf(f, "%v¦%v", r.Start, r.End) 34 | } 35 | } 36 | 37 | func (l Location) Format(f fmt.State, c rune) { 38 | fmt.Fprintf(f, "%s:%v", l.URI, l.Range) 39 | } 40 | 41 | func (s DiagnosticSeverity) Format(f fmt.State, c rune) { 42 | switch s { 43 | case SeverityError: 44 | fmt.Fprint(f, "Error") 45 | case SeverityWarning: 46 | fmt.Fprint(f, "Warning") 47 | case SeverityInformation: 48 | fmt.Fprint(f, "Information") 49 | case SeverityHint: 50 | fmt.Fprint(f, "Hint") 51 | } 52 | } 53 | 54 | func (d Diagnostic) Format(f fmt.State, c rune) { 55 | fmt.Fprintf(f, "%v:%v from %v at %v: %v", d.Severity, d.Code, d.Source, d.Range, d.Message) 56 | } 57 | 58 | func (i CompletionItem) Format(f fmt.State, c rune) { 59 | fmt.Fprintf(f, "%v %v %v", i.Label, i.Detail, CompletionItemKind(i.Kind)) 60 | } 61 | 62 | func (k CompletionItemKind) Format(f fmt.State, c rune) { 63 | switch k { 64 | case StructCompletion: 65 | fmt.Fprintf(f, "struct") 66 | case FunctionCompletion: 67 | fmt.Fprintf(f, "func") 68 | case VariableCompletion: 69 | fmt.Fprintf(f, "var") 70 | case TypeParameterCompletion: 71 | fmt.Fprintf(f, "type") 72 | case FieldCompletion: 73 | fmt.Fprintf(f, "field") 74 | case InterfaceCompletion: 75 | fmt.Fprintf(f, "interface") 76 | case ConstantCompletion: 77 | fmt.Fprintf(f, "const") 78 | case MethodCompletion: 79 | fmt.Fprintf(f, "method") 80 | case ModuleCompletion: 81 | fmt.Fprintf(f, "package") 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /vscode-yolol/src/test/diagnostics.test.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import * as assert from 'assert' 3 | import { getDocUri, activate } from './helper' 4 | 5 | describe('Should get diagnostics', () => { 6 | 7 | describe('Diagnose errors', () => { 8 | it('Diagnoses errors in yolol', async () => { 9 | const docUri = getDocUri('has_errors.yolol') 10 | await testDiagnostics(docUri, [ 11 | { message: 'Expected token \'end\'(Keyword). Found Token: \'then\'(Keyword)', range: toRange(1, 27, 1, 27), severity: vscode.DiagnosticSeverity.Error, source: 'parser' }, 12 | { message: 'Expected an assignment-operator. Found Token: \':number\'(ID)', range: toRange(4, 4, 4, 4), severity: vscode.DiagnosticSeverity.Error, source: 'parser' } 13 | ]) 14 | }) 15 | 16 | it('Diagnoses errors in nolol', async () => { 17 | const docUri = getDocUri('has_errors.nolol') 18 | await testDiagnostics(docUri, [ 19 | { message: 'Expected newline. Found Token: \'do\'(Keyword)', range: toRange(9, 23, 9, 23), severity: vscode.DiagnosticSeverity.Error, source: 'parser' }, 20 | ]) 21 | }) 22 | }) 23 | 24 | describe('Diagnose no wrong errors', () => { 25 | it('Diagnoses no errors in correct yolol', async () => { 26 | const docUri = getDocUri('correct.yolol') 27 | await testNoErrors(docUri) 28 | }) 29 | 30 | it('Diagnoses no errors in correct nolol', async () => { 31 | const docUri = getDocUri('correct.nolol') 32 | await testNoErrors(docUri) 33 | }) 34 | }) 35 | 36 | }) 37 | 38 | function toRange(sLine: number, sChar: number, eLine: number, eChar: number) { 39 | const start = new vscode.Position(sLine, sChar) 40 | const end = new vscode.Position(eLine, eChar) 41 | return new vscode.Range(start, end) 42 | } 43 | 44 | async function testNoErrors(docUri: vscode.Uri) { 45 | await activate(docUri) 46 | 47 | const actualDiagnostics = vscode.languages.getDiagnostics(docUri); 48 | assert.equal(actualDiagnostics.length,0) 49 | } 50 | 51 | async function testDiagnostics(docUri: vscode.Uri, expectedDiagnostics: vscode.Diagnostic[]) { 52 | await activate(docUri) 53 | 54 | const actualDiagnostics = vscode.languages.getDiagnostics(docUri); 55 | 56 | assert.equal(actualDiagnostics.length, expectedDiagnostics.length); 57 | 58 | expectedDiagnostics.forEach((expectedDiagnostic, i) => { 59 | const actualDiagnostic = actualDiagnostics[i] 60 | assert.equal(actualDiagnostic.message, expectedDiagnostic.message) 61 | assert.deepEqual(actualDiagnostic.range, expectedDiagnostic.range) 62 | assert.equal(actualDiagnostic.severity, expectedDiagnostic.severity) 63 | }) 64 | } -------------------------------------------------------------------------------- /pkg/validators/code_length_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/dbaumgarten/yodk/pkg/parser" 7 | ) 8 | 9 | var code1 = `if 1!=b then goto 3 end if b==1 then c=1 end if b==2 then c=2 end 10 | if b==3 then c=3 end goto 1 a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 11 | if 1!=b then goto 3 end if b==1 then c=1 end if b==2 then c=2 end 12 | if b==3 then c=3 end goto 1 a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 13 | if 1!=b then goto 3 end if b==1 then c=1 end if b==2 then c=2 end 14 | if b==3 then c=3 end goto 1 a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 15 | ` 16 | 17 | var code2 = `if 1!=b then goto 3 end if b==1 then c=1 end if b==2 then c=2 end 18 | if b==3 then c=3 end goto 1 a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 19 | if 1!=b then goto 3 end if b==1 then c=1 end if b==2 then c=2 end 20 | if b==3 then c=3 end goto 1 a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 21 | if 1!=b then goto 3 end if b==1 then c=1 end if b==2 then c=2 end 22 | if b==3 then c=3 end goto 1 a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 23 | if 1!=b then goto 3 end if b==1 then c=1 end if b==2 then c=2 end 24 | if b==3 then c=3 end goto 1 a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 25 | if 1!=b then goto 3 end if b==1 then c=1 end if b==2 then c=2 end 26 | if b==3 then c=3 end goto 1 a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 27 | if 1!=b then goto 3 end if b==1 then c=1 end if b==2 then c=2 end 28 | if b==3 then c=3 end goto 1 a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 29 | if 1!=b then goto 3 end if b==1 then c=1 end if b==2 then c=2 end 30 | if b==3 then c=3 end goto 1 a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 31 | if 1!=b then goto 3 end if b==1 then c=1 end if b==2 then c=2 end 32 | if b==3 then c=3 end goto 1 a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 33 | if 1!=b then goto 3 end if b==1 then c=1 end if b==2 then c=2 end 34 | if b==3 then c=3 end goto 1 a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 35 | if 1!=b then goto 3 end if b==1 then c=1 end if b==2 then c=2 end 36 | if b==3 then c=3 end goto 1 a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 37 | if 1!=b then goto 3 end if b==1 then c=1 end if b==2 then c=2 end 38 | if b==3 then c=3 end goto 1 a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 39 | if 1!=b then goto 3 end if b==1 then c=1 end if b==2 then c=2 end 40 | ` 41 | 42 | func TestValidateCodeLength(t *testing.T) { 43 | err := ValidateCodeLength(code1) 44 | if err == nil { 45 | t.Fatal("Did not find overlong line") 46 | } 47 | 48 | if err.(*parser.Error).StartPosition.Line != 4 { 49 | t.Fatal("Wrong line for overlong line error") 50 | } 51 | 52 | err = ValidateCodeLength(code2) 53 | if err == nil { 54 | t.Fatal("Did not find too many lines") 55 | } 56 | 57 | if err.(*parser.Error).StartPosition.Line != 21 { 58 | t.Fatal("Wrong line for overlong program error") 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pkg/nolol/converter_builtins.go: -------------------------------------------------------------------------------- 1 | package nolol 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/dbaumgarten/yodk/pkg/nolol/nast" 8 | "github.com/dbaumgarten/yodk/pkg/parser" 9 | "github.com/dbaumgarten/yodk/pkg/parser/ast" 10 | ) 11 | 12 | // reservedTimeVariable is the variable used to track passed time 13 | var reservedTimeVariable = "_time" 14 | 15 | // convert a built-in function to yolol 16 | func (c *Converter) convertFuncCall(function *nast.FuncCall, visitType int) error { 17 | 18 | if visitType != ast.PreVisit { 19 | return nil 20 | } 21 | 22 | nfunc := strings.ToLower(function.Function) 23 | switch nfunc { 24 | case "time": 25 | // time is a nolol-built-in function 26 | if len(function.Arguments) != 0 { 27 | return &parser.Error{ 28 | Message: "The time() function takes no arguments", 29 | StartPosition: function.Start(), 30 | EndPosition: function.End(), 31 | } 32 | } 33 | c.usesTimeTracking = true 34 | return ast.NewNodeReplacementSkip(&ast.Dereference{ 35 | Variable: c.varnameOptimizer.OptimizeVarName(reservedTimeVariable), 36 | }) 37 | } 38 | unaryops := []string{"abs", "sqrt", "sin", "cos", "tan", "asin", "acos", "atan"} 39 | for _, unaryop := range unaryops { 40 | if unaryop == nfunc { 41 | if len(function.Arguments) != 1 { 42 | return &parser.Error{ 43 | Message: "The yolol-functions all take exactly one argument", 44 | StartPosition: function.Start(), 45 | EndPosition: function.End(), 46 | } 47 | } 48 | return ast.NewNodeReplacement(&ast.UnaryOperation{ 49 | Position: function.Position, 50 | Operator: nfunc, 51 | Exp: function.Arguments[0], 52 | }) 53 | } 54 | } 55 | return &parser.Error{ 56 | Message: fmt.Sprintf("Unknown function or macro: %s(%d arguments)", function.Function, len(function.Arguments)), 57 | StartPosition: function.Start(), 58 | EndPosition: function.End(), 59 | } 60 | } 61 | 62 | // checkes, if the program uses nolols time-tracking feature 63 | func usesTimeTracking(n ast.Node) bool { 64 | uses := false 65 | f := func(node ast.Node, visitType int) error { 66 | if function, is := node.(*nast.FuncCall); is { 67 | if function.Function == "time" { 68 | uses = true 69 | } 70 | } 71 | return nil 72 | } 73 | n.Accept(ast.VisitorFunc(f)) 74 | return uses 75 | } 76 | 77 | // inserts the line-counting statement into the beginning of each line 78 | func (c *Converter) insertLineCounter(p *nast.Program) { 79 | for _, line := range p.Elements { 80 | if stmtline, is := line.(*nast.StatementLine); is { 81 | stmts := make([]ast.Statement, 1, len(stmtline.Statements)+1) 82 | stmts[0] = &ast.Dereference{ 83 | Variable: c.varnameOptimizer.OptimizeVarName(reservedTimeVariable), 84 | Operator: "++", 85 | PrePost: "Post", 86 | IsStatement: true, 87 | } 88 | stmts = append(stmts, stmtline.Statements...) 89 | stmtline.Statements = stmts 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /vscode-yolol/src/test/debugadapter.test.ts: -------------------------------------------------------------------------------- 1 | import assert = require('assert'); 2 | import * as Path from 'path'; 3 | import { DebugClient } from 'vscode-debugadapter-testsupport'; 4 | import { DebugAdapterExecutableFactory, getExePath } from '../extension' 5 | import { Uri, DebugAdapterExecutable, Breakpoint, DebugProtocolBreakpoint } from 'vscode' 6 | import { activate, getDocUri } from './helper' 7 | 8 | // these test should verify that the interaction with the debugadapter is at least not completely broken 9 | // more advanced testing of the debugadapters functionality is done in go 10 | describe('Debug Adapter', function () { 11 | 12 | let dc: DebugClient; 13 | let executable: DebugAdapterExecutable; 14 | const DATA_ROOT = Path.join(__dirname, "..","..","testFixture") 15 | 16 | 17 | before(async () => { 18 | 19 | await activate(getDocUri("correct.yolol")) 20 | 21 | var debugSession = new class { 22 | id: ""; 23 | type: ""; 24 | name: ""; 25 | workspaceFolder = new class { 26 | readonly uri = Uri.parse(Path.join(__dirname, "..","..")); 27 | readonly name = "" 28 | readonly index = 0 29 | } 30 | configuration = new class { 31 | type = "" 32 | name = "" 33 | request = "" 34 | } 35 | getDebugProtocolBreakpoint(breakpoint: Breakpoint): Thenable{ 36 | return null 37 | } 38 | 39 | customRequest(command: string, args?: any): Thenable { 40 | return null 41 | } 42 | } 43 | 44 | var fact = new DebugAdapterExecutableFactory() 45 | var descriptor = fact.createDebugAdapterDescriptor(debugSession, null) 46 | executable = descriptor as DebugAdapterExecutable 47 | }) 48 | 49 | beforeEach(async () => { 50 | var cmd = executable.command + " " + executable.args[0] //+ " --debug" 51 | dc = new DebugClient(cmd, "", 'mock', { shell: true }, false); 52 | await dc.start(); 53 | }); 54 | 55 | afterEach(() => { 56 | dc.stop() 57 | } 58 | ); 59 | 60 | it('should return supported features', () => { 61 | return dc.initializeRequest().then(response => { 62 | response.body = response.body || {}; 63 | assert.equal(response.body.supportsConfigurationDoneRequest, true); 64 | }); 65 | }); 66 | 67 | it('should launch pause and resume', async () => { 68 | const PROGRAM = Path.join(DATA_ROOT, 'correct.yolol'); 69 | var configured = dc.configurationSequence() 70 | await dc.launch({ scripts: [PROGRAM] }) 71 | await configured 72 | dc.pauseRequest({ 73 | threadId: 1 74 | }) 75 | await dc.assertStoppedLocation('pause', {}) 76 | await dc.continueRequest({ 77 | threadId: 1, 78 | }) 79 | dc.pauseRequest({ 80 | threadId: 1 81 | }) 82 | await dc.assertStoppedLocation('pause', {}) 83 | }); 84 | 85 | it('should stop on a breakpoint', () => { 86 | const PROGRAM = Path.join(DATA_ROOT, 'correct.yolol'); 87 | const BREAKPOINT_LINE = 2; 88 | 89 | return dc.hitBreakpoint({ scripts: [PROGRAM] }, { path: PROGRAM, line: BREAKPOINT_LINE }); 90 | }); 91 | 92 | }); -------------------------------------------------------------------------------- /pkg/nolol/converter_test.go: -------------------------------------------------------------------------------- 1 | package nolol_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/dbaumgarten/yodk/pkg/nolol" 8 | "github.com/dbaumgarten/yodk/pkg/parser" 9 | "github.com/dbaumgarten/yodk/pkg/vm" 10 | ) 11 | 12 | var testProg = ` 13 | define fizz = "fizz" 14 | define buzz = "buzz" 15 | define sep = " " 16 | define upto = 100 17 | :out = "" 18 | number = 0 19 | while number<=upto do 20 | if number%3==0 and number%5==0 then 21 | :out+=fizz+buzz+sep 22 | goto next 23 | end 24 | if number%3==0 then 25 | :out+=fizz+sep 26 | goto next 27 | end 28 | if number%5==0 then 29 | :out+=buzz+sep 30 | goto next 31 | end 32 | :out += number + sep 33 | next> 34 | number++ 35 | end 36 | :done = 1 37 | ` 38 | 39 | var testProg2 = ` 40 | zzz=7 41 | a=1;b=2;c=3 $ 42 | x="blabla" 43 | y="hey" 44 | $ 45 | $ 46 | foo="bar" 47 | $ what="ever" 48 | $ z = 0 $ 49 | x = 99 50 | ` 51 | 52 | var testProg3 = ` 53 | include "testProg" 54 | ` 55 | 56 | var testfs = nolol.MemoryFileSystem{ 57 | "testProg.nolol": testProg, 58 | "testProg2.nolol": testProg2, 59 | "testProg3.nolol": testProg3, 60 | } 61 | 62 | func TestNolol(t *testing.T) { 63 | conv := nolol.NewConverter() 64 | prog, err := conv.LoadFileEx("testProg.nolol", testfs).Convert() 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | 69 | gen := parser.Printer{} 70 | code, err := gen.Print(prog) 71 | if err != nil { 72 | t.Error(err) 73 | } 74 | 75 | v, err := vm.CreateFromSource(code) 76 | if err != nil { 77 | t.Error(err) 78 | } 79 | v.SetLineExecutedHandler(vm.TerminateOnDoneVar) 80 | v.Resume() 81 | v.WaitForTermination() 82 | 83 | expected := "fizzbuzz 1 2 fizz 4 buzz fizz 7 8 fizz buzz 11 fizz 13 14 fizzbuzz 16 17 fizz 19 buzz fizz 22 23 fizz buzz 26 fizz 28 29 fizzbuzz 31 32 fizz 34 buzz fizz 37 38 fizz buzz 41 fizz 43 44 fizzbuzz 46 47 fizz 49 buzz fizz 52 53 fizz buzz 56 fizz 58 59 fizzbuzz 61 62 fizz 64 buzz fizz 67 68 fizz buzz 71 fizz 73 74 fizzbuzz 76 77 fizz 79 buzz fizz 82 83 fizz buzz 86 fizz 88 89 fizzbuzz 91 92 fizz 94 buzz fizz 97 98 fizz buzz " 84 | 85 | result, exists := v.GetVariable(":out") 86 | if !exists { 87 | t.Fatal("Output variable does not exist") 88 | } 89 | 90 | if result.String() != expected { 91 | t.Fatal("Output is wrong:", result.String()) 92 | } 93 | } 94 | 95 | func TestInclude(t *testing.T) { 96 | conv := nolol.NewConverter() 97 | prog, _ := conv.LoadFileEx("testProg.nolol", testfs).Convert() 98 | prog2, _ := conv.LoadFileEx("testProg3.nolol", testfs).Convert() 99 | printer := &parser.Printer{} 100 | printed, _ := printer.Print(prog) 101 | printed2, _ := printer.Print(prog2) 102 | if printed != printed2 { 103 | fmt.Println(printed) 104 | fmt.Println("------------") 105 | fmt.Println(printed2) 106 | t.Fatal("Include does not match original") 107 | } 108 | } 109 | 110 | func TestLineHandling(t *testing.T) { 111 | conv := nolol.NewConverter() 112 | prog, err := conv.LoadFileEx("testProg2.nolol", testfs).Convert() 113 | if err != nil { 114 | t.Error(err) 115 | } 116 | 117 | lines := len(prog.Lines) 118 | 119 | if lines != 8 { 120 | t.Fatal("Wrong amount of lines after merging. Expected 8, but got: ", lines) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /pkg/vm/variable_test.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | ) 7 | 8 | func TestVariableFromString(t *testing.T) { 9 | var1 := VariableFromString("abc") 10 | if !var1.IsString() { 11 | t.Fatal("var1 should be a string") 12 | } 13 | if var1.String() != "abc" { 14 | t.Fatal("var1 has wrong value") 15 | } 16 | 17 | var2 := VariableFromString("123") 18 | if !var2.IsNumber() { 19 | t.Fatal("var2 should be a number") 20 | } 21 | if var2.Number().Int() != 123 { 22 | t.Fatal("var2 has wrong value") 23 | } 24 | 25 | var3 := VariableFromString("123.5") 26 | if !var3.IsNumber() { 27 | t.Fatal("var2 should be a number") 28 | } 29 | floatval := var3.Number().Float64() 30 | if math.Abs(floatval-123.5) > 0.000001 { 31 | t.Fatal("var3 has wrong value") 32 | } 33 | 34 | var4 := VariableFromString("\"123\"") 35 | if !var4.IsString() { 36 | t.Fatal("var4 should be a string") 37 | } 38 | if var4.String() != "123" { 39 | t.Fatal("var4 has wrong value") 40 | } 41 | } 42 | 43 | func TestVariableFromType(t *testing.T) { 44 | var1, err := VariableFromType("abc") 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | if !var1.IsString() { 49 | t.Fatal("var1 should be a string") 50 | } 51 | if var1.String() != "abc" { 52 | t.Fatal("var1 has wrong value") 53 | } 54 | 55 | var2, err := VariableFromType(123) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | if !var2.IsNumber() { 60 | t.Fatal("var2 should be a number") 61 | } 62 | if var2.Number().Int() != 123 { 63 | t.Fatal("var2 has wrong value") 64 | } 65 | 66 | var3, err := VariableFromType(123.5) 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | if !var3.IsNumber() { 71 | t.Fatal("var2 should be a number") 72 | } 73 | floatval := var3.Number().Float64() 74 | if math.Abs(floatval-123.5) > 0.000001 { 75 | t.Fatal("var3 has wrong value") 76 | } 77 | 78 | _, err = VariableFromType([]string{}) 79 | if err == nil { 80 | t.Fatal("No error for invalid type") 81 | } 82 | } 83 | 84 | func TestVariableTypeHandling(t *testing.T) { 85 | var1 := VariableFromString("abc") 86 | var2 := VariableFromString("123") 87 | var3 := VariableFromString("123.5") 88 | var4 := VariableFromString("\"123\"") 89 | 90 | if var2.Itoa() != "123" { 91 | t.Fatal("Itoa not working") 92 | } 93 | 94 | if !var1.SameType(var4) { 95 | t.Fatal("SameType not working") 96 | } 97 | 98 | if !var2.SameType(var3) { 99 | t.Fatal("SameType not working") 100 | } 101 | } 102 | 103 | func TestEquals(t *testing.T) { 104 | if !VariableFromString("abc").Equals(VariableFromString("abc")) { 105 | t.Fatal("Equals failed") 106 | } 107 | if !VariableFromString("123.5").Equals(VariableFromString("123.5")) { 108 | t.Fatal("Equals failed") 109 | } 110 | if VariableFromString("abc").Equals(VariableFromString("abcd")) { 111 | t.Fatal("Equals failed") 112 | } 113 | if VariableFromString("123").Equals(VariableFromString("\"123\"")) { 114 | t.Fatal("Equals failed") 115 | } 116 | } 117 | 118 | func TestOutputs(t *testing.T) { 119 | var1 := VariableFromString("abc") 120 | if var1.String() != "abc" || var1.Repr() != "\"abc\"" || var1.Itoa() != "" { 121 | t.Fatal("String output not working") 122 | } 123 | var2 := VariableFromString("123") 124 | if var2.String() != "" || var2.Repr() != "123" || var2.Itoa() != "123" { 125 | t.Fatal("Number output not working") 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /pkg/langserver/win32/hotkey.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package win32 4 | 5 | import ( 6 | "bytes" 7 | "context" 8 | "fmt" 9 | "runtime" 10 | "time" 11 | "unsafe" 12 | ) 13 | 14 | // most of the code is taken from: https://stackoverflow.com/questions/38646794/implement-a-global-hotkey-in-golang#38954281 15 | 16 | // Modifiers for Hotkeys 17 | const ( 18 | ModAlt = 1 << iota 19 | ModCtrl 20 | ModShift 21 | ModWin 22 | ) 23 | 24 | var ( 25 | reghotkey = user32.MustFindProc("RegisterHotKey") 26 | unreghotkey = user32.MustFindProc("UnregisterHotKey") 27 | getmsg = user32.MustFindProc("PeekMessageW") 28 | ) 29 | 30 | // Hotkey represents a key-combination pressed by a user 31 | type Hotkey struct { 32 | // Id, must be unique for each registered hotkey 33 | ID int // Unique id 34 | // Modifiers is a bitmask containing modifiers for the hotkey 35 | Modifiers int // Mask of modifiers 36 | // KeyCode is the keycode for the hotkey 37 | KeyCode int // Key code, e.g. 'A' 38 | } 39 | 40 | // String returns a human-friendly display name of the hotkey 41 | // such as "Hotkey[Id: 1, Alt+Ctrl+O]" 42 | func (h Hotkey) String() string { 43 | mod := &bytes.Buffer{} 44 | if h.Modifiers&ModAlt != 0 { 45 | mod.WriteString("Alt+") 46 | } 47 | if h.Modifiers&ModCtrl != 0 { 48 | mod.WriteString("Ctrl+") 49 | } 50 | if h.Modifiers&ModShift != 0 { 51 | mod.WriteString("Shift+") 52 | } 53 | if h.Modifiers&ModWin != 0 { 54 | mod.WriteString("Win+") 55 | } 56 | return fmt.Sprintf("Hotkey[Id: %d, %s%c]", h.ID, mod, h.KeyCode) 57 | } 58 | 59 | // HotkeyHandler is the callback for registered hotkeys 60 | type HotkeyHandler func(Hotkey) 61 | 62 | type msg struct { 63 | HWND uintptr 64 | UINT uintptr 65 | WPARAM int16 66 | LPARAM int64 67 | DWORD int32 68 | POINT struct{ X, Y int64 } 69 | } 70 | 71 | func registerHotkey(hk *Hotkey) error { 72 | r1, _, err := reghotkey.Call(0, uintptr(hk.ID), uintptr(hk.Modifiers), uintptr(hk.KeyCode)) 73 | if r1 == 0 { 74 | return err 75 | } 76 | return nil 77 | } 78 | 79 | func unregisterHotkey(hk *Hotkey) error { 80 | r1, _, err := unreghotkey.Call(0, uintptr(hk.ID)) 81 | if r1 == 0 { 82 | return err 83 | } 84 | return nil 85 | } 86 | 87 | // ListenForHotkeys registers an listens for the given global Hotkeys. If a hotkey is pressed, the hendler function is executed 88 | // This function blocks, so it shoue have it's own goroutine 89 | func ListenForHotkeys(ctx context.Context, handler HotkeyHandler, hotkeys ...*Hotkey) error { 90 | 91 | runtime.LockOSThread() 92 | defer runtime.UnlockOSThread() 93 | 94 | hotkeymap := make(map[int16]*Hotkey) 95 | 96 | // unregister all hotkeys when exiting 97 | defer func() { 98 | for _, v := range hotkeymap { 99 | unregisterHotkey(v) 100 | } 101 | }() 102 | 103 | // register all requested hotkeys 104 | for _, v := range hotkeys { 105 | err := registerHotkey(v) 106 | if err != nil { 107 | return err 108 | } 109 | hotkeymap[int16(v.ID)] = v 110 | } 111 | 112 | for { 113 | if ctx != nil && ctx.Err() != nil { 114 | return nil 115 | } 116 | var msg = &msg{} 117 | getmsg.Call(uintptr(unsafe.Pointer(msg)), 0, 0, 0, 1) 118 | 119 | // Registered id is in the WPARAM field: 120 | if id := msg.WPARAM; id != 0 { 121 | hk, exists := hotkeymap[id] 122 | if exists { 123 | handler(*hk) 124 | } 125 | } 126 | time.Sleep(50 * time.Millisecond) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /pkg/optimizers/variable_name_test.go: -------------------------------------------------------------------------------- 1 | package optimizers 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/dbaumgarten/yodk/pkg/parser" 9 | "github.com/dbaumgarten/yodk/pkg/testdata" 10 | ) 11 | 12 | func TestGetNextVarName(t *testing.T) { 13 | 14 | vno := NewVariableNameOptimizer() 15 | 16 | for i := 0; i < 100; i++ { 17 | 18 | original := fmt.Sprintf("varn%d", i) 19 | 20 | actual := vno.getNextVarName() 21 | var expected string 22 | switch i { 23 | case 0: 24 | expected = "a" 25 | case 1: 26 | expected = "b" 27 | case 25: 28 | expected = "z" 29 | case 26: 30 | expected = "aa" 31 | case 27: 32 | expected = "ab" 33 | case 28: 34 | expected = "ac" 35 | } 36 | 37 | if expected != "" && expected != actual { 38 | t.Fatalf("Wrong var name for variable number %d. Expected '%s' but found '%s'.", i, expected, actual) 39 | } 40 | 41 | vno.variableMappings[original] = actual 42 | } 43 | } 44 | 45 | func TestOptName(t *testing.T) { 46 | vno := NewVariableNameOptimizer() 47 | 48 | vno.SetBlacklist([]string{"c"}) 49 | 50 | if vno.OptimizeVarName(":extvar") != ":extvar" { 51 | t.Fatal("Replaced external var") 52 | } 53 | 54 | if vno.OptimizeVarName("abc") != "a" { 55 | t.Fatal("Wrong replacement for first variable") 56 | } 57 | 58 | if vno.OptimizeVarName("aBc") != "a" { 59 | t.Fatal("Wrong replacement for other cased variable") 60 | } 61 | 62 | if vno.OptimizeVarName("abcd") != "b" { 63 | t.Fatal("Wrong replacement for second variable") 64 | } 65 | 66 | if vno.OptimizeVarName("abc") != "a" { 67 | t.Fatal("Did not remember first variable") 68 | } 69 | 70 | if vno.OptimizeVarName("abcde") != "d" { 71 | t.Fatal("Did not honor blacklisting") 72 | } 73 | } 74 | 75 | func TestVarOpt(t *testing.T) { 76 | p := parser.NewParser() 77 | parsed, err := p.Parse(testdata.TestProgram) 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | opt := NewVariableNameOptimizer() 82 | err = opt.Optimize(parsed) 83 | if err != nil { 84 | t.Fatal(err) 85 | } 86 | 87 | gen := parser.Printer{} 88 | generated, err := gen.Print(parsed) 89 | if err != nil { 90 | t.Fatal(err) 91 | } 92 | 93 | if strings.Contains(generated, " pi") || strings.Contains(generated, " hw") { 94 | t.Fatal("Variables have not been replaced", generated) 95 | } 96 | 97 | err = testdata.ExecuteTestProgram(generated) 98 | if err != nil { 99 | t.Fatal(err) 100 | } 101 | } 102 | 103 | func TestInitializeByFrequency(t *testing.T) { 104 | testprog := ` 105 | foo=123 106 | bar=456 107 | bar=555 108 | baz=aaa+aaa+aaa 109 | ignoreme=1 110 | other=2 111 | ` 112 | p := parser.NewParser() 113 | parsed, err := p.Parse(testprog) 114 | if err != nil { 115 | t.Fatal(err) 116 | } 117 | opt := NewVariableNameOptimizer() 118 | 119 | opt.InitializeByFrequency(parsed, []string{"ignoreme"}) 120 | 121 | if opt.OptimizeVarName("aaa") != "a" { 122 | t.Fatal("Wrong replacement for aaa variable") 123 | } 124 | 125 | if opt.OptimizeVarName("bar") != "b" { 126 | t.Fatal("Wrong replacement for bar variable") 127 | } 128 | 129 | if opt.OptimizeVarName("foo") != "c" { 130 | t.Fatal("Wrong replacement for foo variable") 131 | } 132 | 133 | if opt.OptimizeVarName("baz") != "d" { 134 | t.Fatal("Wrong replacement for baz variable") 135 | } 136 | 137 | if opt.OptimizeVarName("other") != "e" { 138 | t.Fatal("Wrong replacement for baz variable") 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /examples/nolol/macros.nolol: -------------------------------------------------------------------------------- 1 | define separator="." 2 | 3 | //macros are defined using the macro-keyword, followed by a list of arguments, a optional list of external variables and a macro-type 4 | //macros must be defined at the top-level of the program (can not be nested) 5 | 6 | // There are three types of macros 7 | 8 | // Macros of type expr define a SINGLE expression 9 | // These macros can be used as expressions like regular functions 10 | macro addByte(a, b) expr 11 | // comments are allowed here 12 | (a+b)%2^8 13 | // but the only non-comment thing in a macro of type expr, is a single expression 14 | end 15 | 16 | a=123 17 | b=55 18 | :sum=addByte(a,100)+addByte(b,100) 19 | 20 | 21 | // Macros of type line define a SINGLE line of statements (and optional label and BOL/EOL marker) 22 | // the line inside the macro will be merged with the line it is inserted into 23 | // if the line of the macro and the line where it is inserted are incompatible, the compiler will complain 24 | macro block_while(condition) line 25 | //comments are also allowed here 26 | here> if condition then goto here end; :foo=1 27 | end 28 | 29 | block_while(:timer++<=10); :go=1 30 | 31 | 32 | // macros of type block can contain (almost) anything, like for example multi-line constructs 33 | // the can only be used, if they are the only thing on that line (and can not be used as expressions) 34 | macro greet(output, name) block 35 | output="Hello" 36 | while i++<=5 do 37 | output+=separator 38 | end 39 | output+=name 40 | end 41 | 42 | greet(:out1,"world") 43 | 44 | 45 | // arguments work, by replacing every instance of the name, by the value that is provided when using the macro 46 | // this feels a lot like if the arguments were passed by reference. This is way arguments can also be used to transport outputs of the function 47 | macro goodbye(output, name) block 48 | // "output" is replaced by the first argument provided when using the macro 49 | output="Goodbye" 50 | // i is a macro-local variable. Changes to i are local to macro-insertions 51 | // if you insert greet() multipe time, all insertions will have their own version of i 52 | while i++<=5 do 53 | output+=separator 54 | end 55 | // "name" is replaced by the second argument provided when using the macro 56 | output+=name 57 | end 58 | 59 | // definitions are resolved when using a macro (and not when defining it) 60 | // Also, new definitions override old ones 61 | // Therefore definitions can be used to configure the behavior of macros 62 | define separator="_" 63 | 64 | greet(:out2,"you") 65 | 66 | 67 | // all non-global vars (=vars that start not with ':') are (by default) private to a usage of a macro 68 | // for example, the following DOES NOT WORK: the code inside the macro does not have access to the external variable avar 69 | avar="foo" 70 | macro doesntwork() line 71 | :out3=avar 72 | end 73 | doesntwork() 74 | 75 | // to give the code inside the macro access to an outside var, you either need to pass the var as an argument, 76 | // or mark the variable as an external variable. You can do this as shown below.# 77 | // This is done, so that different uses of a macro do not interfere with each other 78 | macro works() line 79 | :out4=avar 80 | end 81 | works() 82 | // you can use more than one external variable by using 83 | 84 | 85 | // macros can contain other macros. BUT macros can not contain themselves (=recursion DOES NOT WORK) 86 | macro addByteOffset(a, b, offset) expr 87 | addByte(a,b)+offset 88 | end 89 | 90 | :sum2=addByteOffset(a,b,100) 91 | 92 | :done=1 93 | -------------------------------------------------------------------------------- /pkg/validators/available_ops.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | 7 | "github.com/dbaumgarten/yodk/pkg/nolol/nast" 8 | "github.com/dbaumgarten/yodk/pkg/parser" 9 | "github.com/dbaumgarten/yodk/pkg/parser/ast" 10 | ) 11 | 12 | const ( 13 | ChipTypeAuto = "auto" 14 | ChipTypeBasic = "basic" 15 | ChipTypeAdvanced = "advanced" 16 | ChipTypeProfessional = "professional" 17 | ) 18 | 19 | var unavailableBinaryOps = map[string][]string{ 20 | ChipTypeBasic: {"^", "%"}, 21 | ChipTypeAdvanced: {}, 22 | ChipTypeProfessional: {}, 23 | } 24 | 25 | var unavailableUnaryOps = map[string][]string{ 26 | ChipTypeBasic: {"!", "sqrt", "sin", "cos", "tan", "asin", "acos", "atan", "abs"}, 27 | ChipTypeAdvanced: {"sin", "cos", "tan", "asin", "acos", "atan"}, 28 | ChipTypeProfessional: {}, 29 | } 30 | 31 | var unavailableAssignments = map[string][]string{ 32 | ChipTypeBasic: {"^=", "%="}, 33 | ChipTypeAdvanced: {}, 34 | ChipTypeProfessional: {}, 35 | } 36 | 37 | func contains(list []string, element string) bool { 38 | for _, el := range list { 39 | if el == element { 40 | return true 41 | } 42 | } 43 | return false 44 | } 45 | 46 | var filenameChiptypeRegex = regexp.MustCompile(".*_(basic|advanced|professional).(?:n|y)olol") 47 | 48 | // AutoChooseChipType chooses a chip-type based on the provided type and the filename of the source-file 49 | func AutoChooseChipType(choosen string, filename string) (string, error) { 50 | 51 | if choosen != ChipTypeBasic && choosen != ChipTypeAdvanced && choosen != ChipTypeProfessional && choosen != ChipTypeAuto { 52 | return "", fmt.Errorf("Unknown chip-type. Possible options are professional, advanced, basic or auto") 53 | } 54 | 55 | if choosen != ChipTypeAuto { 56 | return choosen, nil 57 | } 58 | 59 | match := filenameChiptypeRegex.FindStringSubmatch(filename) 60 | if match != nil { 61 | return match[1], nil 62 | } 63 | 64 | return ChipTypeProfessional, nil 65 | } 66 | 67 | // ValidateAvailableOperations checks if all used operations are available on the given chip-type 68 | func ValidateAvailableOperations(program ast.Node, chiptype string) error { 69 | 70 | if chiptype == ChipTypeProfessional { 71 | return nil 72 | } 73 | 74 | errors := make(parser.Errors, 0) 75 | 76 | logError := func(op string, node ast.Node) { 77 | errors = append(errors, &parser.Error{ 78 | Message: fmt.Sprintf("Operator '%s' is not available on %s-chips", op, chiptype), 79 | StartPosition: node.Start(), 80 | EndPosition: node.End(), 81 | }) 82 | } 83 | 84 | f := func(node ast.Node, visitType int) error { 85 | if visitType == ast.SingleVisit || visitType == ast.PreVisit { 86 | switch n := node.(type) { 87 | case *ast.UnaryOperation: 88 | if contains(unavailableUnaryOps[chiptype], n.Operator) { 89 | logError(n.Operator, n) 90 | } 91 | case *ast.BinaryOperation: 92 | if contains(unavailableBinaryOps[chiptype], n.Operator) { 93 | logError(n.Operator, n) 94 | } 95 | case *ast.Assignment: 96 | if contains(unavailableAssignments[chiptype], n.Operator) { 97 | logError(n.Operator, n) 98 | } 99 | case *nast.FuncCall: 100 | if contains(unavailableUnaryOps[chiptype], n.Function) { 101 | logError(n.Function, n) 102 | } 103 | } 104 | } 105 | return nil 106 | } 107 | err := program.Accept(ast.VisitorFunc(f)) 108 | if err != nil { 109 | return err 110 | } 111 | 112 | if len(errors) > 0 { 113 | return errors 114 | } 115 | 116 | return nil 117 | } 118 | -------------------------------------------------------------------------------- /pkg/nolol/converter_definitions.go: -------------------------------------------------------------------------------- 1 | package nolol 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/dbaumgarten/yodk/pkg/nolol/nast" 7 | "github.com/dbaumgarten/yodk/pkg/parser" 8 | "github.com/dbaumgarten/yodk/pkg/parser/ast" 9 | ) 10 | 11 | const maxDefinitionInsertions = 1000 12 | 13 | // getDefinition is a case-insensitive getter for c.definitions 14 | func (c *Converter) getDefinition(name string) (*nast.Definition, bool) { 15 | name = strings.ToLower(name) 16 | val, exists := c.definitions[name] 17 | return val, exists 18 | } 19 | 20 | // setDefinition is a case-insensitive setter for c.definitions 21 | func (c *Converter) setDefinition(name string, val *nast.Definition) { 22 | name = strings.ToLower(name) 23 | c.definitions[name] = val 24 | } 25 | 26 | // convertDefinitions converts a definition to yolol by discarding it, but saving the defined value 27 | func (c *Converter) convertDefinition(decl *nast.Definition, visitType int) error { 28 | if visitType == ast.PreVisit { 29 | c.setDefinition(decl.Name, decl) 30 | return ast.NewNodeReplacement() 31 | } 32 | return nil 33 | } 34 | 35 | // convertDefinitionAssignment replaces assignments with definitions 36 | func (c *Converter) convertDefinitionAssignment(ass *ast.Assignment, visitType int) error { 37 | if visitType == ast.PostVisit { 38 | if replacement, exists := c.getDefinition(ass.Variable); exists { 39 | if replacementVariable, isvar := replacement.Value.(*ast.Dereference); isvar && replacementVariable.Operator == "" { 40 | ass.Variable = replacementVariable.Variable 41 | } else { 42 | return &parser.Error{ 43 | Message: "Can not assign to a definition that is an expression (need a single variable name)", 44 | StartPosition: ass.Start(), 45 | EndPosition: ass.End(), 46 | } 47 | } 48 | c.definitionInsertionCount++ 49 | if c.definitionInsertionCount > maxDefinitionInsertions { 50 | return &parser.Error{ 51 | Message: "Detected a definition-loop. Definitions can not be recursive!", 52 | StartPosition: replacement.Start(), 53 | EndPosition: replacement.End(), 54 | } 55 | } 56 | } 57 | } 58 | return nil 59 | } 60 | 61 | // convertDereference replaces mentionings of definitions with the value of the definition 62 | func (c *Converter) convertDefinitionDereference(deref *ast.Dereference) error { 63 | if definition, exists := c.getDefinition(deref.Variable); exists { 64 | // dereference of definition 65 | replacement := nast.CopyAst(definition.Value) 66 | if replacementVariable, isvar := replacement.(*ast.Dereference); isvar { 67 | if deref.Operator != "" && replacementVariable.Operator != "" { 68 | return &parser.Error{ 69 | Message: "You can not use pre/post-operators on defitions that use the operator themselves", 70 | StartPosition: deref.Start(), 71 | EndPosition: deref.End(), 72 | } 73 | } 74 | if deref.Operator != "" { 75 | replacementVariable.Operator = deref.Operator 76 | replacementVariable.PrePost = deref.PrePost 77 | } 78 | } else if deref.Operator != "" { 79 | return &parser.Error{ 80 | Message: "Can not use pre/port-operators on expressions", 81 | StartPosition: deref.Start(), 82 | EndPosition: deref.End(), 83 | } 84 | } 85 | c.definitionInsertionCount++ 86 | if c.definitionInsertionCount > maxDefinitionInsertions { 87 | return &parser.Error{ 88 | Message: "Detected a definition-loop. Definitions can not be recursive!", 89 | StartPosition: replacement.Start(), 90 | EndPosition: replacement.End(), 91 | } 92 | } 93 | return ast.NewNodeReplacement(replacement) 94 | } 95 | return nil 96 | } 97 | -------------------------------------------------------------------------------- /pkg/nolol/converter_includes.go: -------------------------------------------------------------------------------- 1 | package nolol 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | "path/filepath" 7 | "strings" 8 | 9 | "github.com/dbaumgarten/yodk/pkg/nolol/nast" 10 | "github.com/dbaumgarten/yodk/pkg/parser" 11 | "github.com/dbaumgarten/yodk/pkg/parser/ast" 12 | "github.com/dbaumgarten/yodk/pkg/validators" 13 | "github.com/dbaumgarten/yodk/stdlib" 14 | ) 15 | 16 | // resolveIncludes searches for include-directives and inserts the lines of the included files 17 | func (c *Converter) convertInclude(include *nast.IncludeDirective) error { 18 | 19 | c.includecount++ 20 | if c.includecount > 20 { 21 | return &parser.Error{ 22 | Message: "Error when processing includes: Include-loop detected", 23 | StartPosition: ast.NewPosition("", 1, 1), 24 | EndPosition: ast.NewPosition("", 20, 70), 25 | } 26 | } 27 | 28 | filesnames := make([]string, 1) 29 | filesnames[0] = include.File 30 | 31 | file, err := c.getIncludedFile(include) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | p := NewParser().(*Parser) 37 | p.SetFilename(include.File) 38 | parsed, err := p.Parse(file) 39 | if err != nil { 40 | // override the position of the error with the position of the include 41 | // this way the error gets displayed at the correct location 42 | // the message does contain the original location 43 | return &parser.Error{ 44 | Message: err.Error(), 45 | StartPosition: include.Start(), 46 | EndPosition: include.End(), 47 | } 48 | } 49 | 50 | if usesTimeTracking(parsed) { 51 | c.usesTimeTracking = true 52 | } 53 | 54 | replacements := make([]ast.Node, len(parsed.Elements)) 55 | for i := range parsed.Elements { 56 | replacements[i] = parsed.Elements[i] 57 | } 58 | return ast.NewNodeReplacement(replacements...) 59 | } 60 | 61 | func (c *Converter) getIncludedFile(include *nast.IncludeDirective) (string, error) { 62 | 63 | importname := include.File 64 | getfunc := c.files.Get 65 | 66 | if stdlib.Is(importname) { 67 | getfunc = stdlib.Get 68 | } else { 69 | // this include is inside an included file 70 | if include.Position.File != "" { 71 | // the included file is inside another directory 72 | dir := path.Dir(include.Position.File) 73 | if dir != "." { 74 | dir = filepath.ToSlash(dir) 75 | // fix the import-path 76 | importname = path.Join(dir, importname) 77 | } 78 | } 79 | } 80 | 81 | filename := importname 82 | 83 | if !strings.HasSuffix(importname, ".nolol") { 84 | filename = importname + ".nolol" 85 | } 86 | 87 | // first try to import exact file 88 | file, origerr := getfunc(filename) 89 | if origerr == nil { 90 | return file, nil 91 | } 92 | 93 | // next try all available chip-specific imports 94 | switch c.targetChipType { 95 | case validators.ChipTypeProfessional: 96 | file, err := getfunc(importname + "_" + validators.ChipTypeProfessional + ".nolol") 97 | if err == nil { 98 | return file, nil 99 | } 100 | fallthrough 101 | case validators.ChipTypeAdvanced: 102 | file, err := getfunc(importname + "_" + validators.ChipTypeAdvanced + ".nolol") 103 | if err == nil { 104 | return file, nil 105 | } 106 | fallthrough 107 | case validators.ChipTypeBasic: 108 | file, err := getfunc(importname + "_" + validators.ChipTypeBasic + ".nolol") 109 | if err == nil { 110 | return file, nil 111 | } 112 | } 113 | 114 | return "", &parser.Error{ 115 | Message: fmt.Sprintf("Error when opening included file '%s': %s", importname, origerr.Error()), 116 | StartPosition: include.Start(), 117 | EndPosition: include.End(), 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /pkg/optimizers/expression_inversion.go: -------------------------------------------------------------------------------- 1 | package optimizers 2 | 3 | import ( 4 | "github.com/dbaumgarten/yodk/pkg/parser/ast" 5 | ) 6 | 7 | // ExpressionInversionOptimizer inverts negated expressions to shorten them 8 | type ExpressionInversionOptimizer struct { 9 | } 10 | 11 | var inversions = map[string]string{ 12 | ">=": "<", 13 | "<=": ">", 14 | "<": ">=", 15 | ">": "<=", 16 | "==": "!=", 17 | "!=": "==", 18 | } 19 | 20 | var andor = map[string]string{ 21 | "and": "or", 22 | "or": "and", 23 | } 24 | 25 | // Optimize is needed to implement Optimizer 26 | func (o ExpressionInversionOptimizer) Optimize(prog ast.Node) error { 27 | return prog.Accept(o) 28 | } 29 | 30 | // OptimizeExpression optimizes a single expression 31 | // Optimize() in contrast can only optimize whole programms 32 | func (o ExpressionInversionOptimizer) OptimizeExpression(e ast.Expression) ast.Expression { 33 | e, _ = ast.MustExpression(ast.AcceptChild(o, e)) 34 | return e 35 | } 36 | 37 | func pushDownNots(node ast.Node) ast.Expression { 38 | if op, is := node.(*ast.UnaryOperation); is { 39 | if op.Operator == "not" { 40 | switch inner := op.Exp.(type) { 41 | case *ast.BinaryOperation: 42 | if opposite, invertable := inversions[inner.Operator]; invertable { 43 | inner.Operator = opposite 44 | return inner 45 | } 46 | if opposite, is := andor[inner.Operator]; is { 47 | inner.Operator = opposite 48 | inner.Exp1 = &ast.UnaryOperation{ 49 | Operator: "not", 50 | Exp: inner.Exp1, 51 | Position: inner.Exp1.Start(), 52 | } 53 | inner.Exp2 = &ast.UnaryOperation{ 54 | Operator: "not", 55 | Exp: inner.Exp2, 56 | Position: inner.Exp2.Start(), 57 | } 58 | return inner 59 | } 60 | case *ast.UnaryOperation: 61 | if inner.Operator == "not" { 62 | return inner.Exp 63 | } 64 | if inner.Operator == "()" { 65 | inner.Exp = &ast.UnaryOperation{ 66 | Operator: "not", 67 | Exp: inner.Exp, 68 | Position: inner.Exp.Start(), 69 | } 70 | return inner 71 | } 72 | } 73 | } 74 | } 75 | return nil 76 | } 77 | 78 | func bubbleUpNots(node ast.Node) ast.Expression { 79 | if bin, isbinary := node.(*ast.BinaryOperation); isbinary { 80 | l, lisunary := bin.Exp1.(*ast.UnaryOperation) 81 | r, risunary := bin.Exp2.(*ast.UnaryOperation) 82 | if lisunary && risunary && l.Operator == "not" && r.Operator == "not" { 83 | if opposite, invertable := andor[bin.Operator]; invertable { 84 | bin.Operator = opposite 85 | bin.Exp1 = l.Exp 86 | bin.Exp2 = r.Exp 87 | return &ast.UnaryOperation{ 88 | Operator: "not", 89 | Exp: bin, 90 | Position: bin.Start(), 91 | } 92 | } 93 | } 94 | } 95 | if un, isUnary := node.(*ast.UnaryOperation); isUnary { 96 | if un.Operator == "()" { 97 | if innerun, is := un.Exp.(*ast.UnaryOperation); is { 98 | if innerun.Operator == "not" { 99 | un.Exp = innerun.Exp 100 | return &ast.UnaryOperation{ 101 | Operator: "not", 102 | Exp: un, 103 | Position: un.Start(), 104 | } 105 | } 106 | } 107 | } 108 | } 109 | return nil 110 | } 111 | 112 | // Visit is needed to implement Visitor 113 | func (o ExpressionInversionOptimizer) Visit(node ast.Node, visitType int) error { 114 | if visitType == ast.PreVisit { 115 | replace := pushDownNots(node) 116 | if replace != nil { 117 | return ast.NewNodeReplacement(replace) 118 | } 119 | } 120 | if visitType == ast.PostVisit { 121 | replace := bubbleUpNots(node) 122 | if replace != nil { 123 | return ast.NewNodeReplacementSkip(replace) 124 | } 125 | } 126 | return nil 127 | } 128 | -------------------------------------------------------------------------------- /pkg/lsp/text.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This file contains the corresponding structures to the 6 | // "Text Synchronization" part of the LSP specification. 7 | 8 | package lsp 9 | 10 | type DidOpenTextDocumentParams struct { 11 | /** 12 | * The document that was opened. 13 | */ 14 | TextDocument TextDocumentItem `json:"textDocument"` 15 | } 16 | 17 | type DidChangeTextDocumentParams struct { 18 | /** 19 | * The document that did change. The version number points 20 | * to the version after all provided content changes have 21 | * been applied. 22 | */ 23 | TextDocument VersionedTextDocumentIdentifier `json:"textDocument"` 24 | 25 | /** 26 | * The actual content changes. The content changes describe single state changes 27 | * to the document. So if there are two content changes c1 and c2 for a document 28 | * in state S10 then c1 move the document to S11 and c2 to S12. 29 | */ 30 | ContentChanges []TextDocumentContentChangeEvent `json:"contentChanges"` 31 | } 32 | 33 | /** 34 | * An event describing a change to a text document. If range and rangeLength are omitted 35 | * the new text is considered to be the full content of the document. 36 | */ 37 | type TextDocumentContentChangeEvent struct { 38 | /** 39 | * The range of the document that changed. 40 | */ 41 | Range Range `json:"range,omitempty"` 42 | 43 | /** 44 | * The length of the range that got replaced. 45 | */ 46 | RangeLength float64 `json:"rangeLength,omitempty"` 47 | 48 | /** 49 | * The new text of the range/document. 50 | */ 51 | Text string `json:"text"` 52 | } 53 | 54 | /** 55 | * Describe options to be used when registering for text document change events. 56 | */ 57 | type TextDocumentChangeRegistrationOptions struct { 58 | TextDocumentRegistrationOptions 59 | /** 60 | * How documents are synced to the server. See TextDocumentSyncKind.Full 61 | * and TextDocumentSyncKind.Incremental. 62 | */ 63 | SyncKind float64 `json:"syncKind"` 64 | } 65 | 66 | /** 67 | * The parameters send in a will save text document notification. 68 | */ 69 | type WillSaveTextDocumentParams struct { 70 | /** 71 | * The document that will be saved. 72 | */ 73 | TextDocument TextDocumentIdentifier `json:"textDocument"` 74 | 75 | /** 76 | * The 'TextDocumentSaveReason'. 77 | */ 78 | Reason TextDocumentSaveReason `json:"reason"` 79 | } 80 | 81 | /** 82 | * Represents reasons why a text document is saved. 83 | */ 84 | type TextDocumentSaveReason float64 85 | 86 | const ( 87 | /** 88 | * Manually triggered, e.g. by the user pressing save, by starting debugging, 89 | * or by an API call. 90 | */ 91 | Manual TextDocumentSaveReason = 1 92 | 93 | /** 94 | * Automatic after a delay. 95 | */ 96 | AfterDelay TextDocumentSaveReason = 2 97 | 98 | /** 99 | * When the editor lost focus. 100 | */ 101 | FocusOut TextDocumentSaveReason = 3 102 | ) 103 | 104 | type DidSaveTextDocumentParams struct { 105 | /** 106 | * The document that was saved. 107 | */ 108 | TextDocument TextDocumentIdentifier `json:"textDocument"` 109 | 110 | /** 111 | * Optional the content when saved. Depends on the includeText value 112 | * when the save notification was requested. 113 | */ 114 | Text string `json:"text,omitempty"` 115 | } 116 | 117 | type TextDocumentSaveRegistrationOptions struct { 118 | TextDocumentRegistrationOptions 119 | /** 120 | * The client is supposed to include the content on save. 121 | */ 122 | IncludeText bool `json:"includeText,omitempty"` 123 | } 124 | 125 | type DidCloseTextDocumentParams struct { 126 | /** 127 | * The document that was closed. 128 | */ 129 | TextDocument TextDocumentIdentifier `json:"textDocument"` 130 | } 131 | -------------------------------------------------------------------------------- /pkg/langserver/win32/keyboard.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package win32 4 | 5 | import ( 6 | "reflect" 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | var ( 12 | user32 = syscall.MustLoadDLL("user32.dll") 13 | sendInputProc = user32.MustFindProc("SendInput") 14 | ) 15 | 16 | // KeyboardInput describes a keyboard-input event 17 | type KeyboardInput struct { 18 | VirtualKeyCode uint16 19 | ScanCode uint16 20 | Flags uint32 21 | Time uint32 22 | ExtraInfo uint64 23 | } 24 | 25 | // Input describes an input-event 26 | type Input struct { 27 | InputType uint32 28 | KeyboardInput KeyboardInput 29 | Padding uint64 30 | } 31 | 32 | // Keyboard-Input flags 33 | const ( 34 | KeyeventfExtendedkey = 0x0001 35 | KeyeventfKeyup = 0x0002 36 | KeyeventfUnicode = 0x0004 37 | KeyeventfScancode = 0x0008 38 | ) 39 | 40 | // Constants for special keycodes 41 | const ( 42 | KeycodeCtrl = 0x11 43 | KeycodeAlt = 0x12 44 | KeycodeReturn = 0x0D 45 | KeycodeShift = 0x10 46 | KeycodeBackspace = 0x08 47 | KeycodeDown = 0x28 48 | KeycodeUp = 0x26 49 | KeycodeRight = 0x27 50 | KeycodeLeft = 0x25 51 | KeycodeDelete = 0x2E 52 | KeycodeEnd = 0x23 53 | ) 54 | 55 | // SendInput is a go-wrapper for the win32 SendInput function. It sends input events to the currently active window 56 | func SendInput(inputEvents ...Input) error { 57 | hdr := (*reflect.SliceHeader)(unsafe.Pointer(&inputEvents)) 58 | data := unsafe.Pointer(hdr.Data) 59 | ret, _, err := sendInputProc.Call( 60 | uintptr(len(inputEvents)), 61 | uintptr(data), 62 | uintptr(unsafe.Sizeof(inputEvents[0])), 63 | ) 64 | if int(ret) != len(inputEvents) { 65 | return err 66 | } 67 | return nil 68 | } 69 | 70 | func KeyDownInputArrowDownSSC() Input { 71 | return Input{ 72 | InputType: 1, 73 | KeyboardInput: KeyboardInput{ 74 | ScanCode: 0x50, 75 | Flags: KeyeventfScancode | KeyeventfExtendedkey, 76 | }, 77 | } 78 | } 79 | 80 | func KeyUpInputArrowDownSSC() Input { 81 | return Input{ 82 | InputType: 1, 83 | KeyboardInput: KeyboardInput{ 84 | ScanCode: 0x50 + 128, 85 | Flags: KeyeventfScancode | KeyeventfKeyup | KeyeventfExtendedkey, 86 | }, 87 | } 88 | } 89 | 90 | // SendString sends input-events to the OS, that simulate typing the given string 91 | func SendString(s string) { 92 | for _, r := range s { 93 | SendInput(UnicodeKeyDownInput(uint16(r)), UnicodeKeyUpInput(uint16(r))) 94 | } 95 | } 96 | 97 | // KeyDownInput returns the input-struct for pressing the given key (by virtual-keycode) 98 | func KeyDownInput(keycode uint16) Input { 99 | return Input{ 100 | InputType: 1, 101 | KeyboardInput: KeyboardInput{ 102 | VirtualKeyCode: keycode, 103 | }, 104 | } 105 | } 106 | 107 | // KeyUpInput returns the input-struct for releasing the given key (by virtual-keycode) 108 | func KeyUpInput(keycode uint16) Input { 109 | return Input{ 110 | InputType: 1, 111 | KeyboardInput: KeyboardInput{ 112 | VirtualKeyCode: keycode, 113 | Flags: KeyeventfKeyup, 114 | }, 115 | } 116 | } 117 | 118 | // UnicodeKeyDownInput returns the input-struct for pressing the given key (by unicode codepoint) 119 | func UnicodeKeyDownInput(keycode uint16) Input { 120 | return Input{ 121 | InputType: 1, 122 | KeyboardInput: KeyboardInput{ 123 | VirtualKeyCode: 0, 124 | ScanCode: keycode, 125 | Flags: KeyeventfUnicode, 126 | }, 127 | } 128 | } 129 | 130 | // UnicodeKeyUpInput returns the input-struct for releasing the given key (by unicode codepoint) 131 | func UnicodeKeyUpInput(keycode uint16) Input { 132 | return Input{ 133 | InputType: 1, 134 | KeyboardInput: KeyboardInput{ 135 | VirtualKeyCode: 0, 136 | ScanCode: keycode, 137 | Flags: KeyeventfKeyup | KeyeventfUnicode, 138 | }, 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION=unversioned 2 | TOKEN=none 3 | 4 | ifeq (, $(shell which go.exe)) 5 | go=go 6 | go-bindata=go-bindata-assetfs 7 | else 8 | go=go.exe 9 | go-bindata=go-bindata-assetfs.exe 10 | endif 11 | 12 | ifeq ($(filter v%,${VERSION}),${VERSION}) 13 | NPMVERSION=$(subst v,,${VERSION}) 14 | else 15 | NPMVERSION=0.0.0 16 | endif 17 | 18 | 19 | all: build test package docs CHANGELOG.md 20 | 21 | .PHONY: setup 22 | setup: setup-submodule setup-go setup-npm 23 | 24 | .PHONY: setup-go 25 | setup-go: 26 | ${go} mod download 27 | ${go} get github.com/go-bindata/go-bindata/... 28 | ${go} get github.com/elazarl/go-bindata-assetfs/... 29 | 30 | .PHONY: setup-npm 31 | setup-npm: 32 | cd vscode-yolol && \ 33 | npm install && \ 34 | npm install -g vsce 35 | 36 | .PHONY: setup-submodule 37 | setup-submodule: 38 | git submodule init 39 | git submodule update 40 | 41 | 42 | 43 | .PHONY: test 44 | test: go-test acid-tests vsc-test 45 | 46 | .PHONY: go-test 47 | go-test: 48 | ${go} test ./... 49 | 50 | .PHONY: acid-tests 51 | acid-tests: yodk 52 | ./ci/run-acid-tests.sh 53 | 54 | .PHONY: vsc-test 55 | vsc-test: vscode-yolol.vsix 56 | ifdef WSLENV 57 | echo Skipping extension-tests on wsl 58 | else 59 | cd vscode-yolol && npm test --silent 60 | endif 61 | 62 | 63 | 64 | .PHONY: build 65 | build: binaries vscode-yolol.vsix 66 | 67 | .PHONY: binaries 68 | binaries: yodk yodk.exe yodk-darwin 69 | 70 | yodk: yodk-${VERSION} 71 | cp yodk-${VERSION} yodk 72 | yodk-${VERSION}: $(shell find pkg) $(shell find cmd) stdlib/bindata.go stdlib/generate.go 73 | GOOS=linux ${go} build -o yodk-${VERSION} -ldflags "-X github.com/dbaumgarten/yodk/cmd.YodkVersion=${VERSION}" 74 | 75 | yodk.exe: yodk-${VERSION}.exe 76 | -cp yodk-${VERSION}.exe yodk.exe 77 | yodk-${VERSION}.exe: $(shell find pkg) $(shell find cmd) stdlib/bindata.go stdlib/generate.go 78 | GOOS=windows ${go} build -o yodk-${VERSION}.exe -ldflags "-X github.com/dbaumgarten/yodk/cmd.YodkVersion=${VERSION}" 79 | 80 | yodk-darwin: yodk-darwin-${VERSION} 81 | cp yodk-darwin-${VERSION} yodk-darwin 82 | yodk-darwin-${VERSION}: $(shell find pkg) $(shell find cmd) stdlib/bindata.go stdlib/generate.go 83 | GOOS=darwin ${go} build -o yodk-darwin-${VERSION} -ldflags "-X github.com/dbaumgarten/yodk/cmd.YodkVersion=${VERSION}" 84 | 85 | 86 | 87 | .PHONY: stdlib 88 | stdlib: 89 | cd stdlib && ${go-bindata} -pkg stdlib -prefix src/ ./src 90 | 91 | 92 | 93 | .PHONY: package 94 | package: zips vscode-yolol.vsix 95 | 96 | .PHONY: zips 97 | zips: yodk-win.zip yodk-linux.zip yodk-darwin.zip 98 | 99 | yodk-win.zip: yodk.exe 100 | zip yodk-win.zip yodk.exe 101 | 102 | yodk-linux.zip: yodk 103 | zip yodk-linux.zip yodk 104 | 105 | yodk-darwin.zip: yodk-darwin 106 | zip yodk-darwin.zip yodk-darwin 107 | 108 | vscode-yolol.vsix: vscode-yolol/vscode-yolol-${NPMVERSION}.vsix 109 | cp vscode-yolol/vscode-yolol-${NPMVERSION}.vsix vscode-yolol.vsix 110 | 111 | vscode-yolol/vscode-yolol-${NPMVERSION}.vsix: yodk yodk.exe yodk-darwin CHANGELOG.md $(shell find vscode-yolol/src) $(shell find vscode-yolol/syntaxes/) vscode-yolol/package.json 112 | cd vscode-yolol && \ 113 | origtime=`stat -c %Y package.json` && \ 114 | npm version --no-git-tag-version ${NPMVERSION} --allow-same-version && \ 115 | vsce package && \ 116 | npm version 0.0.0 --allow-same-version && \ 117 | touch -m -d @$${origtime} package.json 118 | 119 | 120 | 121 | CHANGELOG.md: .git/ 122 | ./ci/build-changelog.sh 123 | cp CHANGELOG.md vscode-yolol/ 124 | 125 | 126 | 127 | .PHONY: docs 128 | docs: yodk yodk.exe 129 | ./ci/build-docs.sh 130 | 131 | 132 | 133 | publish-vsix: vscode-yolol.vsix 134 | vsce publish --packagePath vscode-yolol.vsix -p ${TOKEN} 135 | 136 | 137 | 138 | .PHONY: clean 139 | clean: 140 | -rm -rf yodk* *.zip *.vsix CHANGELOG.md vscode-yolol/*.vsix vscode-yolol/CHANGELOG.md vscode-yolol/bin/win32/yo* vscode-yolol/bin/linux/yo* vscode-yolol/bin/darwin/yo* acid_test.yaml 141 | -rm -rf docs/sitemap_new.xml docs/generated/* docs/vscode-yolol.md docs/README.md docs/nolol-stdlib.md 142 | -------------------------------------------------------------------------------- /pkg/vm/variable.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/dbaumgarten/yodk/pkg/number" 8 | ) 9 | 10 | // VariableFromString tries to create a variable of the correct type from the given string. 11 | // If the string is enclosed in quotes, the string between the quotes is used as string-value for the variable. 12 | // Else, it tries to parse the given string into a number. If that also fails, the plain given string is used as value. 13 | func VariableFromString(str string) *Variable { 14 | var value interface{} 15 | if strings.HasPrefix(str, "\"") && strings.HasSuffix(str, "\"") && len(str) >= 2 { 16 | value = str[1 : len(str)-1] 17 | } else { 18 | deci, err := number.FromString(str) 19 | if err == nil { 20 | value = deci 21 | } else { 22 | value = str 23 | } 24 | } 25 | return &Variable{ 26 | Value: value, 27 | } 28 | } 29 | 30 | // VariableFromType creates a new variable from the given input. The type of the variable is decided by the input-type. 31 | func VariableFromType(inp interface{}) (*Variable, error) { 32 | var value interface{} 33 | switch v := inp.(type) { 34 | case string: 35 | value = v 36 | case *string: 37 | value = *v 38 | case int: 39 | value = number.FromInt(v) 40 | case int32: 41 | value = number.FromInt(int(v)) 42 | case int64: 43 | value = number.FromInt(int(v)) 44 | case float32: 45 | value = number.FromFloat64(float64(v)) 46 | case float64: 47 | value = number.FromFloat64(v) 48 | default: 49 | return nil, fmt.Errorf("Can not convert type %T to variable", inp) 50 | } 51 | return &Variable{ 52 | Value: value, 53 | }, nil 54 | } 55 | 56 | // Variable represents a yolol-variable during the execution 57 | type Variable struct { 58 | Value interface{} 59 | } 60 | 61 | // IsNumber returns true if the variable represents a number 62 | func (v *Variable) IsNumber() bool { 63 | _, isNum := v.Value.(number.Number) 64 | _, isNump := v.Value.(*number.Number) 65 | return isNum || isNump 66 | } 67 | 68 | // IsString returns true if the variable represents a string 69 | func (v *Variable) IsString() bool { 70 | _, isStr := v.Value.(string) 71 | _, isStrp := v.Value.(string) 72 | return isStr || isStrp 73 | } 74 | 75 | // SameType returns true if the variable has the same type as the given variable 76 | func (v *Variable) SameType(other *Variable) bool { 77 | return v.IsNumber() == other.IsNumber() 78 | } 79 | 80 | // TypeName returns the name of the type this variable has 81 | func (v *Variable) TypeName() string { 82 | if v.IsString() { 83 | return "string" 84 | } 85 | return "number" 86 | } 87 | 88 | // Equals checks if this variable equals another variable 89 | func (v *Variable) Equals(other *Variable) bool { 90 | if !v.SameType(other) { 91 | return false 92 | } 93 | if v.IsString() { 94 | return v.String() == other.String() 95 | } 96 | if v.IsNumber() { 97 | return v.Number() == other.Number() 98 | } 99 | return false 100 | } 101 | 102 | func (v *Variable) String() string { 103 | if val, isString := v.Value.(string); isString { 104 | return val 105 | } 106 | return "" 107 | } 108 | 109 | // Repr returns the string-representation of the variable. 110 | // If the variable is of type string, its value is enclosed in quotes. 111 | func (v *Variable) Repr() string { 112 | if v.IsNumber() { 113 | return v.Itoa() 114 | } 115 | return "\"" + v.String() + "\"" 116 | } 117 | 118 | // Itoa returns the string-representation of the number stored in the variable 119 | func (v *Variable) Itoa() string { 120 | if val, isNum := v.Value.(number.Number); isNum { 121 | return val.String() 122 | } 123 | return "" 124 | } 125 | 126 | // Number returns the value of the variable as number 127 | func (v *Variable) Number() number.Number { 128 | if val, isNum := v.Value.(number.Number); isNum { 129 | return val 130 | } 131 | return number.Zero 132 | } 133 | 134 | // Bool returns the truth-value of a variable 135 | func (v *Variable) Bool() *Variable { 136 | if v.IsString() { 137 | return &Variable{ 138 | Value: number.Zero, 139 | } 140 | } 141 | return v 142 | } 143 | -------------------------------------------------------------------------------- /pkg/jsonrpc2/stream.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package jsonrpc2 6 | 7 | import ( 8 | "bufio" 9 | "context" 10 | "encoding/json" 11 | "fmt" 12 | "io" 13 | "strconv" 14 | "strings" 15 | "sync" 16 | ) 17 | 18 | // Stream abstracts the transport mechanics from the JSON RPC protocol. 19 | // A Conn reads and writes messages using the stream it was provided on 20 | // construction, and assumes that each call to Read or Write fully transfers 21 | // a single message, or returns an error. 22 | type Stream interface { 23 | // Read gets the next message from the stream. 24 | // It is never called concurrently. 25 | Read(context.Context) ([]byte, error) 26 | // Write sends a message to the stream. 27 | // It must be safe for concurrent use. 28 | Write(context.Context, []byte) error 29 | } 30 | 31 | // NewStream returns a Stream built on top of an io.Reader and io.Writer 32 | // The messages are sent with no wrapping, and rely on json decode consistency 33 | // to determine message boundaries. 34 | func NewStream(in io.Reader, out io.Writer) Stream { 35 | return &plainStream{ 36 | in: json.NewDecoder(in), 37 | out: out, 38 | } 39 | } 40 | 41 | type plainStream struct { 42 | in *json.Decoder 43 | outMu sync.Mutex 44 | out io.Writer 45 | } 46 | 47 | func (s *plainStream) Read(ctx context.Context) ([]byte, error) { 48 | select { 49 | case <-ctx.Done(): 50 | return nil, ctx.Err() 51 | default: 52 | } 53 | var raw json.RawMessage 54 | if err := s.in.Decode(&raw); err != nil { 55 | return nil, err 56 | } 57 | return raw, nil 58 | } 59 | 60 | func (s *plainStream) Write(ctx context.Context, data []byte) error { 61 | select { 62 | case <-ctx.Done(): 63 | return ctx.Err() 64 | default: 65 | } 66 | s.outMu.Lock() 67 | _, err := s.out.Write(data) 68 | s.outMu.Unlock() 69 | return err 70 | } 71 | 72 | // NewHeaderStream returns a Stream built on top of an io.Reader and io.Writer 73 | // The messages are sent with HTTP content length and MIME type headers. 74 | // This is the format used by LSP and others. 75 | func NewHeaderStream(in io.Reader, out io.Writer) Stream { 76 | return &headerStream{ 77 | in: bufio.NewReader(in), 78 | out: out, 79 | } 80 | } 81 | 82 | type headerStream struct { 83 | in *bufio.Reader 84 | outMu sync.Mutex 85 | out io.Writer 86 | } 87 | 88 | func (s *headerStream) Read(ctx context.Context) ([]byte, error) { 89 | select { 90 | case <-ctx.Done(): 91 | return nil, ctx.Err() 92 | default: 93 | } 94 | var length int64 95 | // read the header, stop on the first empty line 96 | for { 97 | line, err := s.in.ReadString('\n') 98 | if err != nil { 99 | return nil, fmt.Errorf("failed reading header line %q", err) 100 | } 101 | line = strings.TrimSpace(line) 102 | // check we have a header line 103 | if line == "" { 104 | break 105 | } 106 | colon := strings.IndexRune(line, ':') 107 | if colon < 0 { 108 | return nil, fmt.Errorf("invalid header line %q", line) 109 | } 110 | name, value := line[:colon], strings.TrimSpace(line[colon+1:]) 111 | switch name { 112 | case "Content-Length": 113 | if length, err = strconv.ParseInt(value, 10, 32); err != nil { 114 | return nil, fmt.Errorf("failed parsing Content-Length: %v", value) 115 | } 116 | if length <= 0 { 117 | return nil, fmt.Errorf("invalid Content-Length: %v", length) 118 | } 119 | default: 120 | // ignoring unknown headers 121 | } 122 | } 123 | if length == 0 { 124 | return nil, fmt.Errorf("missing Content-Length header") 125 | } 126 | data := make([]byte, length) 127 | if _, err := io.ReadFull(s.in, data); err != nil { 128 | return nil, err 129 | } 130 | return data, nil 131 | } 132 | 133 | func (s *headerStream) Write(ctx context.Context, data []byte) error { 134 | select { 135 | case <-ctx.Done(): 136 | return ctx.Err() 137 | default: 138 | } 139 | s.outMu.Lock() 140 | _, err := fmt.Fprintf(s.out, "Content-Length: %v\r\n\r\n", len(data)) 141 | if err == nil { 142 | _, err = s.out.Write(data) 143 | } 144 | s.outMu.Unlock() 145 | return err 146 | } 147 | -------------------------------------------------------------------------------- /pkg/parser/validate.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | 7 | "github.com/dbaumgarten/yodk/pkg/parser/ast" 8 | ) 9 | 10 | var brokenVarnameRegex = regexp.MustCompile("if|then|else|end|goto") 11 | 12 | const ( 13 | ValidateLocalVars = 1 14 | ValidateGlobalVars = 2 15 | ValidateLogicalNots = 4 16 | ValidateFactorials = 8 17 | ValidateAll = ValidateLocalVars | ValidateGlobalVars | ValidateLogicalNots | ValidateFactorials 18 | ) 19 | 20 | // RemoveParenthesis removes all ()-nodes from the ast. These are superflous but were required duing parding because of bugs in starbase 21 | func RemoveParenthesis(n ast.Node) { 22 | g := func(node ast.Node, visitType int) error { 23 | if unar, is := node.(*ast.UnaryOperation); is { 24 | if unar.Operator == "()" { 25 | return ast.NewNodeReplacement(unar.Exp) 26 | } 27 | } 28 | return nil 29 | } 30 | n.Accept(ast.VisitorFunc(g)) 31 | } 32 | 33 | // Validate checks the program for certain errors. Usualy this is to emulate bug's in the game's implementation 34 | func Validate(prog ast.Node, flags int) []*Error { 35 | errors := make([]*Error, 0) 36 | 37 | checkForInnerNot := func(n ast.Node) { 38 | if innerun, is := n.(*ast.UnaryOperation); is { 39 | if innerun.Operator == "not" { 40 | errors = append(errors, &Error{ 41 | Message: "Because of an ingame-bug this expression must be wrapped in parenthesis", 42 | StartPosition: innerun.Start(), 43 | EndPosition: innerun.End(), 44 | }) 45 | } 46 | } 47 | } 48 | 49 | shouldValidate := func(flag int) bool { 50 | return (flags & flag) != 0 51 | } 52 | 53 | checkVarname := func(name string, node ast.Node) { 54 | isGlobal := strings.HasPrefix(name, ":") 55 | if (isGlobal && shouldValidate(ValidateGlobalVars)) || (!isGlobal && shouldValidate(ValidateLocalVars)) { 56 | if brokenVarnameRegex.MatchString(name) { 57 | errors = append(errors, &Error{ 58 | Message: "Variable-names containing if|then|else|end|goto do not work ingame", 59 | StartPosition: node.Start(), 60 | EndPosition: node.End(), 61 | }) 62 | } 63 | } 64 | } 65 | 66 | f := func(node ast.Node, visitType int) error { 67 | if visitType == ast.PostVisit || visitType == ast.SingleVisit { 68 | switch n := node.(type) { 69 | case *ast.NumberConstant: 70 | if n.Value == problematicNumberConstant { 71 | errors = append(errors, &Error{ 72 | Message: "Invalid number-constant. This value is too large.", 73 | StartPosition: n.Start(), 74 | EndPosition: n.End(), 75 | }) 76 | } 77 | case *ast.Assignment: 78 | checkVarname(n.Variable, n) 79 | break 80 | case *ast.Dereference: 81 | checkVarname(n.Variable, n) 82 | case *ast.UnaryOperation: 83 | if shouldValidate(ValidateLogicalNots) { 84 | if n.Operator == "not" { 85 | if innerbin, is := n.Exp.(*ast.BinaryOperation); is { 86 | if innerbin.Operator == "and" || innerbin.Operator == "or" { 87 | errors = append(errors, &Error{ 88 | Message: "Because of an ingame-bug this expression must be wrapped in parenthesis", 89 | StartPosition: innerbin.Start(), 90 | EndPosition: innerbin.End(), 91 | }) 92 | } 93 | } 94 | } 95 | if n.Operator != "()" { 96 | checkForInnerNot(n.Exp) 97 | } 98 | } 99 | if shouldValidate(ValidateFactorials) { 100 | if n.Operator == "!" { 101 | switch n.Exp.(type) { 102 | case *ast.NumberConstant: 103 | break 104 | case *ast.Dereference: 105 | break 106 | default: 107 | errors = append(errors, &Error{ 108 | Message: "Yolol only allows factorials on number-constants and variables.", 109 | StartPosition: n.Exp.Start(), 110 | EndPosition: n.Exp.End(), 111 | }) 112 | 113 | } 114 | } 115 | 116 | } 117 | case *ast.BinaryOperation: 118 | if shouldValidate(ValidateLogicalNots) { 119 | if n.Operator == "and" || n.Operator == "or" { 120 | checkForInnerNot(n.Exp1) 121 | } else { 122 | checkForInnerNot(n.Exp2) 123 | } 124 | } 125 | } 126 | } 127 | return nil 128 | } 129 | 130 | prog.Accept(ast.VisitorFunc(f)) 131 | 132 | return errors 133 | } 134 | -------------------------------------------------------------------------------- /pkg/langserver/formatting.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "log" 5 | "strings" 6 | 7 | "github.com/dbaumgarten/yodk/pkg/lsp" 8 | "github.com/dbaumgarten/yodk/pkg/nolol" 9 | "github.com/dbaumgarten/yodk/pkg/parser" 10 | "github.com/dbaumgarten/yodk/pkg/util" 11 | "github.com/pmezard/go-difflib/difflib" 12 | ) 13 | 14 | // Format computes formatting instructions for the given document. 15 | // Parser errors during formatting are silently discared, as reporting them to the user would just be annoying 16 | // and showing errors is already done by the diagnostics 17 | func (s *LangServer) Format(params *lsp.DocumentFormattingParams) ([]lsp.TextEdit, error) { 18 | unformatted, err := s.cache.Get(params.TextDocument.URI) 19 | file := string(params.TextDocument.URI) 20 | if err != nil { 21 | return nil, err 22 | } 23 | var formatted string 24 | 25 | if strings.HasSuffix(file, ".yolol") { 26 | p := parser.NewParser() 27 | parsed, errs := p.Parse(unformatted) 28 | if errs != nil { 29 | return []lsp.TextEdit{}, nil 30 | } 31 | gen := parser.Printer{} 32 | if strings.HasSuffix(file, ".opt.yolol") { 33 | gen.Mode = parser.PrintermodeCompact 34 | } else { 35 | switch s.settings.Yolol.Formatting.Mode { 36 | case FormatModeReadale: 37 | gen.Mode = parser.PrintermodeReadable 38 | case FormatModeCompact: 39 | gen.Mode = parser.PrintermodeCompact 40 | default: 41 | gen.Mode = parser.PrintermodeCompact 42 | 43 | } 44 | } 45 | formatted, err = gen.Print(parsed) 46 | if err != nil { 47 | return []lsp.TextEdit{}, nil 48 | } 49 | err = util.CheckForFormattingErrorYolol(parsed, formatted) 50 | if err != nil { 51 | return nil, err 52 | } 53 | } else if strings.HasSuffix(file, ".nolol") { 54 | p := nolol.NewParser() 55 | parsed, errs := p.Parse(unformatted) 56 | if errs != nil { 57 | return []lsp.TextEdit{}, nil 58 | } 59 | printer := nolol.NewPrinter() 60 | formatted, err = printer.Print(parsed) 61 | if err != nil { 62 | return []lsp.TextEdit{}, nil 63 | } 64 | err = util.CheckForFormattingErrorNolol(parsed, formatted) 65 | if err != nil { 66 | return nil, err 67 | } 68 | } else { 69 | log.Println("Unsupported file-type:", file) 70 | } 71 | 72 | return ComputeTextEdits(unformatted, formatted), nil 73 | } 74 | 75 | // ComputeTextEdits computes text edits that are required to 76 | // change the `unformatted` to the `formatted` text. 77 | // Blatantly stolen from https://github.com/sourcegraph/go-langserver/blob/master/langserver/format.go 78 | func ComputeTextEdits(unformatted string, formatted string) []lsp.TextEdit { 79 | // LSP wants a list of TextEdits. We use difflib to compute a 80 | // non-naive TextEdit. Originally we returned an edit which deleted 81 | // everything followed by inserting everything. This leads to a poor 82 | // experience in vscode. 83 | unformattedLines := strings.Split(unformatted, "\n") 84 | formattedLines := strings.Split(formatted, "\n") 85 | m := difflib.NewMatcher(unformattedLines, formattedLines) 86 | var edits []lsp.TextEdit 87 | for _, op := range m.GetOpCodes() { 88 | switch op.Tag { 89 | case 'r': // 'r' (replace): a[i1:i2] should be replaced by b[j1:j2] 90 | edits = append(edits, lsp.TextEdit{ 91 | Range: lsp.Range{ 92 | Start: lsp.Position{ 93 | Line: float64(op.I1), 94 | }, 95 | End: lsp.Position{ 96 | Line: float64(op.I2), 97 | }, 98 | }, 99 | NewText: strings.Join(formattedLines[op.J1:op.J2], "\n") + "\n", 100 | }) 101 | case 'd': // 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case. 102 | edits = append(edits, lsp.TextEdit{ 103 | Range: lsp.Range{ 104 | Start: lsp.Position{ 105 | Line: float64(op.I1), 106 | }, 107 | End: lsp.Position{ 108 | Line: float64(op.I2), 109 | }, 110 | }, 111 | }) 112 | case 'i': // 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case. 113 | edits = append(edits, lsp.TextEdit{ 114 | Range: lsp.Range{ 115 | Start: lsp.Position{ 116 | Line: float64(op.I1), 117 | }, 118 | End: lsp.Position{ 119 | Line: float64(op.I1), 120 | }, 121 | }, 122 | NewText: strings.Join(formattedLines[op.J1:op.J2], "\n") + "\n", 123 | }) 124 | } 125 | } 126 | 127 | return edits 128 | } 129 | --------------------------------------------------------------------------------