diff --git a/.gitignore b/.gitignore index 0998cb12..3be955f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,13 @@ *~ node_modules -./local/ -./tests_output/ -./test/output/ +/local/ +/tests_output/ +/test/output/ .DS_Store -./tmp/ -./web/ -./release/ -./gen/ +/tmp/ +/web/ +/release/ +/gen/ config.js chromedriver.log nightwatch.conf.js diff --git a/README.md b/README.md index 2373e670..36364469 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ npm i npm run build ``` +To use GitHub integration locally, download the Firebase config file, e.g. https://8bitworkshop.com/v[version]/config.js + ### Start Server Start a web server on http://localhost:8000/ while TypeScript compiles in the background: @@ -108,3 +110,4 @@ The IDE uses custom forks for many of these, found at https://github.com/sehugg? * https://github.com/sehugg/8bitworkshop-compilers * https://github.com/sehugg/8bit-tools * https://github.com/sehugg/awesome-8bitgamedev + diff --git a/ecsroot/kernel.txt b/ecsroot/kernel.txt new file mode 100644 index 00000000..f5696d2a --- /dev/null +++ b/ecsroot/kernel.txt @@ -0,0 +1,90 @@ +# Kernel +# A Kernel draws a set of scanlines to the screen. + +component Kernel +lines: 0..255 "Height of region in scanlines" +bgcolor: 0..255 "Background color" +end + +system SimpleKernel +tempbytes 8 +on preframe do once [Kernel] --- + lda #192 ; TODO: numlines + sec + sbc ypos_ypos_b0+ent + sta {{$5}}+ofs + + ldy hasbitmap_bitmap_b0+ent + lda bitmap_bitmapdata_b0,y + sec + sbc {{$5}}+ofs + sta {{$0}}+ofs + lda bitmap_bitmapdata_b8,y + sbc #0 + sta {{$1}}+ofs + + ldy hascolormap_colormap_b0+ent + lda colormap_colormapdata_b0,y + sec + sbc {{$5}}+ofs + sta {{$2}}+ofs + lda colormap_colormapdata_b8,y + sbc #0 + sta {{$3}}+ofs + + lda sprite_height_b0+ent + sta {{$4}}+ofs + lda ypos_ypos_b0+ent + sta {{$5}}+ofs +--- +on preframe do once [Sprite,HasBitmap,HasColormap,HasYpos] -- + {{@KernelSetup}} 0,0 + {{@KernelSetup}} 1,6 +-- +on kernel do once [Kernel]: + lda %{ + diff --git a/package-lock.json b/package-lock.json index 70d390d0..bcf822e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@types/chroma-js": "^2.1.3", "@types/emscripten": "^1.39.5", + "@types/js-yaml": "^4.0.5", "@wasmer/wasi": "^0.12.0", "@wasmer/wasmfs": "^0.12.0", "binaryen": "^101.0.0", @@ -31,8 +32,10 @@ "devDependencies": { "@types/bootbox": "^5.1.3", "@types/bootstrap": "^3.4.0", + "@types/expect": "^24.3.0", "@types/file-saver": "^2.0.3", "@types/jquery": "^3.5.5", + "@types/mocha": "^9.1.0", "@types/node": "^14.14.20", "atob": "^2.1.x", "bootstrap": "^3.4.1", @@ -61,7 +64,7 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "optional": true, + "devOptional": true, "dependencies": { "@babel/highlight": "^7.16.7" }, @@ -158,7 +161,7 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "optional": true, + "devOptional": true, "engines": { "node": ">=6.9.0" } @@ -167,7 +170,7 @@ "version": "7.16.10", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "optional": true, + "devOptional": true, "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", @@ -181,7 +184,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "optional": true, + "devOptional": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -193,7 +196,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "optional": true, + "devOptional": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -207,7 +210,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "optional": true, + "devOptional": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -275,6 +278,92 @@ "node": ">=6.9.0" } }, + "node_modules/@jest/types": { + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", + "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@nightwatch/chai": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@nightwatch/chai/-/chai-5.0.1.tgz", @@ -391,12 +480,46 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "optional": true }, + "node_modules/@types/expect": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-24.3.0.tgz", + "integrity": "sha512-aq5Z+YFBz5o2b6Sp1jigx5nsmoZMK5Ceurjwy6PZmRv7dEi1jLtkARfvB1ME+OXJUG+7TZUDcv3WoCr/aor6dQ==", + "deprecated": "This is a stub types definition. expect provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "expect": "*" + } + }, "node_modules/@types/file-saver": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.5.tgz", "integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==", "dev": true }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, "node_modules/@types/jquery": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.13.tgz", @@ -406,6 +529,17 @@ "@types/sizzle": "*" } }, + "node_modules/@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==" + }, + "node_modules/@types/mocha": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", + "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", + "dev": true + }, "node_modules/@types/node": { "version": "14.18.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.9.tgz", @@ -423,6 +557,27 @@ "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "dev": true }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "dev": true + }, "node_modules/@types/yauzl": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", @@ -596,13 +751,10 @@ "optional": true }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "optional": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "devOptional": true }, "node_modules/array-equal": { "version": "1.0.0", @@ -1276,7 +1428,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "optional": true, + "devOptional": true, "dependencies": { "color-name": "1.1.3" } @@ -1285,7 +1437,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "optional": true + "devOptional": true }, "node_modules/combined-stream": { "version": "1.0.8", @@ -1567,6 +1719,15 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "optional": true }, + "node_modules/diff-sequences": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", + "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1817,6 +1978,21 @@ "node": ">=0.10.0" } }, + "node_modules/expect": { + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.4.6.tgz", + "integrity": "sha512-1M/0kAALIaj5LaG66sFJTbRsWTADnylly82cu4bspI0nl+pgP4E6Bh/aqdHlTUjul06K7xQnnrAoqfxVU0+/ag==", + "dev": true, + "dependencies": { + "@jest/types": "^27.4.2", + "jest-get-type": "^27.4.0", + "jest-matcher-utils": "^27.4.6", + "jest-message-util": "^27.4.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2258,7 +2434,7 @@ "version": "4.2.9", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", - "optional": true + "devOptional": true }, "node_modules/graceful-readlink": { "version": "1.0.1", @@ -2326,7 +2502,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "optional": true, + "devOptional": true, "engines": { "node": ">=4" } @@ -2914,6 +3090,275 @@ "node": ">=4" } }, + "node_modules/jest-diff": { + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.6.tgz", + "integrity": "sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.4.0", + "jest-get-type": "^27.4.0", + "pretty-format": "^27.4.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-get-type": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", + "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz", + "integrity": "sha512-XD4PKT3Wn1LQnRAq7ZsTI0VRuEc9OrCPFiO1XL7bftTGmfNF0DcEwMHRgqiu7NGf8ZoZDREpGrCniDkjt79WbA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.4.6", + "jest-get-type": "^27.4.0", + "pretty-format": "^27.4.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.6.tgz", + "integrity": "sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.4.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "pretty-format": "^27.4.6", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jquery": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", @@ -2923,16 +3368,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "optional": true + "devOptional": true }, "node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "optional": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "devOptional": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -3486,7 +3930,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "optional": true, + "devOptional": true, "dependencies": { "braces": "^3.0.1", "picomatch": "^2.2.3" @@ -3649,12 +4093,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/mocha/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "devOptional": true - }, "node_modules/mocha/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -3763,18 +4201,6 @@ "node": ">=8" } }, - "node_modules/mocha/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "devOptional": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/mocha/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3978,9 +4404,9 @@ "optional": true }, "node_modules/nightwatch": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.0.5.tgz", - "integrity": "sha512-rVQGbGtgCxVnhQonp2DNDuhyZQw2LuVfRS7oWymXwXZFp2/azVTHxa9hsUblwqc5tKQxB1Tzq8EdGG7yqYyzLw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.0.7.tgz", + "integrity": "sha512-nwkD92cacraSrZwOQFv4qrvmIB9T1nIghh9BDpYAIZRVnwjB+oP5JqhEUFQWabEXRp6jX0d/IcPLUBA/D2R3BQ==", "optional": true, "dependencies": { "@nightwatch/chai": "5.0.1", @@ -4186,6 +4612,28 @@ "node": ">=6" } }, + "node_modules/nyc/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "optional": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/nyc/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "optional": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/nyc/node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -4713,6 +5161,41 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz", + "integrity": "sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/pretty-ms": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", @@ -4816,6 +5299,12 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -5237,7 +5726,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "optional": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -5362,6 +5851,27 @@ "node": ">=0.10.0" } }, + "node_modules/stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/stackframe": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", @@ -6120,7 +6630,7 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "optional": true, + "devOptional": true, "requires": { "@babel/highlight": "^7.16.7" } @@ -6195,13 +6705,13 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "optional": true + "devOptional": true }, "@babel/highlight": { "version": "7.16.10", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "optional": true, + "devOptional": true, "requires": { "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", @@ -6212,7 +6722,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "optional": true, + "devOptional": true, "requires": { "color-convert": "^1.9.0" } @@ -6221,7 +6731,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "optional": true, + "devOptional": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -6232,7 +6742,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "optional": true, + "devOptional": true, "requires": { "has-flag": "^3.0.0" } @@ -6284,6 +6794,70 @@ "to-fast-properties": "^2.0.0" } }, + "@jest/types": { + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", + "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "@nightwatch/chai": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@nightwatch/chai/-/chai-5.0.1.tgz", @@ -6388,12 +6962,45 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "optional": true }, + "@types/expect": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-24.3.0.tgz", + "integrity": "sha512-aq5Z+YFBz5o2b6Sp1jigx5nsmoZMK5Ceurjwy6PZmRv7dEi1jLtkARfvB1ME+OXJUG+7TZUDcv3WoCr/aor6dQ==", + "dev": true, + "requires": { + "expect": "*" + } + }, "@types/file-saver": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.5.tgz", "integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==", "dev": true }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, "@types/jquery": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.13.tgz", @@ -6403,6 +7010,17 @@ "@types/sizzle": "*" } }, + "@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==" + }, + "@types/mocha": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", + "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", + "dev": true + }, "@types/node": { "version": "14.18.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.9.tgz", @@ -6420,6 +7038,27 @@ "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "dev": true }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "dev": true + }, "@types/yauzl": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", @@ -6562,13 +7201,10 @@ "optional": true }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "optional": true, - "requires": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "devOptional": true }, "array-equal": { "version": "1.0.0", @@ -7067,7 +7703,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "optional": true, + "devOptional": true, "requires": { "color-name": "1.1.3" } @@ -7076,7 +7712,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "optional": true + "devOptional": true }, "combined-stream": { "version": "1.0.8", @@ -7304,6 +7940,12 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "optional": true }, + "diff-sequences": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", + "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", + "dev": true + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -7499,6 +8141,18 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "devOptional": true }, + "expect": { + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.4.6.tgz", + "integrity": "sha512-1M/0kAALIaj5LaG66sFJTbRsWTADnylly82cu4bspI0nl+pgP4E6Bh/aqdHlTUjul06K7xQnnrAoqfxVU0+/ag==", + "dev": true, + "requires": { + "@jest/types": "^27.4.2", + "jest-get-type": "^27.4.0", + "jest-matcher-utils": "^27.4.6", + "jest-message-util": "^27.4.6" + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -7860,7 +8514,7 @@ "version": "4.2.9", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", - "optional": true + "devOptional": true }, "graceful-readlink": { "version": "1.0.1", @@ -7912,7 +8566,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "optional": true + "devOptional": true }, "has-symbols": { "version": "1.0.2", @@ -8346,6 +9000,206 @@ } } }, + "jest-diff": { + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.6.tgz", + "integrity": "sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^27.4.0", + "jest-get-type": "^27.4.0", + "pretty-format": "^27.4.6" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-get-type": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", + "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", + "dev": true + }, + "jest-matcher-utils": { + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz", + "integrity": "sha512-XD4PKT3Wn1LQnRAq7ZsTI0VRuEc9OrCPFiO1XL7bftTGmfNF0DcEwMHRgqiu7NGf8ZoZDREpGrCniDkjt79WbA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^27.4.6", + "jest-get-type": "^27.4.0", + "pretty-format": "^27.4.6" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-message-util": { + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.6.tgz", + "integrity": "sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.4.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "pretty-format": "^27.4.6", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "jquery": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", @@ -8355,16 +9209,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "optional": true + "devOptional": true }, "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "optional": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "devOptional": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" } }, "jsbn": { @@ -8847,7 +9700,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "optional": true, + "devOptional": true, "requires": { "braces": "^3.0.1", "picomatch": "^2.2.3" @@ -8957,12 +9810,6 @@ "color-convert": "^2.0.1" } }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "devOptional": true - }, "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -9035,15 +9882,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "devOptional": true }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "devOptional": true, - "requires": { - "argparse": "^2.0.1" - } - }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -9198,9 +10036,9 @@ "optional": true }, "nightwatch": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.0.5.tgz", - "integrity": "sha512-rVQGbGtgCxVnhQonp2DNDuhyZQw2LuVfRS7oWymXwXZFp2/azVTHxa9hsUblwqc5tKQxB1Tzq8EdGG7yqYyzLw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.0.7.tgz", + "integrity": "sha512-nwkD92cacraSrZwOQFv4qrvmIB9T1nIghh9BDpYAIZRVnwjB+oP5JqhEUFQWabEXRp6jX0d/IcPLUBA/D2R3BQ==", "optional": true, "requires": { "@nightwatch/chai": "5.0.1", @@ -9354,6 +10192,25 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "optional": true }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "optional": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "optional": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -9740,6 +10597,31 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "devOptional": true }, + "pretty-format": { + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz", + "integrity": "sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, "pretty-ms": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", @@ -9817,6 +10699,12 @@ "safe-buffer": "^5.1.0" } }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -10130,7 +11018,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "optional": true + "devOptional": true }, "source-map": { "version": "0.6.1", @@ -10237,6 +11125,23 @@ "tweetnacl": "~0.14.0" } }, + "stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, "stackframe": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", diff --git a/package.json b/package.json index bf0631cf..24674cfc 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@types/chroma-js": "^2.1.3", "@types/emscripten": "^1.39.5", + "@types/js-yaml": "^4.0.5", "@wasmer/wasi": "^0.12.0", "@wasmer/wasmfs": "^0.12.0", "binaryen": "^101.0.0", @@ -32,8 +33,10 @@ "devDependencies": { "@types/bootbox": "^5.1.3", "@types/bootstrap": "^3.4.0", + "@types/expect": "^24.3.0", "@types/file-saver": "^2.0.3", "@types/jquery": "^3.5.5", + "@types/mocha": "^9.1.0", "@types/node": "^14.14.20", "atob": "^2.1.x", "bootstrap": "^3.4.1", @@ -66,8 +69,8 @@ "esbuild-worker": "esbuild src/worker/workermain.ts --bundle --sourcemap --target=es2017 --outfile=./gen/worker/bundle.js", "esbuild-ui": "esbuild src/ide/ui.ts src/ide/embedui.ts --splitting --format=esm --bundle --minify --sourcemap --target=es2017 --outdir=./gen/ --external:path --external:fs", "test-one": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000", - "test-node": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000 test/cli", - "test-profile": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000 --prof test/cli", + "test-node": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000 test/cli gen/test", + "test-profile": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000 --prof test/cli gen/test", "test-worker": "NODE_PATH=$(pwd) mocha --timeout 60000 test/cli/testworker.js", "test-platforms": "NODE_PATH=$(pwd) mocha --timeout 60000 test/cli/testplatforms.js", "test-verilog": "NODE_PATH=$(pwd) mocha --timeout 60000 --reporter mocha-simple-html-reporter --reporter-options output=test/output/verilog.html test/verilog/testverilog.js", diff --git a/presets/vcs/ecs/jumper.ecs b/presets/vcs/ecs/jumper.ecs new file mode 100644 index 00000000..8eac8ff2 --- /dev/null +++ b/presets/vcs/ecs/jumper.ecs @@ -0,0 +1,687 @@ + +//#resource "vcs-ca65.h" + +import "vcslib.ecs" +import "sprites.ecs" +import "score.ecs" +import "sound.ecs" +import "velocity.ecs" +import "kernel2.ecs" +import "random.ecs" +import "versatile.ecs" +import "music.ecs" + +// TODO: not yet used +component Activity + activity: enum [Standing,Walking,Jumping] +end + +component Enemy +end + +component Gravity +end + +component Jumper +end + +system MoveJoyVel + // dampen velocity + on frame8 do with [HasXpos] +--- + lda {{Bitmap:bitmapdata}},x + sbc #0 + sta {{$2}},y +; get bitmap height + lda {{Colormap:colormapdata}},x + sbc #0 + sta {{$6}},y +; save ypos + ldx {{$12}} ; restore X + lda {{ L0 H0 L1 H1 + lda {{$1}} + ldy {{$2}} + sty {{$1}} + sta {{$2}} + lda {{$5}} + ldy {{$6}} + sty {{$5}} + sta {{$6}} +--- + on preframe do if [KernelSection,BGColor] +--- + lda {{= $80, load next duration +@LoadDuration: + cmp #$ff ; $ff = end of song + bne @NoResetTrack + sta {{set timer}} + {{!musicdone}} + jmp @Done +@NoResetTrack: + and #$7f +; asl + sta {{set timer}} ; store duration * 2 +@IncDataPtr: +; increment song pointer + inc {{set songptr 0}} + bne @Done + inc {{set songptr 8}} +@Done: + --- + // TODO: should use "with"? + on musicnote do select all [MusicChannel] + --- +; Play a note +; X = channel (0,1) +; Y = note index (0-63) + lda {{^FREQZ}},y + sta {{base note}},x + lda {{^DUTYZ}},y + sta {{base duty}},x + lda {{^TONEZ}},y + sta AUDC0,x +; TODO: consts? + lda {{get MusicPlayer:tempo}} + sta {{base duration}},x + lda {{get MusicPlayer:volume}} + sta AUDV0,x + --- + on playmusic do foreach [MusicPlayer] limit 1 + --- + lda #<{{arg 0}} + sta {{set songptr 0}} + lda #>{{arg 0}} + sta {{set songptr 8}} + lda #0 + sta {{set timer}} + --- + on stopmusic do foreach [MusicPlayer] limit 1 + --- + lda #$ff + sta {{set timer}} + --- +end + +/// + +demo music + using FrameLoop + using MusicPlayer + entity [MusicChannel] end + entity [MusicChannel] end + entity MusicPlayer [MusicPlayer] + const volume = 10 + const tempo = 31 + end + system music + on musicdone do with [MusicPlayer] + --- + ; TODO: nested exprs + ; {{!playmusic ^SampleMusic}} + ;{{^SampleMusic}} + {{!playmusic SampleMusic}} + --- + on preframeloop do once + --- + {{!musicdone}} + --- + end +end demo + +resource SampleMusic --- + .byte $35,$41,$8a,$37,$43,$8a,$33,$3f,$8a,$30,$3c,$94,$3e,$32,$8a,$3a,$2e,$94,$35,$29,$8a,$37,$2b,$8a,$33,$27,$8a,$30,$24,$94,$32,$26,$8a,$2e,$22,$94,$29,$1d,$8a,$2b,$1f,$8a,$27,$1b,$8a,$24,$18,$94,$1a,$26,$8a,$18,$24,$8a,$17,$23,$8a,$16,$22,$a8,$3a,$35,$ff +--- + diff --git a/presets/vcs/ecs/random.ecs b/presets/vcs/ecs/random.ecs new file mode 100644 index 00000000..a4c82f9e --- /dev/null +++ b/presets/vcs/ecs/random.ecs @@ -0,0 +1,35 @@ + +component Random8 + seed: 0..255 default 1 // TODO: default = 1, or seed? +end + +system Random + on nextrand8 do foreach [Random8] + --- + lda {{{{^FontTable}} + adc #0 + sta Digit0+1,x ; store pointer hi byte + inx + inx ; next bitmap pointer + lda @BCD0,y ; get BCD value (again) + and #$f ; isolate low nibble + asl + asl + asl ; * 8 + clc + adc #<{{^FontTable}} + sta Digit0,x ; store pointer lo byte + lda #>{{^FontTable}} + adc #0 + sta Digit0+1,x ; store pointer hi byte + inx + inx ; next bitmap pointer + dey ; next BCD value + bpl @Loop ; repeat until < 0 +--- + on kernel do with [BCDScore6,PFColor] +--- + lda {{ [GRP0] + lda (Digit1),y ; load B1 -> A + sta GRP1 ; B1 -> [GRP1], B0 -> GRP0 + sta WSYNC ; sync to next scanline + lda (Digit2),y ; load B2 -> A + sta GRP0 ; B2 -> [GRP0], B1 -> GRP1 + lda (Digit5),y ; load B5 -> A + sta @Temp ; B5 -> temp + lda (Digit4),y ; load B4 + tax ; -> X + lda (Digit3),y ; load B3 -> A + ldy @Temp ; load B5 -> Y + sta GRP1 ; B3 -> [GRP1]; B2 -> GRP0 + stx GRP0 ; B4 -> [GRP0]; B3 -> GRP1 + sty GRP1 ; B5 -> [GRP1]; B4 -> GRP0 + sta GRP0 ; ?? -> [GRP0]; B5 -> GRP1 + dec @LoopCount ; go to next line + bpl :- ; repeat until < 0 + + lda #0 ; clear the sprite registers + sta WSYNC + sta GRP0 + sta GRP1 + sta GRP0 + sta GRP1 + sta COLUBK +--- +end + + +resource FontTable --- +; Font table for digits 0-9 (8x8 pixels) +;;{w:8,h:8,count:10,brev:1,flip:1};; + .byte $00,$3c,$66,$66,$76,$6e,$66,$3c,$00,$7e,$18,$18,$18,$38,$18,$18 + .byte $00,$7e,$60,$30,$0c,$06,$66,$3c,$00,$3c,$66,$06,$1c,$06,$66,$3c + .byte $00,$06,$06,$7f,$66,$1e,$0e,$06,$00,$3c,$66,$06,$06,$7c,$60,$7e + .byte $00,$3c,$66,$66,$7c,$60,$66,$3c,$00,$18,$18,$18,$18,$0c,$66,$7e + .byte $00,$3c,$66,$66,$3c,$66,$66,$3c,$00,$3c,$66,$06,$3e,$66,$66,$3c +--- + +system Kernel2Digit + locals 3 + on preframe do once +--- + lda #0 + sta {{$1}} + sta {{$2}} +--- + on kernel do select [BCDScore2] +--- + lda #$02 + sta CTRLPF +; TODO: should be constants +; and it's wrong, too! + lda {{1 + inx + {{!compute2digit 1}} +.else + {{!compute2digit 0}} +.endif +; playfield + dec {{$0}} + jpl @Loop +; dex +; stx PF1 +--- + on compute2digit do once +--- + lda {{$1}} ; load 1st pf + sta PF1 ; store 1st pf +; first digit + lda {{{{arg 0}} +.endif +--- + on AddBCD4 do with [BCDScore6] +--- +; Adds value to 6-BCD-digit score. +; A = 1st BCD digit +; Y = 2nd BCD digit + sed ; enter BCD mode + clc ; clear carry + adc {{get digits}} + sta {{set digits}} + tya + adc {{get digits 8}} + sta {{set digits 8}} + lda {{get digits 16}} + adc #0 + sta {{set digits 16}} + cld ; exit BCD mode +--- + on AddBCD2 do once +--- +.ifnblank {{arg 0}} + lda #<{{arg 0}} +.endif +--- + on SubBCD2 do once +--- +.ifnblank {{arg 0}} + lda #<{{arg 0}} +.endif +--- + on AddBCD2 do with [BCDScore2] +--- + sed ; enter BCD mode + clc ; clear carry + adc {{get digits}} + sta {{set digits}} + cld ; exit BCD mode + bcc :+ + lda #$99 + sta {{set digits}} +: +--- + on SubBCD2 do with [BCDScore2] +--- +; TODO? + tay + lda {{get digits}} + sty {{set digits}} + sed ; enter BCD mode + sec ; set carry + sbc {{get digits}} + sta {{set digits}} + cld ; exit BCD mode + bcs :+ + lda #0 + sta {{set digits}} +: +--- +end + +demo Main + + using FrameLoop + using Kernel6Digit + using Kernel2Digit + using JoyButton, BCDMath + + entity [Player,BCDScore6,PFColor,BGColor] + init digits = 0x123456 + init pfcolor = $3c + init bgcolor = $02 + end + + entity [BCDScore2] + init digits = 0x24 + init scorecolor = $ce + end + entity [BCDScore2] + init digits = 0x56 + init scorecolor = $3e + end + + system IncScore + on joybutton do with [Player,BCDScore6] +--- + {{!AddBCD4 $0210}} +--- + end +end demo + diff --git a/presets/vcs/ecs/sound.ecs b/presets/vcs/ecs/sound.ecs new file mode 100644 index 00000000..283751a7 --- /dev/null +++ b/presets/vcs/ecs/sound.ecs @@ -0,0 +1,105 @@ + +//#resource "vcs-ca65.h" + +import "vcslib.ecs" + +component SoundEffect + duration: 0..255 + sounddata: array of 0..255 +end + +component SoundChannel + sfx: [SoundEffect] + timer: 0..255 +end + +// TODO +component SoundPriority + priority: 0..15 +end + +system SoundEngine + locals 3 + on preframe do + join [SoundChannel] + with [SoundEffect] + --- + lda {{base timer}},y + jeq @nosound + sec + sbc #1 + sta {{base timer}},y + pha + lda {{sounddata}} + sta {{$1}} ; save pointer to sound data + sty {{$2}} ; save Y (sound channel #) + pla + tay + lda ({{$0}}),y ; get sound data + bpl @setfreq ; hi bit clear = just freq + ldy {{$2}} + lsr ; right shift (/ 2) + bcs @setvol ; lo bit set = volume + sta AUDC0,y ; lo bit clear = control + lsr ; also set freq (/ 2) +@setfreq: + ldy {{$2}} + sta AUDF0,y ; set frequency + jmp @done +@nosound: + lda #0 +@setvol: + sta AUDV0,y ; set volume +@done: + --- + // TODO: need to pass sound entity as arg + on playsound do select [SoundChannel] + --- +; arg 0 = sound channel + ldy #{{arg 0}} +; arg 1 = sound effect # + lda #{{arg 1}} + sta {{base sfx}},y + tax + --- + // TODO: shouldn't need to split up like this... + on playsound do select [SoundEffect] + --- + lda {{base duration}},x + --- + on playsound do select [SoundChannel] + --- + sta {{base timer}},y +; arg 2 = base volume + lda #{{arg 2}} + sta AUDV0,y + --- +end + +// TODO: default entities? + + +demo SoundDemo + using FrameLoop, SoundEngine + + entity SFXScore [SoundEffect] + const duration = 11 + const sounddata = [ + $02,$03,$04,$08,$10,$20,$10,$20,$10,$08, + $a8] + end + + // TODO: make sfx have priority? + entity SFX1 [SoundChannel] end + entity SFX2 [SoundChannel] end + + system Test + on preframeloop do once + --- + {{!playsound 0 0 15}} + --- + end +end demo + diff --git a/presets/vcs/ecs/sprites.ecs b/presets/vcs/ecs/sprites.ecs new file mode 100644 index 00000000..40adb7a9 --- /dev/null +++ b/presets/vcs/ecs/sprites.ecs @@ -0,0 +1,177 @@ + +//#resource "vcs-ca65.h" + +import "vcslib.ecs" + +component Bitmap + bitmapdata: array of 0..255 baseoffset 31 + height: 0..255 +end + +// TODO: remove? +component HasBitmap +end + +component Colormap + colormapdata: array of 0..255 baseoffset 31 +end + +component HasColormap + colormap: [Colormap] +end + +component Sprite + bitmap: [Bitmap] + plyrflags: 0..63 +end + +component HasXpos + xpos: 0..255 +end + +component HasYpos + ypos: 0..255 +end + +component Missile + index: 2..3 +end + +component SpriteSlot + sprite: [Sprite] // TODO: HasBitmap? +end + +system JoyFaceDirection + on joyleft do with [Sprite] +--- + lda {{ normal: ABCDEFGHIJKLMNOPQRSTABCDEFGHIJKLMNOPQRST +PF2 := $0F ; TSRQ PONM / reflect: ABCDEFGHIJKLMNOPQRSTTSRQPONMLKJIHGFEDCBA +RESP0 := $10 ; ---- ---- \ +RESP1 := $11 ; ---- ---- \ +RESM0 := $12 ; ---- ---- > reset players, missiles and the ball. The object will begin its serial graphics at the time of a horizontal line at which the reset address occurs. +RESM1 := $13 ; ---- ---- / +RESBL := $14 ; ---- ---- / +AUDC0 := $15 ; ---- 3210 audio control voice 0 +AUDC1 := $16 ; ---- 3210 audio control voice 1 +AUDF0 := $17 ; ---4 3210 frequency divider voice 0 +AUDF1 := $18 ; ---4 3210 frequency divider voice 1 +AUDV0 := $19 ; ---- 3210 audio volume voice 0 +AUDV1 := $1A ; ---- 3210 audio volume voice 1 +GRP0 := $1B ; 7654 3210 graphics player 0 +GRP1 := $1C ; 7654 3210 graphics player 1 +ENAM0 := $1D ; ---- --1- enable missile 0 +ENAM1 := $1E ; ---- --1- enable missile 1 +ENABL := $1F ; ---- --1- enable ball +HMP0 := $20 ; 7654 ---- write data (horizontal motion values) into the horizontal motion registers +HMP1 := $21 ; 7654 ---- write data (horizontal motion values) into the horizontal motion registers +HMM0 := $22 ; 7654 ---- write data (horizontal motion values) into the horizontal motion registers +HMM1 := $23 ; 7654 ---- write data (horizontal motion values) into the horizontal motion registers +HMBL := $24 ; 7654 ---- write data (horizontal motion values) into the horizontal motion registers +VDELP0 := $25 ; ---- ---0 delay player 0 by one vertical line +VDELP1 := $26 ; ---- ---0 delay player 1 by one vertical line +VDELBL := $27 ; ---- ---0 delay ball by one vertical line +RESMP0 := $28 ; ---- --1- keep missile 0 aligned with player 0 +RESMP1 := $29 ; ---- --1- keep missile 1 aligned with player 1 +HMOVE := $2A ; ---- ---- This address causes the horizontal motion register values to be acted upon during the horizontal blank time in which it occurs. +HMCLR := $2B ; ---- ---- This address clears all horizontal motion registers to zero (no motion). +CXCLR := $2C ; ---- ---- clears all collision latches + +; TIA read registers + +CXM0P := $00 ; xx00 0000 Read Collision M0-P1 M0-P0 +CXM1P := $01 ; xx00 0000 M1-P0 M1-P1 +CXP0FB := $02 ; xx00 0000 P0-PF P0-BL +CXP1FB := $03 ; xx00 0000 P1-PF P1-BL +CXM0FB := $04 ; xx00 0000 M0-PF M0-BL +CXM1FB := $05 ; xx00 0000 M1-PF M1-BL +CXBLPF := $06 ; x000 0000 BL-PF ----- +CXPPMM := $07 ; xx00 0000 P0-P1 M0-M1 +INPT0 := $08 ; x000 0000 Read Pot Port 0 +INPT1 := $09 ; x000 0000 Read Pot Port 1 +INPT2 := $0A ; x000 0000 Read Pot Port 2 +INPT3 := $0B ; x000 0000 Read Pot Port 3 +INPT4 := $0C ; x000 0000 Read Input (Trigger) 0 +INPT5 := $0D ; x000 0000 Read Input (Trigger) 1 + +; RIOT + +SWCHA := $0280 +SWACNT := $0281 +SWCHB := $0282 +SWBCNT := $0283 +INTIM := $0284 ; Timer output +TIMINT := $0285 + +TIM1T := $0294 +TIM8T := $0295 +TIM64T := $0296 +TIM1024T := $0297 + +;------------------------------------------------------------------------------- +; SLEEP duration +; Original author: Thomas Jentzsch +; Inserts code which takes the specified number of cycles to execute. This is +; useful for code where precise timing is required. +; ILLEGAL-OPCODE VERSION DOES NOT AFFECT FLAGS OR REGISTERS. +; LEGAL OPCODE VERSION MAY AFFECT FLAGS +; Uses illegal opcode (DASM 2.20.01 onwards). + +.macro SLEEP cycles +.if cycles < 0 || cycles = 1 +.error "MACRO ERROR: 'SLEEP': Duration must be >= 2" +.endif +.if cycles & 1 +.ifndef NO_ILLEGAL_OPCODES + nop 0 +.else + bit VSYNC +.endif +.repeat (cycles-3)/2 + nop +.endrep +.else +.repeat cycles/2 + nop +.endrep +.endif +.endmacro + +;------------------------------------------------------------------------------- +; VERTICAL_SYNC +; revised version by Edwin Blink -- saves bytes! +; Inserts the code required for a proper 3 scanline vertical sync sequence +; Note: Alters the accumulator + +; OUT: A = 0 + +.macro VERTICAL_SYNC + lda #%1110 ; each '1' bits generate a VSYNC ON line (bits 1..3) +: sta WSYNC ; 1st '0' bit resets Vsync, 2nd '0' bit exit loop + sta VSYNC +.ifdef VERTICAL_SYNC_MACRO + pha + VERTICAL_SYNC_MACRO + pla +.endif + lsr + bne :- ; branch until VYSNC has been reset +.endmacro + +;------------------------------------------------------- +; Usage: TIMER_SETUP lines +; where lines is the number of scanlines to skip (> 2). +; The timer will be set so that it expires before this number +; of scanlines. A WSYNC will be done first. + +.macro TIMER_SETUP lines +.local cycles +cycles = ((lines * 76) - 13) +; special case for when we have two timer events in a line +; and our 2nd event straddles the WSYNC boundary + .if (cycles .mod 64) < 12 + lda #(cycles / 64) - 1 + sta WSYNC + .else + lda #(cycles / 64) + sta WSYNC + .endif + sta TIM64T +.endmacro + +;------------------------------------------------------- +; Use with TIMER_SETUP to wait for timer to complete. +; Performs a WSYNC afterwards. + +.macro TIMER_WAIT +.local waittimer +waittimer: + lda INTIM + bne waittimer + sta WSYNC +.endmacro + +;------------------------------------------------------------------------------- +; CLEAN_START +; Original author: Andrew Davie +; Standardised start-up code, clears stack, all TIA registers and RAM to 0 +; Sets stack pointer to $FF, and all registers to 0 +; Sets decimal mode off, sets interrupt flag (kind of un-necessary) +; Use as very first section of code on boot (ie: at reset) +; Code written to minimise total ROM usage - uses weird 6502 knowledge :) + +.macro CLEAN_START +.local CLEAR_STACK + sei + cld + ldx #0 + txa + tay +CLEAR_STACK: dex + txs + pha + bne CLEAR_STACK ; SP=$FF, X = A = Y = 0 +.endmacro + +;------------------------------------------------------- +; SET_POINTER +; Original author: Manuel Rotschkar +; +; Sets a 2 byte RAM pointer to an absolute address. +; +; Usage: SET_POINTER pointer, address +; Example: SET_POINTER SpritePTR, SpriteData +; +; Note: Alters the accumulator, NZ flags +; IN 1: 2 byte RAM location reserved for pointer +; IN 2: absolute address +.macro SET_POINTER ptr, addr + lda #addr + sta ptr+1 +.endmacro + + +; assume NTSC unless PAL defined +.ifndef PAL +PAL = 0 +.endif + +; 192 visible scanlines for NTSC, 228 for PAL +.if PAL +SCANLINES = 228 +LINESD12 = 19 +.else +SCANLINES = 192 +LINESD12 = 16 +.endif + +; start of frame -- vsync and set back porch timer +.macro FRAME_START + VERTICAL_SYNC + .if PAL + TIMER_SETUP 44 + .else + TIMER_SETUP 36 + .endif +.endmacro + +; end of back porch -- start kernel +.macro KERNEL_START + TIMER_WAIT + lda #0 + sta VBLANK + .if !PAL + TIMER_SETUP 194 + .endif +.endmacro + +; end of kernel -- start front porch timer +.macro KERNEL_END + .if !PAL + TIMER_WAIT + .endif + lda #2 + sta VBLANK + .if PAL + TIMER_SETUP 36 + .else + TIMER_SETUP 28 + .endif +.endmacro + +; end of frame -- jump to frame start +.macro FRAME_END + TIMER_WAIT +.endmacro + +;----------------------------------------------------------- +; SLEEPR - sleep macro that uses JSR/RTS for 12 cycle delays +; Requires a lone RTS instruction with the label "Return" +; (note: may fool 8bitworkshop's Anaylze CPU Timing feature) + +.macro SLEEPR cycles +.if cycles >= 14 || cycles = 12 + jsr Return + SLEEPR (cycles-12) +.else + SLEEP cycles +.endif +.endmacro + +;----------------------------------------------------------- +; SLEEPH - sleep macro that uses PHA/PLA for 12 cycle delays + +.macro SLEEPH cycles +.if cycles >= 9 || cycles = 7 + pha + pla + SLEEPH (cycles-7) +.else + SLEEP cycles +.endif +.endmacro + diff --git a/presets/vcs/ecs/vcslib.ecs b/presets/vcs/ecs/vcslib.ecs new file mode 100644 index 00000000..eb1a6965 --- /dev/null +++ b/presets/vcs/ecs/vcslib.ecs @@ -0,0 +1,336 @@ + +//#resource "vcs-ca65.h" + +system Init + on main_init do once +--- +.include "vcs-ca65.h" +.macpack longbranch +.define PAL 0 +__NMI: +__Reset: +__BRK: + CLEAN_START +{{!start}} ; start main routine +.segment "VECTORS" +ZeroByte: .byte $00 +Return: .byte $60 +VecNMI: +VecReset: .word __Reset +VecBRK: .word __BRK +.code +--- +end + +component Player +end + +component KernelSection + lines: 1..255 default 1 +end + +component BGColor + bgcolor: 0..255 +end + +component FGColor + fgcolor: 0..255 +end + +component PFColor + pfcolor: 0..255 +end + +component Playfield + pf: 0..0xffffff +end + +component AsymPlayfield + pfleft: 0..0xffffff + pfright: 0..0xffffff +end + +system FrameLoop + on start do once +--- + {{emit preframeloop}} +@NextFrame: + FRAME_END + {{emit prevsync}} + FRAME_START + {{emit preframe}} + {{emit prekernel}} + KERNEL_START + {{emit kernel}} + KERNEL_END + {{emit postkernel}} + {{emit postframe}} + jmp @NextFrame ; loop to next frame +--- + on postframe do once +--- + lsr SWCHB ; test Game Reset switch + bcs @NoStart + {{emit resetswitch}} +@NoStart: +--- +end + +system ResetConsole + on resetswitch do once +--- + jmp Main::__Reset ; jump to Reset handler +--- +end + +system JoyButton + on postframe do foreach [Player] +--- + lda {{index INPT4}} ;read button input + bmi @NotPressed + {{emit joybutton}} +@NotPressed: +--- +end + +system Joystick + locals 1 + on postframe do once +--- +; 2 control inputs share a single byte, 4 bits each + lda SWCHA + sta {{$0}} +--- + on postframe do foreach [Player] limit 2 +--- + asl {{$0}} +.ifdef EVENT__joyright + bcs @SkipMoveRight + {{!joyright}} +@SkipMoveRight: +.endif + asl {{$0}} +.ifdef EVENT__joyleft + bcs @SkipMoveLeft + {{!joyleft}} +@SkipMoveLeft: +.endif + asl {{$0}} +.ifdef EVENT__joydown + bcs @SkipMoveDown + {{!joydown}} +@SkipMoveDown: +.endif + asl {{$0}} +.ifdef EVENT__joyup + bcs @SkipMoveUp + {{!joyup}} +@SkipMoveUp: +.endif +--- +end + +system SetHorizPos + on SetHorizPos do critical fit 22 once +--- +; SetHorizPos routine +; A = X coordinate +; Y = player number (0 or 1) + sec ; set carry flag + sta WSYNC ; start a new line +: + sbc #15 ; subtract 15 + bcs :- ; branch until negative + eor #7 ; calculate fine offset + asl + asl + asl + asl + sta HMP0,y ; set fine offset + sta RESP0,y ; fix coarse position + sta WSYNC ; won't overrun if X < 150 +--- +end + + +system StandardKernel + on wsync do once +--- + sta WSYNC +--- + on preframe do foreach [KernelSection] limit 1 +--- + {{!wsync}} + {{!kernelsetup}} +--- + on kernel do foreach [KernelSection] +--- + {{!wsync}} + {{!kernelsetup}} + {{!kerneldraw}} + {{!kerneldone}} +--- + on kerneldraw do with [KernelSection] +--- + ldy {{data}} + sta {{$1}} +--- + on scanline do once +--- +.if {{arg 0}} = 0 + lda ({{local 0}}),y + tax +.endif +--- + on scanline do once +--- +.if {{arg 0}} = 1 + lda ({{local 0}}),y + sta $00,x +.endif +--- + on postframe do once +--- + lda #0 + sta PF0 + sta PF1 + sta PF2 +--- + on checkplatform do with [HasXpos,HasYpos] + --- + lda {{= 9 || cycles = 7 + pha + pla + SLEEPH (cycles-7) +.else + SLEEP cycles +.endif +.endmacro diff --git a/src/codemirror/ecs.js b/src/codemirror/ecs.js new file mode 100644 index 00000000..b65fdc2f --- /dev/null +++ b/src/codemirror/ecs.js @@ -0,0 +1,117 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function (mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function (CodeMirror) { + "use strict"; + + // 6502 DASM syntax + + CodeMirror.defineMode('ecs', function (_config, parserConfig) { + var keywords1, keywords2; + + var directives_list = [ + 'begin', 'end', + 'component', 'system', 'entity', 'scope', 'using', 'demo', 'decode', 'resource', + 'const', 'locals', 'var', + 'enum', 'default', 'array', 'baseoffset', 'critical', 'fit', 'asc', 'desc', + 'on', 'do', 'emit', 'limit', + 'once', 'foreach', 'with', 'join', 'if', 'select', 'unroll', + ]; + var keywords_list = [ + 'processor', + 'byte', 'word', 'long', + 'include', 'seg', 'dc', 'ds', 'dv', 'hex', 'err', 'org', 'rorg', 'echo', 'rend', + 'align', 'subroutine', 'equ', 'eqm', 'set', 'mac', 'endm', 'mexit', 'ifconst', + 'ifnconst', 'else', 'endif', 'eif', 'repeat', 'repend' + ]; + + var directives = new Map(); + directives_list.forEach(function (s) { directives.set(s, 'keyword'); }); + keywords_list.forEach(function (s) { directives.set(s, 'def'); }); + + var opcodes = /^\s[a-z][a-z][a-z]\s/i; + var numbers = /^(0x[\da-f]+|[$][\da-f]+|[\da-f]+h|[0-7]+o|[01]+b|\d+d?)\b/i; + var tags = /^\{\{.*\}\}/; + var comment = /^\/\/.*$/; + var mlcomment = /^\/\*.*?\*\//s; // TODO + var codedelim = /^---/; + + return { + startState: function () { + return { + context: 0 + }; + }, + token: function (stream, state) { + if (stream.eatSpace()) + return null; + + if (stream.match(tags)) { + return 'meta'; + } + if (stream.match(comment)) { + return 'comment'; + } + if (stream.match(mlcomment)) { + return 'comment'; + } + if (stream.match(codedelim)) { + state.context = state.context ^ 1; + return null; + } + + var w; + if (stream.eatWhile(/\w/)) { + w = stream.current(); + var cur = w.toLowerCase(); + var style = directives.get(cur); + if (style) + return style; + + if (numbers.test(w)) { + return 'number'; + } else if (w == 'comment') { + stream.match(mlcomment); + return 'comment'; + } else { + return state.context ? 'variable-2' : null; + } + } else if (stream.eat(';')) { + stream.skipToEnd(); + return 'comment'; + } else if (stream.eat('"')) { + while (w = stream.next()) { + if (w == '"') + break; + + if (w == '\\') + stream.next(); + } + return 'string'; + } else if (stream.eat('\'')) { + if (stream.match(/\\?.'/)) + return 'number'; + } else if (stream.eat('$') || stream.eat('#')) { + if (stream.eatWhile(/[^;]/i)) + return 'number'; + } else if (stream.eat('%')) { + if (stream.eatWhile(/[01]/)) + return 'number'; + } else { + stream.next(); + } + return null; + } + }; + }); + + CodeMirror.defineMIME("text/x-ecs", "ecs"); + +}); diff --git a/src/common/baseplatform.ts b/src/common/baseplatform.ts index 9c8190d7..6f6483b3 100644 --- a/src/common/baseplatform.ts +++ b/src/common/baseplatform.ts @@ -223,7 +223,10 @@ export abstract class BasePlatform { return inspectSymbol((this as any) as Platform, sym); } getDebugTree() : {} { - return this.saveState(); + var o : any = { }; + o.state = this.saveState(); + if (this.debugSymbols?.debuginfo) o.debuginfo = this.debugSymbols.debuginfo; + return o; } readFile(path: string) : FileData { return this.internalFiles[path]; @@ -432,6 +435,7 @@ export function getToolForFilename_6502(fn:string) : string { if (fn.endsWith(".dasm")) return "dasm"; if (fn.endsWith(".acme")) return "acme"; if (fn.endsWith(".wiz")) return "wiz"; + if (fn.endsWith(".ecs")) return "ecs"; return "dasm"; // .a } diff --git a/src/common/basic/compiler.ts b/src/common/basic/compiler.ts index f0770bb7..9f9a8a05 100644 --- a/src/common/basic/compiler.ts +++ b/src/common/basic/compiler.ts @@ -1,4 +1,4 @@ -import { WorkerError, CodeListingMap, SourceLocation, SourceLine } from "../workertypes"; +import { WorkerError, CodeListingMap, SourceLocation, SourceLine, SourceLocated, SourceLineLocated } from "../workertypes"; export interface BASICOptions { dialectName : string; // use this to select the dialect @@ -50,15 +50,6 @@ export interface BASICOptions { maxArrayElements? : number; // max array elements (all dimensions) } -// objects that have source code position info -export interface SourceLocated { - $loc?: SourceLocation; -} -// statements also have the 'offset' (pc) field from SourceLine -export interface SourceLineLocated { - $loc?: SourceLine; -} - export class CompileError extends Error { $loc : SourceLocation; constructor(msg: string, loc: SourceLocation) { diff --git a/src/common/ecs/README.md b/src/common/ecs/README.md new file mode 100644 index 00000000..cdb2b71d --- /dev/null +++ b/src/common/ecs/README.md @@ -0,0 +1,213 @@ + +# NOTES + +entity scopes contain entities, and are nested +also contain segments (code, bss, rodata) +components and systems are global +component fields are stored in arrays, range of entities, can be bit-packed +some values can be constant, are stored in rodata (or loaded immediate) +optional components? on or off +union components? either X or Y or Z... + +systems receive and send events, execute code on entities +systems are generated on a per-scope basis +system queries can only contain entities from self and parent scopes +starting from the 'init' event walk the event tree +include systems that have at least 1 entity in scope (except init?) + +when entering scope, entities are initialized (zero or init w/ data) +to change scope, fire event w/ scope name +- how to handle bank-switching? + +helps with: +- rapid prototyping w/ reasonable defaults +- deconstructing objects into arrays +- packing/unpacking bitfields +- initializing objects +- building lookup tables +- selecting and iterating objects +- managing events +- managing memory and scope +- converting assets to native formats? +- removing unused data + +it's more convenient to have loops be zero-indexed +for page cross, temp storage, etc +should references be zero-indexed to a field, or global? +should we limit # of entities passed to systems? min-max +join thru a reference? load both x and y + +code fragments can be parameterized like macros +if two fragments are identical, do a JSR +(do we have to look for labels?) +should events have parameters? e.g. addscore X Y Z +how are Z80 arrays working? +https://forums.nesdev.org/viewtopic.php?f=20&t=14691 +https://www.cpcwiki.eu/forum/programming/trying-not-to-use-ix/msg133416/#msg133416 + +how to select two between two entities with once? like scoreboard +maybe stack-based interpreter? + +can you query specific entities? merge with existing queries? +bigints? +source/if query? + +only worry about intersection when non-contiguous ranges? + +crazy idea -- full expansion, then relooper + +how to avoid cycle crossing for critical code and data? bin packing + +system define order, action order, entity order, using order? +what happens when a system must be nested inside another? like display kernels + +constants? (NTSC vs PAL) + +set operations: + +E = select entities from scope +A intersect B +A join B +loop over E limit N asc/desc +select Nth from E +run if A intersects B (if) + + +virtual machine +- entityset stack +- register states + + +entity Foo[Bar] { } + +system FrameLoop + +end + +class StaticKernel + locals 12 + func init --- + lda {{$0}} + sta {{$1}} + --- + func display --- + lda {{$0}} + sta {{$1}} + --- +end + +Game { + superman: [Sprite, ...] { + var xpos=12 + } + FrameLoop { + a: StaticKernel(lines=30,bgcolor=$30) + b: Kernel48(...) + c: StaticKernel(...) + + on preframe { + } + on display { + } + on postframe { + } + } +} + + +systems and scope same thing? +nested systems? +systems allocated in blocks +entities allocated in arrays, take up 1 or more blocks +mid-level abstraction for scopes/macros/(banks?) + + + +Init +with FrameLoop do + with StaticKernel do + end + ResetSwitch: + on reset do ResetConsole + end + StaticKernel: + end + JoyButton: + end + end +end + + +scopes are banks! +banks need to duplicate code and/or rodata +- don't split critical code across banks +need bank trampoline macro +nested scopes for game modes? (title / demo / play) +access parent data from child scope + +critical data fields +if accessed in critical section, make critical +ignore arrays that aren't referenced + +use DASM for multipass? + +processes +take up at least one byte if stateful +might need free list +need jump table? +you'd like to change "mode" from any event + +need constant folding, set arrays from other exprs + +a = [Sprite,-Player] +foreach a do begin + xpos = ypos +end + + on gowest do with x:[Location] +--- + ldy {{= a.right || b.right <= a.left || b.top >= a.bottom || b.bottom <= a.top); +} + +function boxesContain(a: Box, b: Box) : boolean { + return b.left >= a.left && b.top >= a.top && b.right <= a.right && b.bottom <= a.bottom; +} + +export class Bin { + boxes: Box[] = []; + free: Box[] = []; + extents: Box = {left:0,top:0,right:0,bottom:0}; + + constructor(public readonly binbounds: Box) { + this.free.push(binbounds); + } + getBoxes(bounds: Box, limit: number, boxes?: Box[]) : Box[] { + let result = []; + if (!boxes) boxes = this.boxes; + for (let box of boxes) { + //console.log(bounds, box, boxesIntersect(bounds, box)) + if (boxesIntersect(bounds, box)) { + result.push(box); + if (result.length >= limit) break; + } + } + return result; + } + fits(b: Box) { + if (!boxesContain(this.binbounds, b)) { + if (debug) console.log('out of bounds!', b.left,b.top,b.right,b.bottom); + return false; + } + if (this.getBoxes(b, 1).length > 0) { + if (debug) console.log('intersect!', b.left,b.top,b.right,b.bottom); + return false; + } + return true; + } + bestFit(b: BoxConstraints) : Box | null { + let bestscore = 0; + let best = null; + for (let f of this.free) { + if (b.left != null && b.left < f.left) continue; + if (b.left != null && b.left + b.width > f.right) continue; + if (b.top != null && b.top < f.top) continue; + if (b.top != null && b.top + b.height > f.bottom) continue; + let dx = (f.right - f.left) - b.width; + let dy = (f.bottom - f.top) - b.height; + if (dx >= 0 && dy >= 0) { + let score = 1 / (1 + dx + dy + f.left * 0.001); + if (score > bestscore) { + best = f; + bestscore = score; + if (score == 1) break; + } + } + } + return best; + } + anyFit(b: BoxConstraints) : Box | null { + let bestscore = 0; + let best = null; + for (let f of this.free) { + let box : Box = { + left: b.left != null ? b.left : f.left, + right: f.left + b.width, + top: b.top != null ? b.top : f.top, + bottom: f.top + b.height }; + if (this.fits(box)) { + let score = 1 / (1 + box.left + box.top); + if (score > bestscore) { + best = f; + if (score == 1) break; + } + } + } + return best; + } + add(b: PlacedBox) { + if (debug) console.log('add', b.left,b.top,b.right,b.bottom); + if (!this.fits(b)) { + //console.log('collided with', this.getBoxes(b, 1)); + throw new Error(`bad fit ${b.left} ${b.top} ${b.right} ${b.bottom}`) + } + // add box to list + this.boxes.push(b); + this.extents.right = Math.max(this.extents.right, b.right); + this.extents.bottom = Math.max(this.extents.bottom, b.bottom); + // delete bin + for (let p of b.parents) { + let i = this.free.indexOf(p); + if (i < 0) throw new Error('cannot find parent'); + if (debug) console.log('removed',p.left,p.top,p.right,p.bottom); + this.free.splice(i, 1); + // split into new bins + // make long columns + this.addFree(p.left, p.top, b.left, p.bottom); + this.addFree(b.right, p.top, p.right, p.bottom); + // make top caps + this.addFree(b.left, p.top, b.right, b.top); + this.addFree(b.left, b.bottom, b.right, p.bottom); + } + } + addFree(left: number, top: number, right: number, bottom: number) { + if (bottom > top && right > left) { + let b = { left, top, right, bottom }; + if (debug) console.log('free',b.left,b.top,b.right,b.bottom); + this.free.push(b); + } + // TODO: merge free boxes? + } +} + +export class Packer { + bins : Bin[] = []; + boxes : BoxConstraints[] = []; + defaultPlacement : BoxPlacement = BoxPlacement.TopLeft; //TODO + + pack() : boolean { + for (let bc of this.boxes) { + let box = this.bestPlacement(bc); + if (!box) return false; + box.bin.add(box); + bc.box = box; + } + return true; + } + bestPlacement(b: BoxConstraints) : PlacedBox | null { + for (let bin of this.bins) { + let parent = bin.bestFit(b); + let approx = false; + if (!parent) { + parent = bin.anyFit(b); + approx = true; + if (debug) console.log('anyfit',parent?.left,parent?.top); + } + if (parent) { + let place = this.defaultPlacement; + let box = { + left: parent.left, + top: parent.top, + right: parent.left + b.width, + bottom: parent.top + b.height + }; + if (b.left != null) { + box.left = b.left; + box.right = b.left + b.width; + } + if (b.top != null) { + box.top = b.top; + box.bottom = b.top + b.height; + } + if (place == BoxPlacement.BottomLeft || place == BoxPlacement.BottomRight) { + let h = box.bottom - box.top; + box.top = parent.bottom - h; + box.bottom = parent.bottom; + } + if (place == BoxPlacement.TopRight || place == BoxPlacement.BottomRight) { + let w = box.right - box.left; + box.left = parent.right - w; + box.right = parent.right; + } + if (debug) console.log('place',b.label,box.left,box.top,box.right,box.bottom,parent?.left,parent?.top); + let parents = [parent]; + // if approx match, might overlap multiple free boxes + if (approx) parents = bin.getBoxes(box, 100, bin.free); + return { parents, place, bin, ...box }; + } + } + if (debug) console.log('cannot place!', b.left,b.top,b.width,b.height); + return null; + } + toSVG() { + let s = ''; + let r = {width:100,height:70} + for (let bin of this.bins) { + r.width = Math.max(r.width, bin.binbounds.right); + r.height = Math.max(r.height, bin.binbounds.bottom); + } + s += ``; + for (let bin of this.bins) { + let be = bin.extents; + s += '' + s += ``; + let textx = be.right+1; + let texty = 0; + for (let box of this.boxes) { + let b = box.box; + if (b) { + if (b.bin == bin) s += ``; + if (b.top == texty) textx += 10; else textx = be.right+1; + texty = b.top; + if (box.label) s += `${box.label}`; + } + } + /* + for (let b of bin.free) { + s += ``; + } + */ + s += '' + } + s += ``; + return s; + } + toSVGUrl() { + return `data:image/svg+xml;base64,${btoa(this.toSVG())}`; + } +} diff --git a/src/common/ecs/compiler.ts b/src/common/ecs/compiler.ts new file mode 100644 index 00000000..ae93fe04 --- /dev/null +++ b/src/common/ecs/compiler.ts @@ -0,0 +1,851 @@ + +import { mergeLocs, Token, Tokenizer, TokenType } from "../tokenizer"; +import { SourceLocated, SourceLocation } from "../workertypes"; +import { newDecoder } from "./decoder"; +import { Action, ActionContext, ArrayType, CodeLiteralNode, CodePlaceholderNode, ComponentType, DataField, DataType, DataValue, ECSError, Entity, EntityArchetype, EntityManager, EntityScope, IntType, Query, RefType, SelectType, SELECT_TYPE, SourceFileExport, System, SystemInstance, SystemInstanceParameters, ComponentFieldPair, Expr, ExprBase, ForwardRef, isLiteral, EntityFieldOp, LExpr, Statement, QueryExpr } from "./ecs"; + +export enum ECSTokenType { + Ellipsis = 'ellipsis', + Operator = 'operator', + Relational = 'relational', + QuotedString = 'quoted-string', + Integer = 'integer', + CodeFragment = 'code-fragment', + Placeholder = 'placeholder', +} + +const OPERATORS = { + 'IMP': {f:'bimp',p:4}, + 'EQV': {f:'beqv',p:5}, + 'XOR': {f:'bxor',p:6}, + 'OR': {f:'bor',p:7}, // or "lor" for logical + 'AND': {f:'band',p:8}, // or "land" for logical + '||': {f:'lor',p:17}, // not used + '&&': {f:'land',p:18}, // not used + '=': {f:'eq',p:50}, + '==': {f:'eq',p:50}, + '<>': {f:'ne',p:50}, + '><': {f:'ne',p:50}, + '!=': {f:'ne',p:50}, + '#': {f:'ne',p:50}, + '<': {f:'lt',p:50}, + '>': {f:'gt',p:50}, + '<=': {f:'le',p:50}, + '>=': {f:'ge',p:50}, + 'MIN': {f:'min',p:75}, + 'MAX': {f:'max',p:75}, + '+': {f:'add',p:100}, + '-': {f:'sub',p:100}, +}; + +function getOperator(op: string) { + return (OPERATORS as any)[op]; +} + +function getPrecedence(tok: Token): number { + switch (tok.type) { + case ECSTokenType.Operator: + case ECSTokenType.Relational: + case TokenType.Ident: + let op = getOperator(tok.str); + if (op) return op.p; + } + return -1; +} + +// is token an end of statement marker? (":" or end of line) +function isEOS(tok: Token) { + return tok.type == TokenType.EOL || tok.type == TokenType.Comment + || tok.str == ':' || tok.str == 'ELSE'; // TODO: only ELSE if ifElse==true +} + +/// + +export class ECSCompiler extends Tokenizer { + + currentScope: EntityScope | null = null; + currentContext: ActionContext | null = null; + includeDebugInfo = false; + + constructor( + public readonly em: EntityManager, + public readonly isMainFile: boolean) { + super(); + //this.includeEOL = true; + this.setTokenRules([ + { type: ECSTokenType.Ellipsis, regex: /\.\./ }, + { type: ECSTokenType.QuotedString, regex: /".*?"/ }, + { type: ECSTokenType.CodeFragment, regex: /---.*?---/ }, + { type: ECSTokenType.Integer, regex: /0[xX][A-Fa-f0-9]+/ }, + { type: ECSTokenType.Integer, regex: /\$[A-Fa-f0-9]+/ }, + { type: ECSTokenType.Integer, regex: /[%][01]+/ }, + { type: ECSTokenType.Integer, regex: /\d+/ }, + { type: ECSTokenType.Relational, regex: /[=<>][=<>]?/ }, + { type: ECSTokenType.Operator, regex: /[.#,:(){}\[\]\-\+]/ }, + { type: TokenType.Ident, regex: /[A-Za-z_][A-Za-z0-9_]*/ }, + { type: TokenType.Ignore, regex: /\/\/.*?[\n\r]/ }, + { type: TokenType.Ignore, regex: /\/\*.*?\*\// }, + { type: TokenType.EOL, regex: /[\n\r]+/ }, + { type: TokenType.Ignore, regex: /\s+/ }, + ]); + this.errorOnCatchAll = true; + } + + annotate(fn: () => T) { + let start = this.peekToken(); + let obj = fn(); + let end = this.lasttoken; + let $loc = end ? mergeLocs(start.$loc, end.$loc) : start.$loc; + if (obj) (obj as SourceLocated).$loc = $loc; + return obj; + } + + parseFile(text: string, path: string) { + this.tokenizeFile(text, path); + while (!this.isEOF()) { + let top = this.parseTopLevel(); + if (top) { + let t = top; + this.annotate(() => t); // TODO? typescript bug? + } + } + this.runDeferred(); + } + + getImportFile: (path: string) => string; + + importFile(path: string) { + if (!this.em.imported[path]) { // already imported? + let text = this.getImportFile && this.getImportFile(path); + if (!text) this.compileError(`I can't find the import file "${path}".`); + this.em.imported[path] = true; + let comp = new ECSCompiler(this.em, false); + comp.includeDebugInfo = this.includeDebugInfo; // TODO: clone compiler + try { + comp.parseFile(text, path); + } catch (e) { + for (var err of comp.errors) this.errors.push(err); + throw e; + } + } + } + + parseTopLevel() { + //this.skipBlankLines(); + let tok = this.expectTokens(['component', 'system', 'scope', 'resource', 'import', 'demo', 'comment']); + if (tok.str == 'component') { + return this.em.defineComponent(this.parseComponentDefinition()); + } + if (tok.str == 'system') { + return this.em.defineSystem(this.parseSystem()); + } + if (tok.str == 'scope') { + return this.parseScope(); + } + if (tok.str == 'resource') { + return this.em.defineSystem(this.parseResource()); + } + if (tok.str == 'import') { + let tok = this.expectTokenTypes([ECSTokenType.QuotedString]); + let path = tok.str.substring(1, tok.str.length - 1); + return this.importFile(path); + } + if (tok.str == 'demo') { + if (this.isMainFile) { + let scope = this.parseScope(); + scope.isDemo = true; + this.expectToken('demo'); + return scope; + } else { + this.skipDemo(); // don't even parse it, just skip it + return; + } + } + if (tok.str == 'comment') { + this.expectTokenTypes([ECSTokenType.CodeFragment]); + return; + } + this.compileError(`Unexpected top-level keyword: ${tok.str}`); + } + + skipDemo() { + var tok; + while ((tok = this.consumeToken()) && !this.isEOF()) { + if (tok.str == 'end' && this.peekToken().str == 'demo') { + this.consumeToken(); + return; + } + } + throw new ECSError(`Expected "end demo" after a "demo" declaration.`); + } + + parseComponentDefinition(): ComponentType { + let name = this.expectIdent().str; + let fields = []; + this.em.deferComponent(name); + while (this.peekToken().str != 'end') { + fields.push(this.parseComponentField()); + } + this.expectToken('end'); + return { name, fields }; + } + + parseComponentField(): DataField { + let name = this.expectIdent(); + this.expectToken(':', 'I expected either a ":" or "end" here.'); // TODO + let type = this.parseDataType(); + return { name: name.str, $loc: name.$loc, ...type }; + } + + parseDataType(): DataType { + if (this.peekToken().type == 'integer') { + let lo = this.parseIntegerConstant(); + this.expectToken('..'); + let hi = this.parseIntegerConstant(); + this.checkLowerLimit(lo, -0x80000000, "lower int range"); + this.checkUpperLimit(hi, 0x7fffffff, "upper int range"); + this.checkUpperLimit(hi-lo, 0xffffffff, "int range"); + this.checkLowerLimit(hi, lo, "int range"); + // TODO: use default value? + let defvalue; + if (this.ifToken('default')) { + defvalue = this.parseIntegerConstant(); + } + // TODO: check types + return { dtype: 'int', lo, hi, defvalue } as IntType; + } + if (this.peekToken().str == '[') { + return { dtype: 'ref', query: this.parseQuery() } as RefType; + } + if (this.ifToken('array')) { + let index: IntType | undefined = undefined; + if (this.peekToken().type == ECSTokenType.Integer) { + index = this.parseDataType() as IntType; + } + this.expectToken('of'); + let elem = this.parseDataType(); + let baseoffset; + if (this.ifToken('baseoffset')) { + baseoffset = this.parseIntegerConstant(); + this.checkLowerLimit(baseoffset, -32768, "base offset"); + this.checkUpperLimit(baseoffset, 32767, "base offset"); + } + return { dtype: 'array', index, elem, baseoffset } as ArrayType; + } + if (this.ifToken('enum')) { + this.expectToken('['); + let enumtoks = this.parseList(this.parseEnumIdent, ','); + this.expectToken(']'); + if (enumtoks.length == 0) this.compileError(`must define at least one enum`); + let lo = 0; + let hi = enumtoks.length-1; + this.checkLowerLimit(hi, 0, "enum count"); + this.checkUpperLimit(hi, 255, "enum count"); + let enums : {[name:string]:number} = {}; + for (let i=0; i<=hi; i++) + enums[enumtoks[i].str] = i; + // TODO: use default value? + let defvalue; + if (this.ifToken('default')) { + defvalue = this.parseIntegerConstant(); + } + return { dtype: 'int', lo, hi, defvalue, enums } as IntType; + } + throw this.compileError(`I expected a data type here.`); + } + + parseEnumIdent() { + let tok = this.expectTokenTypes([TokenType.Ident]); + return tok; + } + parseEnumValue(tok: Token, field: IntType) { + if (!field.enums) throw new ECSError(`field is not an enum`); + let value = field.enums[tok.str]; + if (value == null) throw new ECSError(`unknown enum "${tok.str}"`); + return value; + } + + parseDataValue(field: DataField): DataValue | ForwardRef { + let tok = this.peekToken(); + // TODO: move to expr + if (tok.type == TokenType.Ident && field.dtype == 'int') { + return this.parseEnumValue(this.consumeToken(), field); + } + if (tok.type == TokenType.Ident) { + let entity = this.currentScope?.getEntityByName(tok.str); + if (!entity) + this.compileError('no entity named "${tok.str}"'); + else { + this.consumeToken(); + this.expectToken('.'); + let fieldName = this.expectIdent().str; + let constValue = this.currentScope?.getConstValue(entity, fieldName); + if (constValue == null) + throw new ECSError(`"${fieldName}" is not defined as a constant`, entity); + else + return constValue; + } + } + if (tok.str == '[') { + // TODO: 16-bit? + return new Uint8Array(this.parseDataArray()); + } + if (tok.str == '#') { + this.consumeToken(); + let reftype = field.dtype == 'ref' ? field as RefType : undefined; + return this.parseEntityForwardRef(reftype); + } + // TODO? + return this.parseIntegerConstant(); + // TODO: throw this.compileError(`I expected a ${field.dtype} here.`); + } + + parseEntityForwardRef(reftype?: RefType): ForwardRef { + let token = this.expectIdent(); + return { reftype, token }; + } + + parseDataArray() { + this.expectToken('['); + let arr = this.parseList(this.parseIntegerConstant, ','); + this.expectToken(']'); + return arr; + } + + expectInteger(): number { + let s = this.consumeToken().str; + let i: number; + if (s.startsWith('$')) + i = parseInt(s.substring(1), 16); // hex $... + else if (s.startsWith('%')) + i = parseInt(s.substring(1), 2); // binary %... + else + i = parseInt(s); // default base 10 or 16 (0x...) + if (isNaN(i)) this.compileError('There should be an integer here.'); + return i; + } + + parseSystem(): System { + let name = this.expectIdent().str; + let actions: Action[] = []; + let system: System = { name, actions }; + let cmd; + while ((cmd = this.expectTokens(['on', 'locals', 'end']).str) != 'end') { + if (cmd == 'on') { + let action = this.annotate(() => this.parseAction(system)); + actions.push(action); + } else if (cmd == 'locals') { + system.tempbytes = this.parseIntegerConstant(); + } else { + this.compileError(`Unexpected system keyword: ${cmd}`); + } + } + return system; + } + + parseResource(): System { + let name = this.expectIdent().str; + let tempbytes; + if (this.peekToken().str == 'locals') { + this.consumeToken(); + tempbytes = this.parseIntegerConstant(); + } + let system: System = { name, tempbytes, actions: [] }; + let expr = this.annotate(() => this.parseBlockStatement()); + let action: Action = { expr, event: name }; + system.actions.push(action); + return system; + } + + parseAction(system: System): Action { + // TODO: unused events? + const event = this.expectIdent().str; + this.expectToken('do'); + let fitbytes = undefined; + let critical = undefined; + if (this.ifToken('critical')) critical = true; + if (this.ifToken('fit')) fitbytes = this.parseIntegerConstant(); + let expr = this.annotate(() => this.parseBlockStatement()); + //query, join, select, direction, + let action : Action = { expr, event, fitbytes, critical }; + return action as Action; + } + + parseQuery() { + let q: Query = { include: [] }; + let start = this.expectToken('['); + this.parseList(() => this.parseQueryItem(q), ','); + this.expectToken(']'); + // TODO: other params + q.$loc = mergeLocs(start.$loc, this.lasttoken.$loc); + return q; + } + + parseQueryItem(q: Query) { + let prefix = this.peekToken(); + if (prefix.type != TokenType.Ident) { + this.consumeToken(); + } + if (prefix.type == TokenType.Ident) { + let cref = this.parseComponentRef(); + q.include.push(cref); + } else if (prefix.str == '-') { + let cref = this.parseComponentRef(); + if (!q.exclude) q.exclude = []; + q.exclude.push(cref); + } else if (prefix.str == '#') { + const scope = this.currentScope; + if (scope == null) { + throw this.compileError('You can only reference specific entities inside of a scope.'); + } + let eref = this.parseEntityForwardRef(); + this.deferred.push(() => { + let refvalue = this.resolveEntityRef(scope, eref); + if (!q.entities) q.entities = []; + q.entities.push(scope.entities[refvalue]); + }); + } else { + this.compileError(`Query components may be preceded only by a '-'.`); + } + } + + parseEventName() { + return this.expectIdent().str; + } + + parseEventList() { + return this.parseList(this.parseEventName, ","); + } + + parseCode(): string { // TODOActionNode[] { + // TODO: add $loc + let tok = this.expectTokenTypes([ECSTokenType.CodeFragment]); + let code = tok.str.substring(3, tok.str.length - 3); + // TODO: add after parsing maybe? + let lines = code.split('\n'); + if (this.includeDebugInfo) this.addDebugInfo(lines, tok.$loc.line); + code = lines.join('\n'); + + //let acomp = new ECSActionCompiler(context); + //let nodes = acomp.parseFile(code, this.path); + // TODO: return nodes + return code; + } + + addDebugInfo(lines: string[], startline: number) { + const re = /^\s*(;|\/\/|$)/; // ignore comments and blank lines + for (let i = 0; i < lines.length; i++) { + if (!lines[i].match(re)) + lines[i] = this.em.dialect.debug_line(this.path, startline + i) + '\n' + lines[i]; + } + } + + parseScope(): EntityScope { + let name = this.expectIdent().str; + let scope = this.em.newScope(name, this.currentScope || undefined); + scope.filePath = this.path; + this.currentScope = scope; + let cmd; + while ((cmd = this.expectTokens(['end', 'using', 'entity', 'scope', 'comment', 'system']).str) != 'end') { + if (cmd == 'using') { + this.parseScopeUsing(); + } + if (cmd == 'entity') { + this.annotate(() => this.parseEntity()); + } + if (cmd == 'scope') { + this.annotate(() => this.parseScope()); + } + if (cmd == 'comment') { + this.expectTokenTypes([ECSTokenType.CodeFragment]); + } + // TODO: need to make these local names, otherwise we get "duplicate name" + if (cmd == 'system') { + let sys = this.annotate(() => this.parseSystem()); + this.em.defineSystem(sys); + this.currentScope.newSystemInstanceWithDefaults(sys); + } + } + this.currentScope = scope.parent || null; + return scope; + } + + parseScopeUsing() { + let instlist = this.parseList(this.parseSystemInstanceRef, ','); + let params = {}; + if (this.peekToken().str == 'with') { + this.consumeToken(); + params = this.parseSystemInstanceParameters(); + } + for (let inst of instlist) { + inst.params = params; + this.currentScope?.newSystemInstance(inst); + } + } + + parseEntity(): Entity { + if (!this.currentScope) { throw this.internalError(); } + const scope = this.currentScope; + let entname = ''; + if (this.peekToken().type == TokenType.Ident) { + entname = this.expectIdent().str; + } + let etype = this.parseEntityArchetype(); + let entity = this.currentScope.newEntity(etype, entname); + let cmd2: string; + // TODO: remove init? + while ((cmd2 = this.expectTokens(['const', 'init', 'var', 'decode', 'end']).str) != 'end') { + let cmd = cmd2; // put in scope + if (cmd == 'var') cmd = 'init'; // TODO: remove? + if (cmd == 'init' || cmd == 'const') { + this.parseInitConst(cmd, scope, entity); + } else if (cmd == 'decode') { + this.parseDecode(scope, entity); + } + } + return entity; + } + + parseInitConst(cmd: string, scope: EntityScope, entity: Entity) { + // TODO: check data types + let name = this.expectIdent().str; + let { c, f } = this.getEntityField(entity, name); + let symtype = scope.isConstOrInit(c, name); + if (symtype && symtype != cmd) + this.compileError(`I can't mix const and init values for a given field in a scope.`); + this.expectToken('='); + let valueOrRef = this.parseDataValue(f); + if ((valueOrRef as ForwardRef).token != null) { + this.deferred.push(() => { + this.lasttoken = (valueOrRef as ForwardRef).token; // for errors + let refvalue = this.resolveEntityRef(scope, valueOrRef as ForwardRef); + if (cmd == 'const') scope.setConstValue(entity, c, f, refvalue); + if (cmd == 'init') scope.setInitValue(entity, c, f, refvalue); + }); + } else { + if (cmd == 'const') scope.setConstValue(entity, c, f, valueOrRef as DataValue); + if (cmd == 'init') scope.setInitValue(entity, c, f, valueOrRef as DataValue); + } + } + + parseDecode(scope: EntityScope, entity: Entity) { + let decoderid = this.expectIdent().str; + let codetok = this.expectTokenTypes([ECSTokenType.CodeFragment]); + let code = codetok.str; + code = code.substring(3, code.length - 3); + let decoder = newDecoder(decoderid, code); + if (!decoder) { throw this.compileError(`I can't find a "${decoderid}" decoder.`); } + let result; + try { + result = decoder.parse(); + } catch (e) { + throw new ECSError(e.message, decoder.getErrorLocation(codetok.$loc)); + } + for (let entry of Object.entries(result.properties)) { + let { c, f } = this.getEntityField(entity, entry[0]); + scope.setConstValue(entity, c, f, entry[1] as DataValue); + } + } + + getEntityField(e: Entity, name: string): ComponentFieldPair { + if (!this.currentScope) { throw this.internalError(); } + let comps = this.em.componentsWithFieldName([e.etype], name); + if (comps.length == 0) this.compileError(`I couldn't find a field named "${name}" for this entity.`) + if (comps.length > 1) this.compileError(`I found more than one field named "${name}" for this entity.`) + let component = comps[0]; + let field = component.fields.find(f => f.name == name); + if (!field) { throw this.internalError(); } + return { c: component, f: field }; + } + + parseEntityArchetype(): EntityArchetype { + this.expectToken('['); + let components = this.parseList(this.parseComponentRef, ','); + this.expectToken(']'); + return { components }; + } + + parseComponentRef(): ComponentType { + let name = this.expectIdent().str; + let cref = this.em.getComponentByName(name); + if (!cref) this.compileError(`I couldn't find a component named "${name}".`) + return cref; + } + + findEntityByName(scope: EntityScope, token: Token) { + let name = token.str; + let eref = scope.entities.find(e => e.name == name); + if (!eref) { + throw this.compileError(`I couldn't find an entity named "${name}" in this scope.`, token.$loc) + } + return eref; + } + + resolveEntityRef(scope: EntityScope, ref: ForwardRef): number { + let id = this.findEntityByName(scope, ref.token).id; + if (ref.reftype) { + // TODO: make this a function? elo ehi etc? + let atypes = this.em.archetypesMatching(ref.reftype.query); + let entities = scope.entitiesMatching(atypes); + if (entities.length == 0) + throw this.compileError(`This entity doesn't seem to fit the reference type.`, ref.token.$loc); + id -= entities[0].id; + } + return id; + } + + parseSystemInstanceRef(): SystemInstance { + let name = this.expectIdent().str; + let system = this.em.getSystemByName(name); + if (!system) throw this.compileError(`I couldn't find a system named "${name}".`, this.lasttoken.$loc); + let params = {}; + let inst = { system, params, id: 0 }; + return inst; + } + + parseSystemInstanceParameters(): SystemInstanceParameters { + let scope = this.currentScope; + if (scope == null) throw this.internalError(); + if (this.peekToken().str == '[') { + return { query: this.parseQuery() }; + } + this.expectToken('#'); + let entname = this.expectIdent(); + this.expectToken('.'); + let fieldname = this.expectIdent(); + let entity = this.findEntityByName(scope, entname); + let cf = this.getEntityField(entity, fieldname.str); + return { refEntity: entity, refField: cf }; + } + + exportToFile(src: SourceFileExport) { + this.em.exportToFile(src); + } + + export() { + let src = new SourceFileExport(); + src.line(this.em.dialect.debug_file(this.path)); + for (let path of Object.keys(this.em.imported)) + src.line(this.em.dialect.debug_file(path)); + this.exportToFile(src); + return src.toString(); + } + + checkUpperLimit(value: number, upper: number, what: string) { + if (value > upper) this.compileError(`This ${what} is too high; must be ${upper} or less`); + } + checkLowerLimit(value: number, lower: number, what: string) { + if (value < lower) this.compileError(`This ${what} is too low; must be ${lower} or more`); + } + + // expression stuff + + parseConstant(): DataValue { + let expr = this.parseExpr(); + expr = this.em.evalExpr(expr, this.currentScope); + if (isLiteral(expr)) return expr.value; + throw this.compileError('This expression is not a constant.'); + } + parseIntegerConstant(): number { + let value = this.parseConstant(); + if (typeof value === 'number') return value; + throw this.compileError('This expression is not an integer.'); + } + parseExpr(): Expr { + var startloc = this.peekToken().$loc; + var expr = this.parseExpr1(this.parsePrimary(), 0); + var endloc = this.lasttoken.$loc; + expr.$loc = mergeLocs(startloc, endloc); + return expr; + } + parseExpr1(left: Expr, minPred: number): Expr { + let look = this.peekToken(); + while (getPrecedence(look) >= minPred) { + let op = this.consumeToken(); + let right: Expr = this.parsePrimary(); + look = this.peekToken(); + while (getPrecedence(look) > getPrecedence(op)) { + right = this.parseExpr1(right, getPrecedence(look)); + look = this.peekToken(); + } + var opfn = getOperator(op.str).f; + // use logical operators instead of bitwise? + if (op.str == 'and') opfn = 'land'; + if (op.str == 'or') opfn = 'lor'; + var valtype = this.exprTypeForOp(opfn, left, right, op); + left = { valtype:valtype, op:opfn, left: left, right: right }; + } + return left; + } + parsePrimary(): Expr { + let tok = this.consumeToken(); + switch (tok.type) { + case ECSTokenType.Integer: + this.pushbackToken(tok); + let value = this.expectInteger(); + let valtype : IntType = { dtype: 'int', lo: value, hi: value }; + return { valtype, value }; + case TokenType.Ident: + if (tok.str == 'not') { + let expr = this.parsePrimary(); + let valtype : IntType = { dtype: 'int', lo: 0, hi: 1 }; + return { valtype, op: 'lnot', expr: expr }; + } else { + this.pushbackToken(tok); + return this.parseVarSubscriptOrFunc(); + } + case ECSTokenType.Operator: + if (tok.str == '(') { + let expr = this.parseExpr(); + this.expectToken(')', `There should be another expression or a ")" here.`); + return expr; + } else if (tok.str == '-') { + let expr = this.parsePrimary(); // TODO: -2^2=-4 and -2-2=-4 + let valtype = (expr as ExprBase).valtype; + if (valtype?.dtype == 'int') { + let hi = Math.abs(valtype.hi); + let negtype : IntType = { dtype: 'int', lo: -hi, hi: hi }; + return { valtype: negtype, op: 'neg', expr: expr }; + } + } else if (tok.str == '+') { + return this.parsePrimary(); // ignore unary + + } + default: + throw this.compileError(`The expression is incomplete.`); + } + } + parseVarSubscriptOrFunc(): LExpr { + var tok = this.consumeToken(); + switch (tok.type) { + case TokenType.Ident: + // component:field + if (this.ifToken(':')) { + let ftok = this.consumeToken(); + let component = this.em.getComponentByName(tok.str); + if (!component) throw this.compileError(`A component named "${tok.str}" has not been defined.`); + let field = component.fields.find(f => f.name == ftok.str); + if (!field) throw this.compileError(`There is no "${ftok.str}" field in the ${tok.str} component.`); + if (!this.currentScope) throw this.compileError(`This operation only works inside of a scope.`); + let atypes = this.em.archetypesMatching({ include: [component] }) + let entities = this.currentScope.entitiesMatching(atypes); + return { entities, field } as EntityFieldOp; + } + // entity.field + if (this.ifToken('.')) { + let ftok = this.consumeToken(); + if (!this.currentScope) throw this.compileError(`This operation only works inside of a scope.`); + let entity = this.currentScope.getEntityByName(tok.str); + if (!entity) throw this.compileError(`An entity named "${tok.str}" has not been defined.`); + let component = this.em.singleComponentWithFieldName([entity.etype], ftok.str, ftok); + let field = component.fields.find(f => f.name == ftok.str); + if (!field) throw this.compileError(`There is no "${ftok.str}" field in this entity.`); + let entities = [entity]; + return { entities, field } as EntityFieldOp; + } + let args : Expr[] = []; + if (this.ifToken('(')) { + args = this.parseExprList(); + this.expectToken(')', `There should be another expression or a ")" here.`); + } + var loc = mergeLocs(tok.$loc, this.lasttoken.$loc); + var valtype = this.exprTypeForSubscript(tok.str, args, loc); + return { valtype: valtype, name: tok.str, args: args, $loc:loc }; + default: + throw this.compileError(`There should be a variable name here.`); + } + } + parseLexpr(): LExpr { + var lexpr = this.parseVarSubscriptOrFunc(); + //this.vardefs[lexpr.name] = lexpr; + //this.validateVarName(lexpr); + return lexpr; + } + exprTypeForOp(fnname: string, left: Expr, right: Expr, optok: Token) : DataType { + return { dtype: 'int', lo:0, hi:255 }; // TODO? + } + exprTypeForSubscript(fnname: string, args: Expr[], loc: SourceLocation) : DataType { + return { dtype: 'int', lo:0, hi:255 }; // TODO? + } + parseLexprList(): LExpr[] { + return this.parseList(this.parseLexpr, ','); + } + parseExprList(): Expr[] { + return this.parseList(this.parseExpr, ','); + } + // TODO: annotate with location + parseBlockStatement(): Statement { + let valtype : IntType = { dtype:'int', lo:0, hi: 0 } // TODO? + if (this.peekToken().type == ECSTokenType.CodeFragment) { + return { valtype, code: this.parseCode() }; + } + if (this.ifToken('begin')) { + let stmts = []; + while (this.peekToken().str != 'end') { + stmts.push(this.annotate(() => this.parseBlockStatement())); + } + this.expectToken('end'); + return { valtype, stmts }; + } + let cmd = this.peekToken(); + if (SELECT_TYPE.includes(cmd.str as any)) { + return this.parseQueryStatement(); + } + throw this.compileError(`There should be a statement or "end" here.`, cmd.$loc); + } + parseQueryStatement() : QueryExpr { + // TODO: include modifiers in error msg + const select = this.expectTokens(SELECT_TYPE).str as SelectType; // TODO: type check? + let all = this.ifToken('all') != null; + let query = undefined; + let join = undefined; + if (select == 'once') { + if (this.peekToken().str == '[') this.compileError(`A "${select}" action can't include a query.`) + } else { + query = this.parseQuery(); + } + if (select == 'join') { + this.expectToken('with'); + join = this.parseQuery(); + } + if (this.ifToken('limit')) { + if (!query) { this.compileError(`A "${select}" query can't include a limit.`); } + else query.limit = this.parseIntegerConstant(); + } + const all_modifiers = ['asc', 'desc']; // TODO + const modifiers = this.parseModifiers(all_modifiers); + let direction = undefined; + if (modifiers['asc']) direction = 'asc'; + else if (modifiers['desc']) direction = 'desc'; + let body = this.annotate(() => this.parseBlockStatement()); + return { select, query, join, direction, all, stmts: [body], loop: select == 'foreach' } as QueryExpr; + } +} + +/// + +export class ECSActionCompiler extends Tokenizer { + constructor( + public readonly context: ActionContext) { + super(); + this.setTokenRules([ + { type: ECSTokenType.Placeholder, regex: /\{\{.*?\}\}/ }, + { type: TokenType.CatchAll, regex: /[^{\n]+\n*/ }, + ]); + this.errorOnCatchAll = false; + } + + parseFile(text: string, path: string) { + this.tokenizeFile(text, path); + let nodes = []; + while (!this.isEOF()) { + let tok = this.consumeToken(); + if (tok.type == ECSTokenType.Placeholder) { + let args = tok.str.substring(2, tok.str.length - 2).split(/\s+/); + nodes.push(new CodePlaceholderNode(this.context, tok.$loc, args)); + } else if (tok.type == TokenType.CatchAll) { + nodes.push(new CodeLiteralNode(this.context, tok.$loc, tok.str)); + } + } + return nodes; + } +} diff --git a/src/common/ecs/decoder.ts b/src/common/ecs/decoder.ts new file mode 100644 index 00000000..51f5beb2 --- /dev/null +++ b/src/common/ecs/decoder.ts @@ -0,0 +1,203 @@ + +import { SourceLocation } from "../workertypes"; +import { DataValue, ECSError } from "./ecs"; + +export interface DecoderResult { + properties: {[name: string] : DataValue} +} + +abstract class LineDecoder { + curline: number = 0; // for debugging, zero-indexed + lines : string[][]; // array of token arrays + + constructor( + text: string + ) { + // split the text into lines and into tokens + this.lines = text.split('\n').map(s => s.trim()).filter(s => !!s).map(s => s.split(/\s+/)); + } + + decodeBits(s: string, n: number, msbfirst: boolean) { + if (s.length != n) throw new ECSError(`Expected ${n} characters`); + let b = 0; + for (let i=0; i 1) { + console.log(changed, cur, prev); + throw new ECSError(`More than one register change in line ${i+1}: [${changed}]`); + } + let chgidx = changed.length ? changed[0] : regs.length-1; + data[dataofs - 1] = regs[chgidx]; + data[dataofs - 2] = cur[chgidx]; + prev[chgidx] = cur[chgidx]; + } + return { + properties: { + data + } + } + } +} + +export class VCSBitmap48Decoder extends LineDecoder { + parse() { + let height = this.lines.length; + let bitmap0 = new Uint8Array(height); + let bitmap1 = new Uint8Array(height); + let bitmap2 = new Uint8Array(height); + let bitmap3 = new Uint8Array(height); + let bitmap4 = new Uint8Array(height); + let bitmap5 = new Uint8Array(height); + for (let i=0; i 0) return `${ident}+${offset}`; + if (offset < 0) return `${ident}-${-offset}`; + return ident; + } + indexed_x(ident: string, offset: number) { + return this.addOffset(ident, offset) + ',x'; + } + indexed_y(ident: string, offset: number) { + return this.addOffset(ident, offset) + ',y'; + } + fieldsymbol(component: ComponentType, field: DataField, bitofs: number) { + return `${component.name}_${field.name}_b${bitofs}`; + } + datasymbol(component: ComponentType, field: DataField, eid: number, bitofs: number) { + return `${component.name}_${field.name}_e${eid}_b${bitofs}`; + } + debug_file(path: string) { + return `.dbg file, "${path}", 0, 0` + } + debug_line(path: string, line: number) { + return `.dbg line, "${path}", ${line}` + } + startScope(name: string) { + return `.scope ${name}` + } + endScope(name: string) { + return `.endscope\n${this.scopeSymbol(name)} = ${name}::__Start` + } + scopeSymbol(name: string) { + return `${name}__Start`; + } + align(value: number) { + return `.align ${value}`; + } + alignSegmentStart() { + return this.label('__ALIGNORIGIN'); + } + warningIfPageCrossed(startlabel: string) { + return ` +.assert >(${startlabel}) = >(*), error, "${startlabel} crosses a page boundary!"` + } + warningIfMoreThan(bytes: number, startlabel: string) { + return ` +.assert (* - ${startlabel}) <= ${bytes}, error, .sprintf("${startlabel} does not fit in ${bytes} bytes, it took %d!", (* - ${startlabel}))` + } + alignIfLessThan(bytes: number) { + return ` +.if <(* - __ALIGNORIGIN) > 256-${bytes} +.align $100 +.endif` + } + segment(segtype: 'rodata' | 'bss' | 'code') { + if (segtype == 'bss') { + return `.zeropage`; + } else if (segtype == 'rodata') { + return '.rodata'; + } else { + return `.code`; + } + } + label(sym: string) { + return `${sym}:`; + } + byte(b: number | ConstByte | undefined) { + if (b === undefined) { + return `.res 1` + } else if (typeof b === 'number') { + if (b < 0 || b > 255) throw new ECSError(`out of range byte ${b}`); + return `.byte ${b}` + } else { + if (b.bitofs == 0) return `.byte <${b.symbol}` + else if (b.bitofs == 8) return `.byte >${b.symbol}` + else return `.byte ((${b.symbol} >> ${b.bitofs})&255)` + } + } + tempLabel(inst: SystemInstance) { + return `${inst.system.name}__${inst.id}__tmp`; + } + equate(symbol: string, value: string): string { + return `${symbol} = ${value}`; + } + define(symbol: string, value?: string): string { + if (value) return `.define ${symbol} ${value}`; + else return `.define ${symbol}`; + } + call(symbol: string) { + return ` jsr ${symbol}`; + } + jump(symbol: string) { + return ` jmp ${symbol}`; + } + return() { + return ' rts'; + } +} + +// TODO: merge with Dialect? +export class SourceFileExport { + lines: string[] = []; + + line(s: string) { + this.text(s); + } + text(s: string) { + for (let l of s.split('\n')) + this.lines.push(l); + } + toString() { + return this.lines.join('\n'); + } +} + +class CodeSegment { + codefrags: string[] = []; + + addCodeFragment(code: string) { + this.codefrags.push(code); + } + dump(file: SourceFileExport) { + for (let code of this.codefrags) { + file.text(code); + } + } +} + +class DataSegment { + symbols: { [sym: string]: number } = {}; + equates: { [sym: string]: string } = {}; + ofs2sym = new Map(); + fieldranges: { [cfname: string]: FieldArray } = {}; + size: number = 0; + initdata: (number | ConstByte | undefined)[] = []; + + allocateBytes(name: string, bytes: number) { + let ofs = this.symbols[name]; + if (ofs == null) { + ofs = this.size; + this.declareSymbol(name, ofs); + this.size += bytes; + } + return ofs; + } + declareSymbol(name: string, ofs: number) { + this.symbols[name] = ofs; + if (!this.ofs2sym.has(ofs)) + this.ofs2sym.set(ofs, []); + this.ofs2sym.get(ofs)?.push(name); + } + // TODO: ordering should not matter, but it does + findExistingInitData(bytes: Uint8Array) { + for (let i=0; i= 0) { + this.declareSymbol(name, ofs); + } else { + ofs = this.allocateBytes(name, bytes.length); + for (let i = 0; i < bytes.length; i++) { + this.initdata[ofs + i] = bytes[i]; + } + } + } + dump(file: SourceFileExport, dialect: Dialect_CA65) { + // TODO: fewer lines + for (let i = 0; i < this.size; i++) { + let syms = this.ofs2sym.get(i); + if (syms) { + for (let sym of syms) + file.line(dialect.label(sym)); + } + file.line(dialect.byte(this.initdata[i])); + } + for (let [symbol,value] of Object.entries(this.equates)) { + file.line(dialect.equate(symbol, value)); + } + } + // TODO: move cfname functions in here too + getFieldRange(component: ComponentType, fieldName: string) { + return this.fieldranges[mksymbol(component, fieldName)]; + } + getByteOffset(range: FieldArray, access: FieldAccess, entityID: number) { + if (entityID < range.elo) throw new ECSError(`entity ID ${entityID} too low for ${access.symbol}`); + if (entityID > range.ehi) throw new ECSError(`entity ID ${entityID} too high for ${access.symbol}`); + let ofs = this.symbols[access.symbol]; + if (ofs !== undefined) { + return ofs + entityID - range.elo; + } + throw new ECSError(`cannot find field access for ${access.symbol}`); + } + getOriginSymbol() { + let a = this.ofs2sym.get(0); + if (!a) throw new ECSError('getOriginSymbol(): no symbol at offset 0'); // TODO + return a[0]; + } +} + +class UninitDataSegment extends DataSegment { +} + +class ConstDataSegment extends DataSegment { +} + +// TODO: none of this makes sense +function getFieldBits(f: IntType) { + //let n = Math.abs(f.lo) + f.hi + 1; + let n = f.hi - f.lo + 1; + return Math.ceil(Math.log2(n)); +} + +function getFieldLength(f: DataType) { + if (f.dtype == 'int') { + return f.hi - f.lo + 1; + } else { + return 1; //TODO? + } +} + +function getPackedFieldSize(f: DataType, constValue?: DataValue): number { + if (f.dtype == 'int') { + return getFieldBits(f); + } if (f.dtype == 'array' && f.index) { + return 0; // TODO? getFieldLength(f.index) * getPackedFieldSize(f.elem); + } if (f.dtype == 'array' && constValue != null && Array.isArray(constValue)) { + return constValue.length * getPackedFieldSize(f.elem); + } if (f.dtype == 'ref') { + return 8; // TODO: > 256 entities? + } + return 0; +} + +class EntitySet { + atypes: EntityArchetype[]; + entities: Entity[]; + scope; + + constructor(scope: EntityScope, query?: Query, e?: Entity[]) { + this.scope = scope; + if (query) { + if (query.entities) { + this.entities = query.entities.slice(0); + } else { + this.atypes = scope.em.archetypesMatching(query); + this.entities = scope.entitiesMatching(this.atypes); + } + // TODO: desc? + if (query.limit) { + this.entities = this.entities.slice(0, query.limit); + } + } else if (e) { + this.entities = e; + } else { + throw new ECSError('invalid EntitySet constructor') + } + if (!this.atypes) { + let at = new Set(); + for (let e of this.entities) at.add(e.etype); + this.atypes = Array.from(at.values()); + } + } + contains(c: ComponentType, f: DataField, where: SourceLocated) { + // TODO: action for error msg + return this.scope.em.singleComponentWithFieldName(this.atypes, f.name, where); + } + intersection(qr: EntitySet) { + let ents = this.entities.filter(e => qr.entities.includes(e)); + return new EntitySet(this.scope, undefined, ents); + } + union(qr: EntitySet) { + // TODO: remove dups + let ents = this.entities.concat(qr.entities); + let atypes = this.atypes.concat(qr.atypes); + return new EntitySet(this.scope, undefined, ents); + } + isContiguous() { + if (this.entities.length == 0) return true; + let id = this.entities[0].id; + for (let i = 1; i < this.entities.length; i++) { + if (this.entities[i].id != ++id) return false; + } + return true; + } +} + +class IndexRegister { + lo: number | null; + hi: number | null; + elo: number; + ehi: number; + eset: EntitySet | undefined; + + constructor( + public readonly scope: EntityScope, + eset?: EntitySet + ) { + this.elo = 0; + this.ehi = scope.entities.length - 1; + this.lo = null; + this.hi = null; + if (eset) { this.narrowInPlace(eset); } + } + entityCount() { + return this.ehi - this.elo + 1; + } + clone() { + return Object.assign(new IndexRegister(this.scope), this); + } + narrow(eset: EntitySet, action?: SourceLocated) { + let i = this.clone(); + return i.narrowInPlace(eset, action) ? i : null; + } + narrowInPlace(eset: EntitySet, action?: SourceLocated): boolean { + if (this.scope != eset.scope) throw new ECSError(`scope mismatch`, action); + if (!eset.isContiguous()) throw new ECSError(`entities are not contiguous`, action); + if (this.eset) { + this.eset = this.eset.intersection(eset); + } else { + this.eset = eset; + } + if (this.eset.entities.length == 0) { + return false; + } + let newelo = this.eset.entities[0].id; + let newehi = this.eset.entities[this.eset.entities.length - 1].id; + if (this.lo === null || this.hi === null) { + this.lo = 0; + this.hi = newehi - newelo; + this.elo = newelo; + this.ehi = newehi; + } else { + //if (action) console.log((action as any).event, this.elo, '-', this.ehi, '->', newelo, '..', newehi); + this.lo += newelo - this.elo; + this.hi += newehi - this.ehi; + } + return true; + } + // TODO: removegi + offset() { + return this.lo || 0; + } +} + +// todo: generalize +class ActionCPUState { + working: EntitySet; + props: {[key: string] : string}; + xreg: IndexRegister | null = null; + yreg: IndexRegister | null = null; +} + +class ActionEval { + em : EntityManager; + dialect : Dialect_CA65; + tmplabel = ''; + label : string; + seq : number; + //used = new Set(); // TODO + + constructor( + readonly scope: EntityScope, + readonly instance: SystemInstance, + readonly action: Action, + readonly eventargs: string[]) + { + this.em = scope.em; + this.dialect = scope.em.dialect; + this.tmplabel = this.dialect.tempLabel(this.instance); + //let query = (this.action as ActionWithQuery).query; + //TODO? if (query && this.entities.length == 0) + //throw new ECSError(`query doesn't match any entities`, query); // TODO + this.seq = this.em.seq++; + this.label = `${this.instance.system.name}__${action.event}__${this.seq}`; + } + begin() { + } + end() { + } + codeToString(): string { + let code = this.exprToCode(this.action.expr); + return code; + } + private replaceTags(code: string, action: Action, props: { [name: string]: string; }) { + const tag_re = /\{\{(.+?)\}\}/g; + code = code.replace(tag_re, (entire, group: string) => { + let toks = group.split(/\s+/); + if (toks.length == 0) + throw new ECSError(`empty command`, action); + let cmd = group.charAt(0); + let arg0 = toks[0].substring(1).trim(); + let args = [arg0].concat(toks.slice(1)); + switch (cmd) { + case '!': return this.__emit(args); + case '$': return this.__local(args); + case '^': return this.__use(args); + case '#': return this.__arg(args); + case '&': return this.__eid(args); + case '<': return this.__get([arg0, '0']); + case '>': return this.__get([arg0, '8']); + default: + let value = props[toks[0]]; + if (value) + return value; + let fn = (this as any)['__' + toks[0]]; + if (fn) + return fn.bind(this)(toks.slice(1)); + throw new ECSError(`unrecognized command {{${toks[0]}}}`, action); + } + }); + return code; + } + private replaceLabels(code: string) { + const label_re = /@(\w+)\b/g; + let seq = this.em.seq++; + let label = `${this.instance.system.name}__${this.action.event}__${seq}`; + code = code.replace(label_re, (s: string, a: string) => `${label}__${a}`); + return code; + } + + __get(args: string[]) { + return this.getset(args, false); + } + __set(args: string[]) { + return this.getset(args, true); + } + getset(args: string[], canwrite: boolean) { + let fieldName = args[0]; + let bitofs = parseInt(args[1] || '0'); + return this.generateCodeForField(fieldName, bitofs, canwrite); + } + parseFieldArgs(args: string[]) { + let fieldName = args[0]; + let bitofs = parseInt(args[1] || '0'); + let component = this.em.singleComponentWithFieldName(this.scope.state.working.atypes, fieldName, this.action); + let field = component.fields.find(f => f.name == fieldName); + if (field == null) throw new ECSError(`no field named "${fieldName}" in component`, this.action); + return { component, field, bitofs }; + } + __base(args: string[]) { + let { component, field, bitofs } = this.parseFieldArgs(args); + return this.dialect.fieldsymbol(component, field, bitofs); + } + __data(args: string[]) { + let { component, field, bitofs } = this.parseFieldArgs(args); + let entities = this.scope.state.working.entities; + if (entities.length != 1) throw new ECSError(`data operates on exactly one entity`, this.action); // TODO? + let eid = entities[0].id; // TODO? + return this.dialect.datasymbol(component, field, eid, bitofs); + } + __const(args: string[]) { + let { component, field, bitofs } = this.parseFieldArgs(args); + let entities = this.scope.state.working.entities; + if (entities.length != 1) throw new ECSError(`const operates on exactly one entity`, this.action); // TODO? + let constVal = entities[0].consts[mksymbol(component, field.name)]; + if (constVal === undefined) throw new ECSError(`field is not constant`, this.action); // TODO? + if (typeof constVal !== 'number') throw new ECSError(`field is not numeric`, this.action); // TODO? + return constVal << bitofs; + } + __index(args: string[]) { + // TODO: check select type and if we actually have an index... + let ident = args[0]; + let index = parseInt(args[1] || '0'); + let entities = this.scope.state.working.entities; + if (entities.length == 1) { + return this.dialect.absolute(ident); + } else { + return this.dialect.indexed_x(ident, index); //TODO? + } + } + __eid(args: string[]) { + let e = this.scope.getEntityByName(args[0] || '?'); + if (!e) throw new ECSError(`can't find entity named "${args[0]}"`, this.action); + return e.id.toString(); + } + __use(args: string[]) { + return this.scope.includeResource(args[0]); + } + __emit(args: string[]) { + let event = args[0]; + let eventargs = args.slice(1); + try { + return this.scope.generateCodeForEvent(event, eventargs); + } catch (e) { + if (e.$sources) e.$sources.push(this.action); + throw e; + } + } + __local(args: string[]) { + let tempinc = parseInt(args[0]); + let tempbytes = this.instance.system.tempbytes; + if (isNaN(tempinc)) throw new ECSError(`bad temporary offset`, this.action); + if (!tempbytes) throw new ECSError(`this system has no locals`, this.action); + if (tempinc < 0 || tempinc >= tempbytes) throw new ECSError(`this system only has ${tempbytes} locals`, this.action); + this.scope.updateTempLiveness(this.instance); + return `${this.tmplabel}+${tempinc}`; + } + __arg(args: string[]) { + let argindex = parseInt(args[0] || '0'); + let argvalue = this.eventargs[argindex] || ''; + //this.used.add(`arg_${argindex}_${argvalue}`); + return argvalue; + } + __start(args: string[]) { + let startSymbol = this.dialect.scopeSymbol(args[0]); + return this.dialect.jump(startSymbol); + } + generateCodeForField(fieldName: string, bitofs: number, canWrite: boolean): string { + const action = this.action; + const qr = this.scope.state.working; + + var component: ComponentType; + var baseLookup = false; + var entityLookup = false; + let entities: Entity[]; + // is qualified field? + if (fieldName.indexOf('.') > 0) { + let [entname, fname] = fieldName.split('.'); + let ent = this.scope.getEntityByName(entname); + if (ent == null) throw new ECSError(`no entity named "${entname}" in this scope`, action); + component = this.em.singleComponentWithFieldName([ent.etype], fname, action); + fieldName = fname; + entities = [ent]; + entityLookup = true; + } else if (fieldName.indexOf(':') > 0) { + let [cname, fname] = fieldName.split(':'); + component = this.em.getComponentByName(cname); + if (component == null) throw new ECSError(`no component named "${cname}"`, action) + entities = this.scope.state.working.entities; + fieldName = fname; + baseLookup = true; + } else { + component = this.em.singleComponentWithFieldName(qr.atypes, fieldName, action); + entities = this.scope.state.working.entities; + } + // find archetypes + let field = component.fields.find(f => f.name == fieldName); + if (field == null) throw new ECSError(`no field named "${fieldName}" in component`, action); + let ident = this.dialect.fieldsymbol(component, field, bitofs); + // see if all entities have the same constant value + // TODO: should be done somewhere else? + let constValues = new Set(); + let isConst = false; + for (let e of entities) { + let constVal = e.consts[mksymbol(component, fieldName)]; + if (constVal !== undefined) isConst = true; + constValues.add(constVal); // constVal === undefined is allowed + } + // can't write to constant + if (isConst && canWrite) + throw new ECSError(`can't write to constant field ${fieldName}`, action); + // is it a constant? + if (constValues.size == 1) { + let value = constValues.values().next().value as DataValue; + // TODO: what about symbols? + // TODO: use dialect + if (typeof value === 'number') { + return `#${(value >> bitofs) & 0xff}`; + } + } + // TODO: offset > 0? + // TODO: don't mix const and init data + let range = this.scope.getFieldRange(component, field.name); + if (!range) throw new ECSError(`couldn't find field for ${component.name}:${fieldName}, maybe no entities?`); // TODO + // TODO: dialect + // TODO: doesnt work for entity.field + // TODO: array field baseoffset? + if (baseLookup) { + return this.dialect.absolute(ident); + } else if (entities.length == 1) { + // TODO: qr or this.entites? + let eidofs = entities[0].id - range.elo; // TODO: negative? + return this.dialect.absolute(ident, eidofs); + } else { + let ir; + let int; + let eidofs; + let xreg = this.scope.state.xreg; + let yreg = this.scope.state.yreg; + if (xreg && (int = xreg.eset?.intersection(qr))) { + //console.log(eidofs,'x',qr.entities[0].id,xreg.elo,int.entities[0].id,xreg.offset(),range.elo); + ir = xreg.eset; + //eidofs -= xreg.offset(); + //eidofs -= int.entities[0].id - xreg.elo; + eidofs = xreg.elo - range.elo; + // TODO? if (xreg.ehi > range.ehi) throw new ECSError(`field "${field.name}" could overflow`, action); + } else if (yreg && (int = yreg.eset?.intersection(qr))) { + ir = yreg.eset; + //eidofs -= yreg.offset(); + eidofs = yreg.elo - range.elo; + } else { + ir = null; + eidofs = 0; + } + if (!ir) { + throw new ECSError(`no intersection for index register`, action); + } + if (ir.entities.length == 0) throw new ECSError(`no common entities for index register`, action); + if (!ir.isContiguous()) throw new ECSError(`entities in query are not contiguous`, action); + if (ir == this.scope.state.xreg?.eset) + return this.dialect.indexed_x(ident, eidofs); + if (ir == this.scope.state.yreg?.eset) + return this.dialect.indexed_y(ident, eidofs); + throw new ECSError(`cannot find "${component.name}:${field.name}" in state`, action); + } + } + getJoinField(action: Action, atypes: EntityArchetype[], jtypes: EntityArchetype[]): ComponentFieldPair { + let refs = Array.from(this.scope.iterateArchetypeFields(atypes, (c, f) => f.dtype == 'ref')); + // TODO: better error message + if (refs.length == 0) throw new ECSError(`cannot find join fields`, action); + if (refs.length > 1) throw new ECSError(`cannot join multiple fields (${refs.map(r => r.f.name).join(' ')})`, action); + // TODO: check to make sure join works + return refs[0]; // TODO + /* TODO + let match = refs.map(ref => this.em.archetypesMatching((ref.f as RefType).query)); + for (let ref of refs) { + let m = this.em.archetypesMatching((ref.f as RefType).query); + for (let a of m) { + if (jtypes.includes(a.etype)) { + console.log(a,m); + } + } + } + */ + } + isSubroutineSized(code: string) { + // TODO? + if (code.length > 20000) return false; + if (code.split('\n ').length >= 4) return true; // TODO: :^/ + return false; + } + exprToCode(expr: Expr) : string { + if (isQueryExpr(expr)) { + return this.queryExprToCode(expr); + } + if (isBlockStmt(expr)) { + return this.blockStmtToCode(expr); + } + if (isInlineCode(expr)) { + return this.evalInlineCode(expr.code); + } + throw new ECSError(`cannot convert expression to code`, expr); + } + evalInlineCode(code: string) { + let props = this.scope.state.props || {}; + // replace @labels + code = this.replaceLabels(code); + // replace {{...}} tags + // TODO: use nodes instead + code = this.replaceTags(code, this.action, props); + return code; + } + blockStmtToCode(expr: BlockExpr) { + return expr.stmts.map(node => this.exprToCode(node)).join('\n'); + } + queryExprToCode(qexpr: QueryExpr) : string { + //console.log('query', this.action.event, qexpr.select, qexpr.query.include); + let q = this.startQuery(qexpr); + // TODO: move elsewhere? is "foreach" and "join" part of the empty set? + const allowEmpty = ['if','foreach','join']; + if (q.working.entities.length == 0 && allowEmpty.includes(qexpr.select)) { + //console.log('empty', this.action.event); + this.endQuery(q); + return ''; + } else { + this.scope.state.working = q.working; + this.scope.state.props = q.props; + //console.log('begin', this.action.event, this.scope.state); + q.code = this.evalInlineCode(q.code); + let body = this.blockStmtToCode(qexpr); + this.endQuery(q); + //console.log('end', this.action.event, this.scope.state); + body = q.code.replace('%%CODE%%', body); + return body; + } + } + queryWorkingSet(qexpr: QueryExpr) { + const scope = this.scope; + const instance = this.instance; + let select = qexpr.select; + let q = qexpr.query; + let qr = new EntitySet(scope, q); + // narrow query w/ working set? + if (!(qexpr.all || q.entities)) { + let ir = qr.intersection(scope.state.working); + // if intersection is empty, take the global set + // if doing otherwise would generate an error (execpt for "if") + // TODO: ambiguous? + if (ir.entities.length || select == 'if') { + qr = ir; + } + } + // TODO? error if none? + if (instance.params.refEntity && instance.params.refField) { + let rf = instance.params.refField; + if (rf.f.dtype == 'ref') { + let rq = rf.f.query; + qr = qr.intersection(new EntitySet(scope, rq)); + //console.log('with', instance.params, rq, this.qr); + } + } else if (instance.params.query) { + qr = qr.intersection(new EntitySet(scope, instance.params.query)); + } + return qr; + } + updateIndexRegisters(qr: EntitySet, jr: EntitySet | null, select: SelectType) { + const action = this.action; + const scope = this.scope; + const instance = this.instance; + const state = this.scope.state; + // TODO: generalize to other cpus/langs + if (qr.entities.length > 1) { + switch (select) { + case 'once': + break; + case 'foreach': + case 'unroll': + if (state.xreg && state.yreg) throw new ECSError('no more index registers', action); + if (state.xreg) state.yreg = new IndexRegister(scope, qr); + else state.xreg = new IndexRegister(scope, qr); + break; + case 'join': + // TODO: Joins don't work in superman (arrays offset?) + // ignore the join query, use the ref + if (state.xreg || state.yreg) throw new ECSError('no free index registers for join', action); + if (jr) state.xreg = new IndexRegister(scope, jr); + state.yreg = new IndexRegister(scope, qr); + break; + case 'if': + case 'with': + // TODO: what if not in X because 1 element? + if (state.xreg && state.xreg.eset) { + state.xreg = state.xreg.narrow(qr, action); + } else if (select == 'with') { + if (instance.params.refEntity && instance.params.refField) { + if (state.xreg) + state.xreg.eset = qr; + else + state.xreg = new IndexRegister(scope, qr); + // ??? + } + } + break; + } + } + } + getCodeAndProps(qexpr: QueryExpr, qr: EntitySet, jr: EntitySet|null, + oldState: ActionCPUState) + { + // get properties and code + const entities = qr.entities; + const select = qexpr.select; + let code = '%%CODE%%'; + let props: { [name: string]: string } = {}; + // TODO: detect cycles + // TODO: "source"? + // TODO: what if only 1 item? + // TODO: what if join is subset of items? + if (select == 'join' && jr) { + //let jentities = this.jr.entities; + // TODO? + // TODO? throw new ECSError(`join query doesn't match any entities`, (action as ActionWithJoin).join); // TODO + //console.log('join', qr, jr); + if (qr.entities.length) { + let joinfield = this.getJoinField(this.action, qr.atypes, jr.atypes); + // TODO: what if only 1 item? + // TODO: should be able to access fields via Y reg + code = this.wrapCodeInLoop(code, qexpr, qr.entities, joinfield); + props['%joinfield'] = this.dialect.fieldsymbol(joinfield.c, joinfield.f, 0); //TODO? + } + } + // select subset of entities + let fullEntityCount = qr.entities.length; //entities.length.toString(); + // TODO: let loopreduce = !loopents || entities.length < loopents.length; + //console.log(action.event, entities.length, loopents.length); + // filter entities from loop? + // TODO: when to ignore if entities.length == 1 and not in for loop? + if (select == 'with') { + // TODO? when to load x? + if (this.instance.params.refEntity && this.instance.params.refField) { + let re = this.instance.params.refEntity; + let rf = this.instance.params.refField; + code = this.wrapCodeInRefLookup(code); + // TODO: only fetches 1st entity in list, need offset + let range = this.scope.getFieldRange(rf.c, rf.f.name); + let eidofs = re.id - range.elo; + props['%reffield'] = `${this.dialect.fieldsymbol(rf.c, rf.f, 0)}+${eidofs}`; + } else { + code = this.wrapCodeInFilter(code, qr, oldState, props); + } + } + if (select == 'if') { + code = this.wrapCodeInFilter(code, qr, oldState, props); + } + if (select == 'foreach' && entities.length > 1) { + code = this.wrapCodeInLoop(code, qexpr, qr.entities); + } + if (select == 'unroll' && entities.length > 1) { + throw new ECSError('unroll is not yet implemented'); + } + // define properties + if (entities.length) { + props['%elo'] = entities[0].id.toString(); + props['%ehi'] = entities[entities.length - 1].id.toString(); + } + props['%ecount'] = entities.length.toString(); + props['%efullcount'] = fullEntityCount.toString(); + //console.log('working', action.event, working.entities.length, entities.length); + return { code, props }; + } + startQuery(qexpr: QueryExpr) { + const scope = this.scope; + const action = this.action; + const select = qexpr.select; + + // save old state and make clone + const oldState = this.scope.state; + this.scope.state = Object.assign(new ActionCPUState(), oldState); + + // get working set for this query + const qr = this.queryWorkingSet(qexpr); + + // is it a join? query that too + const jr = qexpr.join && qr.entities.length ? new EntitySet(scope, qexpr.join) : null; + + // update x, y state + this.updateIndexRegisters(qr, jr, select); + + const { code, props } = this.getCodeAndProps(qexpr, qr, jr, oldState); + + // if join, working set is union of both parts + let working = jr ? qr.union(jr) : qr; + + return { working, oldState, props, code }; + } + endQuery(q : { oldState: ActionCPUState }) { + this.scope.state = q.oldState; + } + wrapCodeInLoop(code: string, qexpr: QueryExpr, ents: Entity[], joinfield?: ComponentFieldPair): string { + // TODO: check ents + // TODO: check segment bounds + // TODO: what if 0 or 1 entitites? + // TODO: check > 127 or > 255 + let dir = qexpr.direction; + let s = dir == 'desc' ? this.dialect.ASM_ITERATE_EACH_DESC : this.dialect.ASM_ITERATE_EACH_ASC; + if (joinfield) s = dir == 'desc' ? this.dialect.ASM_ITERATE_JOIN_DESC : this.dialect.ASM_ITERATE_JOIN_ASC; + s = s.replace('{{%code}}', code); + return s; + } + wrapCodeInFilter(code: string, qr: EntitySet, oldState: ActionCPUState, props: any) { + // TODO: :-p filters too often? + const ents = qr.entities; + const ents2 = oldState.xreg?.eset?.entities; + if (ents && ents.length && ents2) { + let lo = ents[0].id; + let hi = ents[ents.length - 1].id; + let lo2 = ents2[0].id; + let hi2 = ents2[ents2.length - 1].id; + if (lo != lo2) { + code = this.dialect.ASM_FILTER_RANGE_LO_X.replace('{{%code}}', code); + props['%xofs'] = lo - lo2; + } + if (hi != hi2) { + code = this.dialect.ASM_FILTER_RANGE_HI_X.replace('{{%code}}', code); + } + } + return code; + } + wrapCodeInRefLookup(code: string) { + code = this.dialect.ASM_LOOKUP_REF_X.replace('{{%code}}', code); + return code; + } +} + +class EventCodeStats { + constructor( + public readonly inst: SystemInstance, + public readonly action: Action, + public readonly eventcode: string + ) { } + labels : string[] = []; + count : number = 0; +} + +export class EntityScope implements SourceLocated { + $loc: SourceLocation; + childScopes: EntityScope[] = []; + instances: SystemInstance[] = []; + entities: Entity[] = []; + fieldtypes: { [name: string]: 'init' | 'const' } = {}; + sysstats = new Map(); + bss = new UninitDataSegment(); + rodata = new ConstDataSegment(); + code = new CodeSegment(); + componentsInScope = new Set(); + resources = new Set(); + state; + isDemo = false; + filePath = ''; + + eventSeq : number; + eventCodeStats : { [code:string] : EventCodeStats }; + inCritical = 0; + + constructor( + public readonly em: EntityManager, + public readonly dialect: Dialect_CA65, + public readonly name: string, + public readonly parent: EntityScope | undefined + ) { + parent?.childScopes.push(this); + this.state = new ActionCPUState(); + // TODO: parent scope entities too? + this.state.working = new EntitySet(this, undefined, this.entities); // working set = all entities + } + newEntity(etype: EntityArchetype, name: string): Entity { + // TODO: add parent ID? lock parent scope? + // TODO: name identical check? + if (name && this.getEntityByName(name)) + throw new ECSError(`already an entity named "${name}"`); + let id = this.entities.length; + etype = this.em.addArchetype(etype); + let entity: Entity = { id, etype, consts: {}, inits: {} }; + for (let c of etype.components) { + this.componentsInScope.add(c.name); + } + entity.name = name; + this.entities.push(entity); + return entity; + } + newSystemInstance(inst: SystemInstance) { + if (!inst) throw new Error(); + inst.id = this.instances.length+1; + this.instances.push(inst); + this.em.registerSystemEvents(inst.system); + return inst; + } + newSystemInstanceWithDefaults(system: System) { + return this.newSystemInstance({ system, params: {}, id:0 }); + } + getSystemInstanceNamed(name: string) { + return this.instances.find(sys => sys.system.name == name); + } + getEntityByName(name: string) { + return this.entities.find(e => e.name == name); + } + *iterateEntityFields(entities: Entity[]) { + for (let i = 0; i < entities.length; i++) { + let e = entities[i]; + for (let c of e.etype.components) { + for (let f of c.fields) { + yield { i, e, c, f, v: e.consts[mksymbol(c, f.name)] }; + } + } + } + } + *iterateArchetypeFields(arch: EntityArchetype[], filter?: (c: ComponentType, f: DataField) => boolean) { + for (let i = 0; i < arch.length; i++) { + let a = arch[i]; + for (let c of a.components) { + for (let f of c.fields) { + if (!filter || filter(c, f)) + yield { i, c, f }; + } + } + } + } + *iterateChildScopes() { + for (let scope of this.childScopes) { + yield scope; + } + } + entitiesMatching(atypes: EntityArchetype[]) { + let result: Entity[] = []; + for (let e of this.entities) { + for (let a of atypes) { + // TODO: what about subclasses? + // TODO: very scary identity ocmpare + if (e.etype === a) { + result.push(e); + break; + } + } + } + return result; + } + hasComponent(ctype: ComponentType) { + return this.componentsInScope.has(ctype.name); + } + buildSegments() { + // build FieldArray for each component/field pair + // they will be different for bss/rodata segments + let iter = this.iterateEntityFields(this.entities); + for (var o = iter.next(); o.value; o = iter.next()) { + let { i, e, c, f, v } = o.value; + // constants and array pointers go into rodata + let cfname = mksymbol(c, f.name); + let ftype = this.fieldtypes[cfname]; + let isConst = ftype == 'const'; + let segment = isConst ? this.rodata : this.bss; + if (v === undefined && isConst) + throw new ECSError(`no value for const field ${cfname}`, e); + // determine range of indices for entities + let array = segment.fieldranges[cfname]; + if (!array) { + array = segment.fieldranges[cfname] = { component: c, field: f, elo: i, ehi: i }; + } else { + array.ehi = i; + if (array.ehi - array.elo + 1 >= 256) + throw new ECSError(`too many entities have field ${cfname}, limit is 256`); + } + // set default values for entity/field + if (!isConst) { + if (f.dtype == 'int' && f.defvalue !== undefined) { + let ecfname = mkscopesymbol(this, c, f.name); + if (e.inits[ecfname] == null) { + this.setInitValue(e, c, f, f.defvalue); + } + } + } + } + } + // TODO: cull unused entity fields + allocateSegment(segment: DataSegment, alloc: boolean, type: 'init' | 'const' | undefined) { + let fields: FieldArray[] = Object.values(segment.fieldranges); + // TODO: fields.sort((a, b) => (a.ehi - a.elo + 1) * getPackedFieldSize(a.field)); + for (let f of fields) { + if (this.fieldtypes[mksymbol(f.component, f.field.name)] == type) { + //console.log(f.component.name, f.field.name, type); + let rangelen = (f.ehi - f.elo + 1); + // TODO: doesn't work for packed arrays too well + let bits = getPackedFieldSize(f.field); + // variable size? make it a pointer + if (bits == 0) bits = 16; // TODO? + let bytesperelem = Math.ceil(bits / 8); + // TODO: packing bits + // TODO: split arrays + let access = []; + for (let i = 0; i < bits; i += 8) { + let symbol = this.dialect.fieldsymbol(f.component, f.field, i); + access.push({ symbol, bit: i, width: 8 }); // TODO + if (alloc) { + segment.allocateBytes(symbol, rangelen); // TODO + } + } + f.access = access; + } + } + } + allocateROData(segment: DataSegment) { + let iter = this.iterateEntityFields(this.entities); + for (var o = iter.next(); o.value; o = iter.next()) { + let { i, e, c, f, v } = o.value; + let cfname = mksymbol(c, f.name); + // TODO: what if mix of var, const, and init values? + if (this.fieldtypes[cfname] == 'const') { + let range = segment.fieldranges[cfname]; + let entcount = range ? range.ehi - range.elo + 1 : 0; + if (v == null && f.dtype == 'int') v = 0; + if (v == null && f.dtype == 'ref') v = 0; + if (v == null && f.dtype == 'array') + throw new ECSError(`no default value for array ${cfname}`, e); + //console.log(c.name, f.name, '#'+e.id, '=', v); + // this is a constant + // is it a byte array? + //TODO? if (ArrayBuffer.isView(v) && f.dtype == 'array') { + if (v instanceof Uint8Array && f.dtype == 'array') { + let ptrlosym = this.dialect.fieldsymbol(c, f, 0); + let ptrhisym = this.dialect.fieldsymbol(c, f, 8); + let loofs = segment.allocateBytes(ptrlosym, entcount); + let hiofs = segment.allocateBytes(ptrhisym, entcount); + let datasym = this.dialect.datasymbol(c, f, e.id, 0); + segment.allocateInitData(datasym, v); + if (f.baseoffset) datasym = `(${datasym}+${f.baseoffset})`; + segment.initdata[loofs + e.id - range.elo] = { symbol: datasym, bitofs: 0 }; + segment.initdata[hiofs + e.id - range.elo] = { symbol: datasym, bitofs: 8 }; + } else if (typeof v === 'number') { + // more than 1 entity, add an array + // TODO: infer need for array by usage + /*if (entcount > 1)*/ { + if (!range.access) throw new ECSError(`no access for field ${cfname}`) + for (let a of range.access) { + segment.allocateBytes(a.symbol, entcount); + let ofs = segment.getByteOffset(range, a, e.id); + // TODO: this happens if you forget a const field on an object? + if (e.id < range.elo) throw new ECSError('entity out of range ' + c.name + ' ' + f.name, e); + if (segment.initdata[ofs] !== undefined) throw new ECSError('initdata already set ' + ofs), e; + segment.initdata[ofs] = (v >> a.bit) & 0xff; + } + } + } else if (v == null && f.dtype == 'array' && f.index) { + // TODO + let datasym = this.dialect.datasymbol(c, f, e.id, 0); + let databytes = getFieldLength(f.index); + let offset = this.bss.allocateBytes(datasym, databytes); + // TODO? this.allocatePointerArray(c, f, datasym, entcount); + let ptrlosym = this.dialect.fieldsymbol(c, f, 0); + let ptrhisym = this.dialect.fieldsymbol(c, f, 8); + // TODO: what if we don't need a pointer array? + let loofs = segment.allocateBytes(ptrlosym, entcount); + let hiofs = segment.allocateBytes(ptrhisym, entcount); + if (f.baseoffset) datasym = `(${datasym}+${f.baseoffset})`; + segment.initdata[loofs + e.id - range.elo] = { symbol: datasym, bitofs: 0 }; + segment.initdata[hiofs + e.id - range.elo] = { symbol: datasym, bitofs: 8 }; + } else { + // TODO: bad error message - should say "wrong type, should be array" + throw new ECSError(`unhandled constant ${e.id}:${cfname} -- ${typeof v}`); + } + } + } + //console.log(segment.initdata) + } + allocateInitData(segment: DataSegment) { + if (segment.size == 0) return ''; + let initbytes = new Uint8Array(segment.size); + let iter = this.iterateEntityFields(this.entities); + for (var o = iter.next(); o.value; o = iter.next()) { + let { i, e, c, f, v } = o.value; + let scfname = mkscopesymbol(this, c, f.name); + let initvalue = e.inits[scfname]; + if (initvalue !== undefined) { + let range = segment.getFieldRange(c, f.name); + if (!range) throw new ECSError(`no init range for ${scfname}`, e); + if (!range.access) throw new ECSError(`no init range access for ${scfname}`, e); + if (typeof initvalue === 'number') { + for (let a of range.access) { + let offset = segment.getByteOffset(range, a, e.id); + initbytes[offset] = (initvalue >> a.bit) & ((1 << a.width) - 1); + } + } else if (initvalue instanceof Uint8Array) { + // TODO: 16/32... + let datasym = this.dialect.datasymbol(c, f, e.id, 0); + let ofs = this.bss.symbols[datasym]; + initbytes.set(initvalue, ofs); + } else { + // TODO: init arrays? + throw new ECSError(`cannot initialize ${scfname} = ${initvalue}`); // TODO?? + } + } + } + // build the final init buffer + // TODO: compress 0s? + let bufsym = this.name + '__INITDATA'; + let bufofs = this.rodata.allocateInitData(bufsym, initbytes); + let code = this.dialect.INIT_FROM_ARRAY; + //TODO: function to repalce from dict? + code = code.replace('{{%nbytes}}', initbytes.length.toString()) + code = code.replace('{{%src}}', bufsym); + code = code.replace('{{%dest}}', segment.getOriginSymbol()); + return code; + } + getFieldRange(c: ComponentType, fn: string) { + return this.bss.getFieldRange(c, fn) || this.rodata.getFieldRange(c, fn); + } + setConstValue(e: Entity, component: ComponentType, field: DataField, value: DataValue) { + this.setConstInitValue(e, component, field, value, 'const'); + } + setInitValue(e: Entity, component: ComponentType, field: DataField, value: DataValue) { + this.setConstInitValue(e, component, field, value, 'init'); + } + setConstInitValue(e: Entity, component: ComponentType, field: DataField, value: DataValue, + type: 'const'|'init') + { + this.checkFieldValue(field, value); + let fieldName = field.name; + let cfname = mksymbol(component, fieldName); + let ecfname = mkscopesymbol(this, component, fieldName); + if (e.consts[cfname] !== undefined) throw new ECSError(`"${fieldName}" is already defined as a constant`, e); + if (e.inits[ecfname] !== undefined) throw new ECSError(`"${fieldName}" is already defined as a variable`, e); + if (type == 'const') e.consts[cfname] = value; + if (type == 'init') e.inits[ecfname] = value; + this.fieldtypes[cfname] = type; + } + isConstOrInit(component: ComponentType, fieldName: string) : 'const' | 'init' { + return this.fieldtypes[mksymbol(component, fieldName)]; + } + getConstValue(entity: Entity, fieldName: string) { + let component = this.em.singleComponentWithFieldName([entity.etype], fieldName, entity); + let cfname = mksymbol(component, fieldName); + return entity.consts[cfname]; + } + checkFieldValue(field: DataField, value: DataValue) { + if (field.dtype == 'array') { + if (!(value instanceof Uint8Array)) + throw new ECSError(`This "${field.name}" value should be an array.`); + } else if (typeof value !== 'number') { + throw new ECSError(`This "${field.name}" ${field.dtype} value should be an number.`); + } else { + if (field.dtype == 'int') { + if (value < field.lo || value > field.hi) + throw new ECSError(`This "${field.name}" value is out of range, should be between ${field.lo} and ${field.hi}.`); + } else if (field.dtype == 'ref') { + // TODO: allow override if number + let eset = new EntitySet(this, field.query); + if (value < 0 || value >= eset.entities.length) + throw new ECSError(`This "${field.name}" value is out of range for this ref type.`); + } + } + } + generateCodeForEvent(event: string, args?: string[], codelabel?: string): string { + // find systems that respond to event + // and have entities in this scope + let systems = this.em.event2systems[event]; + if (!systems || systems.length == 0) { + // TODO: error or warning? + //throw new ECSError(`warning: no system responds to "${event}"`); + console.log(`warning: no system responds to "${event}"`); + return ''; + } + this.eventSeq++; + // generate code + let code = ''; + // is there a label? generate it first + if (codelabel) { code += this.dialect.label(codelabel) + '\n'; } + // if "start" event, initialize data segment + if (event == 'start') { + code += this.allocateInitData(this.bss); + } + // iterate all instances and generate matching events + let eventCount = 0; + let instances = this.instances.filter(inst => systems.includes(inst.system)); + for (let inst of instances) { + let sys = inst.system; + for (let action of sys.actions) { + if (action.event == event) { + eventCount++; + // TODO: use Tokenizer so error msgs are better + // TODO: keep event tree + let codeeval = new ActionEval(this, inst, action, args || []); + codeeval.begin(); + if (action.critical) this.inCritical++; + let eventcode = codeeval.codeToString(); + if (action.critical) this.inCritical--; + if (!this.inCritical && codeeval.isSubroutineSized(eventcode)) { + let normcode = this.normalizeCode(eventcode, action); + let estats = this.eventCodeStats[normcode]; + if (!estats) { + estats = this.eventCodeStats[normcode] = new EventCodeStats( + inst, action, eventcode); + } + estats.labels.push(codeeval.label); + estats.count++; + if (action.critical) estats.count++; // always make critical event subroutines + } + let s = ''; + s += this.dialect.comment(`start action ${codeeval.label}`); + s += eventcode; + s += this.dialect.comment(`end action ${codeeval.label}`); + code += s; + // TODO: check that this happens once? + codeeval.end(); + } + } + } + if (eventCount == 0) { + console.log(`warning: event ${event} not handled`); + } + return code; + } + normalizeCode(code: string, action: Action) { + // TODO: use dialect to help with this + code = code.replace(/\b(\w+__\w+__)(\d+)__(\w+)\b/g, (z,a,b,c) => a+c); + return code; + } + getSystemStats(inst: SystemInstance) : SystemStats { + let stats = this.sysstats.get(inst); + if (!stats) { + stats = new SystemStats(); + this.sysstats.set(inst, stats); + } + return stats; + } + updateTempLiveness(inst: SystemInstance) { + let stats = this.getSystemStats(inst); + let n = this.eventSeq; + if (stats.tempstartseq && stats.tempendseq) { + stats.tempstartseq = Math.min(stats.tempstartseq, n); + stats.tempendseq = Math.max(stats.tempendseq, n); + } else { + stats.tempstartseq = stats.tempendseq = n; + } + } + includeResource(symbol: string): string { + this.resources.add(symbol); + return symbol; + } + private allocateTempVars() { + let pack = new Packer(); + let maxTempBytes = 128 - this.bss.size; // TODO: multiple data segs + let bssbin = new Bin({ left:0, top:0, bottom: this.eventSeq+1, right: maxTempBytes }); + pack.bins.push(bssbin); + for (let instance of this.instances) { + let stats = this.getSystemStats(instance); + if (instance.system.tempbytes && stats.tempstartseq && stats.tempendseq) { + let v = { + inst: instance, + top: stats.tempstartseq, + bottom: stats.tempendseq+1, + width: instance.system.tempbytes, + height: stats.tempendseq - stats.tempstartseq + 1, + label: instance.system.name + }; + pack.boxes.push(v); + } + } + if (!pack.pack()) console.log('cannot pack temporary local vars'); // TODO + //console.log('tempvars', pack); + if (bssbin.extents.right > 0) { + let tempofs = this.bss.allocateBytes('TEMP', bssbin.extents.right); + for (let b of pack.boxes) { + let inst : SystemInstance = (b as any).inst; + //console.log(inst.system.name, b.box?.left); + if (b.box) this.bss.declareSymbol(this.dialect.tempLabel(inst), tempofs + b.box.left); + //this.bss.equates[this.dialect.tempLabel(inst)] = `TEMP+${b.box?.left}`; + } + } + console.log(pack.toSVGUrl()); + } + private analyzeEntities() { + this.buildSegments(); + this.allocateSegment(this.bss, true, 'init'); // initialized vars + this.allocateSegment(this.bss, true, undefined); // uninitialized vars + this.allocateSegment(this.rodata, false, 'const'); // constants + this.allocateROData(this.rodata); + } + private generateCode() { + this.eventSeq = 0; + this.eventCodeStats = {}; + let isMainScope = this.parent == null; + let start; + let initsys = this.em.getSystemByName('Init'); + if (isMainScope && initsys) { + this.newSystemInstanceWithDefaults(initsys); //TODO: what if none? + start = this.generateCodeForEvent('main_init'); + } else { + start = this.generateCodeForEvent('start'); + } + start = this.replaceSubroutines(start); + this.code.addCodeFragment(start); + for (let sub of Array.from(this.resources.values())) { + if (!this.getSystemInstanceNamed(sub)) { + let sys = this.em.getSystemByName(sub); + if (!sys) throw new ECSError(`cannot find resource named "${sub}"`); + this.newSystemInstanceWithDefaults(sys); + } + let code = this.generateCodeForEvent(sub, [], sub); + this.code.addCodeFragment(code); // TODO: should be rodata? + } + //this.showStats(); + } + replaceSubroutines(code: string) { + // TODO: bin-packing for critical code + // TODO: doesn't work with nested subroutines? + // TODO: doesn't work between scopes + let allsubs : string[] = []; + for (let stats of Object.values(this.eventCodeStats)) { + if (stats.count > 1) { + if (allsubs.length == 0) { + allsubs = [ + this.dialect.segment('rodata'), + this.dialect.alignSegmentStart() + ] + } else if (stats.action.fitbytes) { + allsubs.push(this.dialect.alignIfLessThan(stats.action.fitbytes)); + } + let subcall = this.dialect.call(stats.labels[0]); + for (let label of stats.labels) { + let startdelim = this.dialect.comment(`start action ${label}`).trim(); + let enddelim = this.dialect.comment(`end action ${label}`).trim(); + let istart = code.indexOf(startdelim); + let iend = code.indexOf(enddelim, istart); + if (istart >= 0 && iend > istart) { + code = code.substring(0, istart) + subcall + code.substring(iend + enddelim.length); + } + } + let substart = stats.labels[0]; + let sublines = [ + this.dialect.segment('rodata'), + this.dialect.label(substart), + stats.eventcode, + this.dialect.return(), + ]; + if (stats.action.critical) { + sublines.push(this.dialect.warningIfPageCrossed(substart)); + } + if (stats.action.fitbytes) { + sublines.push(this.dialect.warningIfMoreThan(stats.action.fitbytes, substart)); + } + allsubs = allsubs.concat(sublines); + } + } + code += allsubs.join('\n'); + return code; + } + showStats() { + for (let inst of this.instances) { + // TODO? + console.log(inst.system.name, this.getSystemStats(inst)); + } + } + private dumpCodeTo(file: SourceFileExport) { + let dialect = this.dialect; + file.line(dialect.startScope(this.name)); + file.line(dialect.segment('bss')); + this.bss.dump(file, dialect); + file.line(dialect.segment('code')); // TODO: rodata for aligned? + this.rodata.dump(file, dialect); + //file.segment(`${this.name}_CODE`, 'code'); + file.line(dialect.label('__Start')); + this.code.dump(file); + for (let subscope of this.childScopes) { + // TODO: overlay child BSS segments + subscope.dump(file); + } + file.line(dialect.endScope(this.name)); + } + dump(file: SourceFileExport) { + this.analyzeEntities(); + this.generateCode(); + this.allocateTempVars(); + this.dumpCodeTo(file); + } +} + +export class EntityManager { + archetypes: { [key: string]: EntityArchetype } = {}; + components: { [name: string]: ComponentType } = {}; + systems: { [name: string]: System } = {}; + topScopes: { [name: string]: EntityScope } = {}; + event2systems: { [event: string]: System[] } = {}; + name2cfpairs: { [cfname: string]: ComponentFieldPair[] } = {}; + mainPath: string = ''; + imported: { [path: string]: boolean } = {}; + seq = 1; + + constructor(public readonly dialect: Dialect_CA65) { + } + newScope(name: string, parent?: EntityScope) { + let existing = this.topScopes[name]; + if (existing && !existing.isDemo) + throw new ECSError(`scope ${name} already defined`, existing); + let scope = new EntityScope(this, this.dialect, name, parent); + if (!parent) this.topScopes[name] = scope; + return scope; + } + deferComponent(name: string) { + this.components[name] = { name, fields: [] }; + } + defineComponent(ctype: ComponentType) { + let existing = this.components[ctype.name]; + // we can defer component definitions, just declare a component with 0 fields? + if (existing && existing.fields.length > 0) + throw new ECSError(`component ${ctype.name} already defined`, existing); + if (existing) { + existing.fields = ctype.fields; + ctype = existing; + } + for (let field of ctype.fields) { + let list = this.name2cfpairs[field.name]; + if (!list) list = this.name2cfpairs[field.name] = []; + list.push({ c: ctype, f: field }); + } + this.components[ctype.name] = ctype; + return ctype; + } + defineSystem(system: System) { + let existing = this.systems[system.name]; + if (existing) throw new ECSError(`system ${system.name} already defined`, existing); + return this.systems[system.name] = system; + } + registerSystemEvents(system: System) { + for (let a of system.actions) { + let event = a.event; + let list = this.event2systems[event]; + if (list == null) list = this.event2systems[event] = []; + if (!list.includes(system)) list.push(system); + } + } + addArchetype(atype: EntityArchetype): EntityArchetype { + let key = atype.components.map(c => c.name).join(','); + if (this.archetypes[key]) + return this.archetypes[key]; + else + return this.archetypes[key] = atype; + } + componentsMatching(q: Query, etype: EntityArchetype) { + let list = []; + for (let c of etype.components) { + if (q.exclude?.includes(c)) { + return []; + } + // TODO: 0 includes == all entities? + if (q.include.length == 0 || q.include.includes(c)) { + list.push(c); + } + } + return list.length == q.include.length ? list : []; + } + archetypesMatching(q: Query) { + let result = new Set(); + for (let etype of Object.values(this.archetypes)) { + let cmatch = this.componentsMatching(q, etype); + if (cmatch.length > 0) { + result.add(etype); + } + } + return Array.from(result.values()); + } + componentsWithFieldName(atypes: EntityArchetype[], fieldName: string) { + // TODO??? + let comps = new Set(); + for (let at of atypes) { + for (let c of at.components) { + for (let f of c.fields) { + if (f.name == fieldName) + comps.add(c); + } + } + } + return Array.from(comps); + } + getComponentByName(name: string): ComponentType { + return this.components[name]; + } + getSystemByName(name: string): System { + return this.systems[name]; + } + singleComponentWithFieldName(atypes: EntityArchetype[], fieldName: string, where: SourceLocated) { + let cfpairs = this.name2cfpairs[fieldName]; + if (!cfpairs) throw new ECSError(`cannot find field named "${fieldName}"`, where); + let filtered = cfpairs.filter(cf => atypes.find(a => a.components.includes(cf.c))); + if (filtered.length == 0) { + throw new ECSError(`cannot find component with field "${fieldName}" in this context`, where); + } + if (filtered.length > 1) { + throw new ECSError(`ambiguous field name "${fieldName}"`, where); + } + return filtered[0].c; + } + toJSON() { + return JSON.stringify({ + components: this.components, + systems: this.systems + }) + } + exportToFile(file: SourceFileExport) { + for (let event of Object.keys(this.event2systems)) { + file.line(this.dialect.equate(`EVENT__${event}`, '1')) + } + for (let scope of Object.values(this.topScopes)) { + if (!scope.isDemo || scope.filePath == this.mainPath) { + scope.dump(file); + } + } + } + *iterateScopes() { + for (let scope of Object.values(this.topScopes)) { + yield scope; + scope.iterateChildScopes(); + } + } + getDebugTree() : {} { + let scopes = this.topScopes; + let components = this.components; + let fields = this.name2cfpairs; + let systems = this.systems; + let events = this.event2systems; + let entities : {[key:string]:Entity} = {}; + for (let scope of Array.from(this.iterateScopes())) { + for (let e of scope.entities) + entities[e.name || '#'+e.id.toString()] = e; + } + return { scopes, components, fields, systems, events, entities }; + } + + // expression stuff + + evalExpr(expr: Expr, scope: EntityScope | null) : Expr { + if (isLiteral(expr)) return expr; + if (isBinOp(expr) || isUnOp(expr)) { + var fn = (this as any)['evalop__' + expr.op]; + if (!fn) throw new ECSError(`no eval function for "${expr.op}"`); + } + if (isBinOp(expr)) { + expr.left = this.evalExpr(expr.left, scope); + expr.right = this.evalExpr(expr.right, scope); + let e = fn(expr.left, expr.right); + return e || expr; + } + if (isUnOp(expr)) { + expr.expr = this.evalExpr(expr.expr, scope); + let e = fn(expr.expr); + return e || expr; + } + return expr; + } + evalop__neg(arg: Expr) : Expr | undefined { + if (isLiteralInt(arg)) { + let valtype : IntType = { dtype:'int', + lo: -arg.valtype.hi, + hi: arg.valtype.hi }; + return { valtype, value: -arg.value }; + } + } + evalop__add(left: Expr, right: Expr) : Expr | undefined { + if (isLiteralInt(left) && isLiteralInt(right)) { + let valtype : IntType = { dtype:'int', + lo: left.valtype.lo + right.valtype.lo, + hi: left.valtype.hi + right.valtype.hi }; + return { valtype, value: left.value + right.value }; + } + } + evalop__sub(left: Expr, right: Expr) : Expr | undefined { + if (isLiteralInt(left) && isLiteralInt(right)) { + let valtype : IntType = { dtype:'int', + lo: left.valtype.lo - right.valtype.hi, + hi: left.valtype.hi - right.valtype.lo }; + return { valtype, value: left.value - right.value }; + } + } +} diff --git a/src/common/ecs/main.ts b/src/common/ecs/main.ts new file mode 100644 index 00000000..e93382be --- /dev/null +++ b/src/common/ecs/main.ts @@ -0,0 +1,32 @@ +import { readFileSync } from "fs"; +import { ECSCompiler } from "./compiler"; +import { Dialect_CA65, EntityManager, SourceFileExport } from "./ecs"; + +class ECSMain { + + compiler = new ECSCompiler(new EntityManager(new Dialect_CA65()), true); // TODO + + constructor(readonly args: string[]) { + } + + run() { + for (let path of this.args) { + let text = readFileSync(path, 'utf-8'); + try { + this.compiler.parseFile(text, path); + if (this.compiler.errors.length == 0) { + let file = new SourceFileExport(); + this.compiler.exportToFile(file); + console.log(file.toString()); + } + } catch (e) { + console.error(e); + } + for (let err of this.compiler.errors) { + console.error(`${err.path}:${err.line}:${err.start}: ${err.msg}`); + } + } + } +} + +new ECSMain(process.argv.slice(2)).run(); diff --git a/src/common/ecs/tsconfig.json b/src/common/ecs/tsconfig.json new file mode 100644 index 00000000..44c1e47b --- /dev/null +++ b/src/common/ecs/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "noImplicitThis": true, + "noImplicitAny": true, + "preserveConstEnums": true, + "alwaysStrict": true, + "strictNullChecks": true, + } +} diff --git a/src/common/tokenizer.ts b/src/common/tokenizer.ts new file mode 100644 index 00000000..21111204 --- /dev/null +++ b/src/common/tokenizer.ts @@ -0,0 +1,247 @@ + +import type { SourceLocation, SourceLine, WorkerError, SourceLocated } from "./workertypes"; + +export class CompileError extends Error { + $loc: SourceLocation; + constructor(msg: string, loc: SourceLocation) { + super(msg); + Object.setPrototypeOf(this, CompileError.prototype); + this.$loc = loc; + } +} + +export function mergeLocs(a: SourceLocation, b: SourceLocation): SourceLocation { + return { + line: Math.min(a.line, b.line), + start: Math.min(a.start, b.start), + end: Math.max(a.end, b.end), + label: a.label || b.label, + path: a.path || b.path, + } +} + +export enum TokenType { + EOF = 'eof', + EOL = 'eol', + Ident = 'ident', + Comment = 'comment', + Ignore = 'ignore', + CatchAll = 'catch-all', +} + +export class Token implements SourceLocated { + str: string; + type: string; + eol: boolean; // end of line? + $loc: SourceLocation; +} + +export class TokenRule { + type: string; + regex: RegExp; +} + +const CATCH_ALL_RULES: TokenRule[] = [ + { type: TokenType.CatchAll, regex: /.+?/ } +] + +function re_escape(rule: TokenRule): string { + return `(${rule.regex.source})`; +} + +export class TokenizerRuleSet { + rules: TokenRule[]; + regex: RegExp; + constructor(rules: TokenRule[]) { + this.rules = rules.concat(CATCH_ALL_RULES); + var pattern = this.rules.map(re_escape).join('|'); + this.regex = new RegExp(pattern, "gs"); // global, dotall + } +} + +export class Tokenizer { + ruleset: TokenizerRuleSet; + lineindex: number[]; + path: string; + lineno: number; + tokens: Token[]; + lasttoken: Token; + errors: WorkerError[]; + curlabel: string; + eof: Token; + errorOnCatchAll = false; + deferred: (() => void)[] = []; + + constructor() { + this.errors = []; + this.lineno = 0; + this.lineindex = []; + this.tokens = []; + } + setTokenRuleSet(ruleset: TokenizerRuleSet) { + this.ruleset = ruleset; + } + setTokenRules(rules: TokenRule[]) { + this.setTokenRuleSet(new TokenizerRuleSet(rules)); + } + tokenizeFile(contents: string, path: string) { + this.path = path; + let m; + let re = /\n|\r\n?/g; + this.lineindex.push(0); + while (m = re.exec(contents)) { + this.lineindex.push(m.index); + } + this._tokenize(contents); + this.eof = { type: TokenType.EOF, str: "", eol: true, $loc: { path: this.path, line: this.lineno } }; + this.pushToken(this.eof); + } + _tokenize(text: string): void { + // iterate over each token via re_toks regex + let m: RegExpMatchArray; + this.lineno = 0; + while (m = this.ruleset.regex.exec(text)) { + let found = false; + // find line # + while (m.index >= this.lineindex[this.lineno]) { + this.lineno++; + } + // find out which capture group was matched, and thus token type + let rules = this.ruleset.rules; + for (let i = 0; i < rules.length; i++) { + let s: string = m[i + 1]; + if (s != null) { + found = true; + let col = m.index - (this.lineindex[this.lineno-1] || -1) - 1; + let loc = { path: this.path, line: this.lineno, start: col, end: col + s.length }; + let rule = rules[i]; + // add token to list + switch (rule.type) { + case TokenType.CatchAll: + if (this.errorOnCatchAll) { + this.compileError(`I didn't expect the character "${m[0]}" here.`, loc); + } + default: + this.pushToken({ str: s, type: rule.type, $loc: loc, eol: false }); + break; + case TokenType.EOL: + // set EOL for last token + if (this.tokens.length) + this.tokens[this.tokens.length-1].eol = true; + case TokenType.Comment: + case TokenType.Ignore: + break; + } + break; + } + } + if (!found) { + this.compileError(`Could not parse token: <<${m[0]}>>`) + } + } + } + pushToken(token: Token) { + this.tokens.push(token); + } + addError(msg: string, loc?: SourceLocation) { + let tok = this.lasttoken || this.peekToken(); + if (!loc) loc = tok.$loc; + this.errors.push({ path: loc.path, line: loc.line, label: this.curlabel, start: loc.start, end: loc.end, msg: msg }); + } + internalError() { + return this.compileError("Internal error."); + } + notImplementedError() { + return this.compileError("Not yet implemented."); + } + compileError(msg: string, loc?: SourceLocation, loc2?: SourceLocation) : CompileError { + this.addError(msg, loc); + //if (loc2 != null) this.addError(`...`, loc2); + let e = new CompileError(msg, loc); + throw e; + return e; + } + peekToken(lookahead?: number): Token { + let tok = this.tokens[lookahead || 0]; + return tok ? tok : this.eof; + } + consumeToken(): Token { + let tok = this.lasttoken = (this.tokens.shift() || this.eof); + return tok; + } + ifToken(match: string): Token | undefined { + if (this.peekToken().str == match) return this.consumeToken(); + } + expectToken(str: string, msg?: string): Token { + let tok = this.consumeToken(); + let tokstr = tok.str; + if (str != tokstr) { + this.compileError(msg || `There should be a "${str}" here.`); + } + return tok; + } + expectTokens(strlist: readonly string[], msg?: string): Token { + let tok = this.consumeToken(); + let tokstr = tok.str; + if (!strlist.includes(tokstr)) { + this.compileError(msg || `These keywords are valid here: ${strlist.join(', ')}`); + } + return tok; + } + parseModifiers(modifiers: string[]): { [modifier: string]: boolean } { + let result = {}; + do { + var tok = this.peekToken(); + if (modifiers.indexOf(tok.str) < 0) + return result; + this.consumeToken(); + result[tok.str] = true; + } while (tok != null); + } + expectIdent(msg?: string): Token { + let tok = this.consumeToken(); + if (tok.type != TokenType.Ident) + this.compileError(msg || `There should be an identifier here.`); + return tok; + } + pushbackToken(tok: Token) { + this.tokens.unshift(tok); + } + isEOF() { + return this.tokens.length == 0 || this.peekToken().type == 'eof'; // TODO? + } + expectEOL(msg?: string) { + let tok = this.consumeToken(); + if (tok.type != TokenType.EOL) + this.compileError(msg || `There's too much stuff on this line.`); + } + skipBlankLines() { + this.skipTokenTypes(['eol']); + } + skipTokenTypes(types: string[]) { + while (types.includes(this.peekToken().type)) + this.consumeToken(); + } + expectTokenTypes(types: string[], msg?: string) { + let tok = this.consumeToken(); + if (!types.includes(tok.type)) + this.compileError(msg || `There should be a ${types.map((s) => `"${s}"`).join(' or ')} here. not a "${tok.type}".`); + return tok; + } + parseList(parseFunc:()=>T, delim:string): T[] { + var sep; + var list = []; + do { + var el = parseFunc.bind(this)(); // call parse function + if (el != null) list.push(el); // add parsed element to list + sep = this.consumeToken(); // consume seperator token + } while (sep.str == delim); + this.pushbackToken(sep); + return list; + } + runDeferred() { + while (this.deferred.length) { + this.deferred.shift()(); + } + } +} diff --git a/src/common/util.ts b/src/common/util.ts index 47c854ab..d2687958 100644 --- a/src/common/util.ts +++ b/src/common/util.ts @@ -482,7 +482,7 @@ export function getBasePlatform(platform : string) : string { } // get platform ID without - specialization -export function getRootPlatform(platform : string) : string { +function getRootPlatform(platform : string) : string { return platform.split('-')[0]; } diff --git a/src/common/workertypes.ts b/src/common/workertypes.ts index 44972162..198a2292 100644 --- a/src/common/workertypes.ts +++ b/src/common/workertypes.ts @@ -19,6 +19,15 @@ export interface SourceLine extends SourceLocation { cycles?:number; } +// objects that have source code position info +export interface SourceLocated { + $loc?: SourceLocation; +} +// statements also have the 'offset' (pc) field from SourceLine +export interface SourceLineLocated { + $loc?: SourceLine; +} + export class SourceFile { lines: SourceLine[]; text: string; diff --git a/src/ide/project.ts b/src/ide/project.ts index a399ef33..8db8f63f 100644 --- a/src/ide/project.ts +++ b/src/ide/project.ts @@ -1,5 +1,5 @@ -import { FileData, Dependency, SourceLine, SourceFile, CodeListing, CodeListingMap, WorkerError, Segment, WorkerResult, WorkerOutputResult, isUnchanged, isOutputResult, WorkerMessage, WorkerItemUpdate } from "../common/workertypes"; +import { FileData, Dependency, SourceLine, SourceFile, CodeListing, CodeListingMap, WorkerError, Segment, WorkerResult, WorkerOutputResult, isUnchanged, isOutputResult, WorkerMessage, WorkerItemUpdate, WorkerErrorResult, isErrorResult } from "../common/workertypes"; import { getFilenamePrefix, getFolderForPath, isProbablyBinary, getBasePlatform, getWithBinary } from "../common/util"; import { Platform } from "../common/baseplatform"; import localforage from "localforage"; @@ -129,6 +129,8 @@ export class CodeProject { } if (data && isOutputResult(data)) { this.processBuildResult(data); + } else if (isErrorResult(data)) { + this.processBuildListings(data); } this.callbackBuildResult(data); } @@ -150,6 +152,7 @@ export class CodeProject { files.push(dir + '/' + fn); } + // TODO: use tool id to parse files, not platform parseIncludeDependencies(text:string):string[] { let files = []; let m; @@ -199,13 +202,18 @@ export class CodeProject { this.pushAllFiles(files, m[2]); } // for wiz - let re5 = /^\s*(import|embed)\s+"(.+?)";/gmi; + let re5 = /^\s*(import|embed)\s*"(.+?)";/gmi; while (m = re5.exec(text)) { if (m[1] == 'import') this.pushAllFiles(files, m[2] + ".wiz"); else this.pushAllFiles(files, m[2]); } + // for ecs + let re6 = /^\s*(import)\s*"(.+?)"/gmi; + while (m = re6.exec(text)) { + this.pushAllFiles(files, m[2]); + } } return files; } @@ -370,7 +378,7 @@ export class CodeProject { this.sendBuild(); } - processBuildResult(data: WorkerOutputResult) { + processBuildListings(data: WorkerOutputResult | WorkerErrorResult) { // TODO: link listings with source files if (data.listings) { this.listings = data.listings; @@ -382,6 +390,14 @@ export class CodeProject { lst.assemblyfile = new SourceFile(lst.asmlines, lst.text); } } + } + + processBuildResult(data: WorkerOutputResult) { + this.processBuildListings(data); + this.processBuildSegments(data); + } + + processBuildSegments(data: WorkerOutputResult) { // save and sort segment list var segs = (this.platform.getMemoryMap && this.platform.getMemoryMap()["main"]) || []; if (data.segments) { segs = segs.concat(data.segments || []); } @@ -401,6 +417,10 @@ export class CodeProject { var fnprefix = getFilenamePrefix(this.stripLocalPath(path)); var listings = this.getListings(); var onlyfile = null; + for (var lstfn in listings) { + if (lstfn == path) + return listings[lstfn]; + } for (var lstfn in listings) { onlyfile = lstfn; if (getFilenamePrefix(lstfn) == fnprefix) { diff --git a/src/ide/ui.ts b/src/ide/ui.ts index 4551dfe9..d2a78d59 100644 --- a/src/ide/ui.ts +++ b/src/ide/ui.ts @@ -115,7 +115,8 @@ const TOOL_TO_SOURCE_STYLE = { 'silice': 'verilog', 'wiz': 'text/x-wiz', 'vasmarm': 'vasm', - 'armips': 'vasm' + 'armips': 'vasm', + 'ecs': 'ecs', } const TOOL_TO_HELPURL = { @@ -323,7 +324,7 @@ function refreshWindowList() { for (var lstfn in listings) { var lst = listings[lstfn]; // add listing if source/assembly file exists and has text - if ((lst.assemblyfile && lst.assemblyfile.text) || (lst.sourcefile && lst.sourcefile.text)) { + if ((lst.assemblyfile && lst.assemblyfile.text) || (lst.sourcefile && lst.sourcefile.text) || lst.text) { addWindowItem(lstfn, getFilenameForPath(lstfn), (path) => { return new ListingView(path); }); @@ -1829,8 +1830,10 @@ function _addIncludeFile() { addFileToProject("Include", ".inc", (s) => { return '\t.include "'+s+'"' }); else if (tool == 'verilator') addFileToProject("Verilog", ".v", (s) => { return '`include "'+s+'"' }); - else if (tool == 'wiz') + else if (tool == 'wiz') addFileToProject("Include", ".wiz", (s) => { return 'import "'+s+'";' }); + else if (tool == 'ecs') + addFileToProject("Include", ".ecs", (s) => { return 'import "'+s+'"' }); else alertError("Can't add include file to this project type (" + tool + ")"); } diff --git a/src/ide/views/asseteditor.ts b/src/ide/views/asseteditor.ts index 73334e74..1cdf47c9 100644 --- a/src/ide/views/asseteditor.ts +++ b/src/ide/views/asseteditor.ts @@ -175,6 +175,12 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext { var meta = {defs:metadefs,width:width*8,height:height*8}; result.push({fileid:id,label:ident,meta:meta}); } + // TODO: look for decode --- ... --- + /* + var re3 = /\bdecode\s+(\w+)\s*---(.+?)---/gims; + while (m = re3.exec(data)) { + } + */ return result; } diff --git a/src/ide/views/editors.ts b/src/ide/views/editors.ts index 949353d5..eff2e84f 100644 --- a/src/ide/views/editors.ts +++ b/src/ide/views/editors.ts @@ -36,6 +36,7 @@ const MODEDEFS = { markdown: { lineWrap: true }, fastbasic: { noGutters: true }, basic: { noLineNumbers: true, noGutters: true }, // TODO: not used? + ecs: { theme: 'mbo', isAsm: true }, } export var textMapFunctions = { @@ -213,7 +214,7 @@ export class SourceEditor implements ProjectView { if (!info.path || this.path.endsWith(info.path)) { var numLines = this.editor.lineCount(); var line = info.line-1; - if (line < 0 || line >= numLines) line = 0; + if (isNaN(line) || line < 0 || line >= numLines) line = 0; this.addErrorMarker(line, info.msg); if (info.start != null) { var markOpts = {className:"mark-error", inclusiveLeft:true}; @@ -278,6 +279,7 @@ export class SourceEditor implements ProjectView { this.editor.clearGutter("gutter-clock"); var lstlines = this.sourcefile.lines || []; for (var info of lstlines) { + //if (info.path && info.path != this.path) continue; if (info.offset >= 0) { this.setGutter("gutter-offset", info.line-1, hex(info.offset&0xffff,4)); } @@ -579,7 +581,7 @@ export class ListingView extends DisassemblerView implements ProjectView { refreshListing() { // lookup corresponding assemblyfile for this file, using listing var lst = current_project.getListingForFile(this.path); - // TODO? + // TODO? this.assemblyfile = lst && (lst.assemblyfile || lst.sourcefile); } @@ -589,6 +591,7 @@ export class ListingView extends DisassemblerView implements ProjectView { if (!this.assemblyfile) return; var asmtext = this.assemblyfile.text; var disasmview = this.getDisasmView(); + // TODO: sometimes it picks one without a text file disasmview.setValue(asmtext); // go to PC if (!platform.saveState) return; diff --git a/src/platform/nes.ts b/src/platform/nes.ts index 87dc7441..fe7859bc 100644 --- a/src/platform/nes.ts +++ b/src/platform/nes.ts @@ -475,11 +475,12 @@ class JSNESPlatform extends Base6502Platform implements Platform, Probeable { } getMemoryMap = function() { return { main:[ - //{name:'Work RAM',start:0x0,size:0x800,type:'ram'}, + {name:'Zero Page RAM',start:0x0,size:0x100,type:'ram'}, {name:'OAM Buffer',start:0x200,size:0x100,type:'ram'}, + {name:'Work RAM',start:0x300,size:0x1000-0x300,type:'ram'}, {name:'PPU Registers',start:0x2000,last:0x2008,size:0x2000,type:'io'}, {name:'APU Registers',start:0x4000,last:0x4020,size:0x2000,type:'io'}, - {name:'Cartridge RAM',start:0x6000,size:0x2000,type:'ram'}, + {name:'Optional Cartridge RAM',start:0x6000,size:0x2000,type:'ram'}, ] } }; showHelp(tool:string, ident:string) { diff --git a/src/platform/vcs.ts b/src/platform/vcs.ts index 961c523d..c38d4b23 100644 --- a/src/platform/vcs.ts +++ b/src/platform/vcs.ts @@ -60,6 +60,7 @@ function getToolForFilename_vcs(fn: string) { if (fn.endsWith(".wiz")) return "wiz"; if (fn.endsWith(".bb") || fn.endsWith(".bas")) return "bataribasic"; if (fn.endsWith(".ca65")) return "ca65"; + if (fn.endsWith(".ecs")) return "ecs"; return "dasm"; } diff --git a/src/platform/williams.ts b/src/platform/williams.ts index 747abd3e..a760d6e4 100644 --- a/src/platform/williams.ts +++ b/src/platform/williams.ts @@ -16,7 +16,7 @@ var WilliamsPlatform = function(mainElement, proto, options) { this.__proto__ = new (proto ? proto : Base6809Platform)(); options = options || {}; - var isDefender = options.isDefender; + var isDefender = options?.isDefender; var SCREEN_HEIGHT = 304; var SCREEN_WIDTH = 256; @@ -304,7 +304,7 @@ var WilliamsPlatform = function(mainElement, proto, options) { workerchannel = new WorkerSoundChannel(worker); audio.master.addChannel(workerchannel); - let rotate = options.rotate == null ? -90 : parseFloat(options.rotate); + let rotate = options?.rotate == null ? -90 : parseFloat(options.rotate); video = new RasterVideo(mainElement, SCREEN_WIDTH, SCREEN_HEIGHT, { rotate }); video.create(); $(video.canvas).click(function(e) { diff --git a/src/test/testecs.ts b/src/test/testecs.ts new file mode 100644 index 00000000..0d64ea96 --- /dev/null +++ b/src/test/testecs.ts @@ -0,0 +1,190 @@ + +import { spawnSync } from "child_process"; +import { existsSync, readdirSync, readFileSync, writeFileSync } from "fs"; +import { describe } from "mocha"; +import { Bin, BoxConstraints, Packer } from "../common/ecs/binpack"; +import { ECSCompiler } from "../common/ecs/compiler"; +import { Dialect_CA65, EntityManager, SourceFileExport } from "../common/ecs/ecs"; + +function testCompiler() { + let em = new EntityManager(new Dialect_CA65()); // TODO + let c = new ECSCompiler(em, true); + try { + c.parseFile(` + // comment + /* + mju,fjeqowfjqewiofjqe + */ +component Kernel + lines: 0..255 + bgcolor: 0..255 +end +component Bitmap + data: array of 0..255 +end +component HasBitmap + bitmap: [Bitmap] +end + +system SimpleKernel +locals 8 +on preframe do with [Kernel] --- JUNK_AT_END + lda #5 + sta #6 +Label: +--- +end + +comment --- + +--- + +scope Root + entity kernel [Kernel] + const lines = 0xc0 + //const lines = $c0 + end + entity nobmp [Bitmap] + const data = [4] + end + entity bmp [Bitmap] + const data = [1,2,3] + end + entity player1 [HasBitmap] + init bitmap = #bmp + end +end + +`, 'foo.txt'); + //console.log('json', c.em.toJSON()); + let src = new SourceFileExport(); + c.exportToFile(src); + // TODO: test? + console.log(src.toString()); + return em; + } catch (e) { + console.log(e); + for (let err of c.errors) { + console.log(err); + } + console.log(c.tokens); + throw e; + } +} + +// TODO: files in markdown? +// TODO: jsr OperModeExecutionTree? + +describe('Tokenizer', function() { + it('Should use Compiler', function() { + testCompiler(); + }); +}); + +describe('Compiler', function() { + let testdir = './test/ecs/'; + let files = readdirSync(testdir).filter(f => f.endsWith('.ecs')); + files.forEach((ecsfn) => { + it('Should compile ' + ecsfn, function() { + let asmfn = ecsfn.replace('.ecs','.asm'); + let goodfn = ecsfn.replace('.ecs','.txt'); + let ecspath = testdir + ecsfn; + let goodpath = testdir + goodfn; + let dialect = new Dialect_CA65(); + let em = new EntityManager(dialect); + em.mainPath = ecspath; + let compiler = new ECSCompiler(em, true); + compiler.getImportFile = (path: string) => { + return readFileSync(testdir + path, 'utf-8'); + } + let code = readFileSync(ecspath, 'utf-8'); + var outtxt = ''; + try { + compiler.parseFile(code, ecspath); + // TODO: errors + let out = new SourceFileExport(); + em.exportToFile(out); + outtxt = out.toString(); + } catch (e) { + outtxt = e.toString(); + console.log(e); + } + if (compiler.errors.length) + outtxt = compiler.errors.map(e => `${e.line}:${e.msg}`).join('\n'); + let goodtxt = existsSync(goodpath) ? readFileSync(goodpath, 'utf-8') : ''; + if (outtxt.trim() != goodtxt.trim()) { + let asmpath = '/tmp/' + asmfn; + writeFileSync(asmpath, outtxt, 'utf-8'); + console.log(spawnSync('/usr/bin/diff', [goodpath, asmpath], {encoding:'utf-8'}).stdout); + throw new Error(`files different; to fix: cp ${asmpath} ${goodpath}`); + } + }); + }); +}); + +function testPack(bins: Bin[], boxes: BoxConstraints[]) { + let packer = new Packer(); + for (let bin of bins) packer.bins.push(bin); + for (let bc of boxes) packer.boxes.push(bc); + if (!packer.pack()) throw new Error('cannot pack') + //console.log(packer.boxes); + //console.log(packer.bins[0].free) +} + +describe('Box Packer', function() { + it('Should pack boxes', function() { + testPack( + [ + new Bin({ left:0, top:0, right:10, bottom:10 }) + ], [ + { width: 5, height: 5 }, + { width: 5, height: 5 }, + { width: 5, height: 5 }, + { width: 5, height: 5 }, + ] + ); + }); + it('Should pack top-aligned boxes', function() { + testPack( + [ + new Bin({ left:0, top:0, right:10, bottom:10 }) + ], [ + { width: 5, height: 7, top: 0 }, + { width: 5, height: 7, top: 1 }, + { width: 5, height: 1 }, + { width: 5, height: 1 }, + { width: 5, height: 3 }, + { width: 5, height: 1 }, + ] + ); + }); + it('Should pack unaligned boxes', function() { + testPack( + [ + new Bin({ left:0, top:0, right:10, bottom:10 }) + ], [ + { width: 3, height: 7, top: 0 }, + { width: 3, height: 7, top: 1 }, + { width: 3, height: 7, top: 2 }, + { width: 5, height: 1 }, + { width: 3, height: 1 }, + ] + ); + }); + it('Should pack multiple bins', function() { + testPack( + [ + new Bin({ left:0, top:0, right:10, bottom:10 }), + new Bin({ left:0, top:0, right:10, bottom:10 }) + ], [ + + { width: 5, height: 10 }, + { width: 5, height: 10 }, + { width: 5, height: 5 }, + { width: 5, height: 10 }, + { width: 5, height: 5 }, + ] + ); + }); +}); + diff --git a/src/test/testutil.ts b/src/test/testutil.ts new file mode 100644 index 00000000..2bba1e7f --- /dev/null +++ b/src/test/testutil.ts @@ -0,0 +1,177 @@ + +import assert from "assert"; +import { describe } from "mocha"; +import { EmuHalt } from "../common/emu" +import { lzgmini, isProbablyBinary } from "../common/util"; +import { Tokenizer, TokenType } from "../common/tokenizer"; + +var NES_CONIO_ROM_LZG = [ + 76, 90, 71, 0, 0, 160, 16, 0, 0, 11, 158, 107, 131, 223, 83, 1, 9, 17, 21, 22, 78, 69, 83, 26, 2, 1, 3, 0, 22, 6, 120, 216, + 162, 0, 134, 112, 134, 114, 134, 113, 134, 115, 154, 169, 32, 157, 0, 2, 157, 0, 3, 157, 0, 4, 232, 208, 244, 32, 134, 130, 32, 85, 129, 169, + 0, 162, 8, 133, 2, 134, 3, 32, 93, 128, 32, 50, 129, 32, 73, 129, 76, 0, 128, 72, 152, 72, 138, 72, 169, 1, 133, 112, 230, 107, 208, 2, + 230, 108, 32, 232, 129, 169, 32, 141, 6, 32, 169, 0, 22, 129, 141, 5, 22, 66, 104, 170, 104, 168, 104, 64, 160, 0, 240, 7, 169, 105, 162, 128, + 76, 4, 96, 96, 162, 0, 21, 23, 0, 32, 22, 195, 1, 22, 194, 63, 21, 37, 21, 134, 22, 197, 41, 21, 27, 173, 41, 96, 201, 4, 32, 169, + 129, 240, 3, 76, 158, 128, 76, 188, 128, 169, 184, 162, 130, 24, 109, 41, 96, 144, 1, 232, 160, 0, 32, 130, 129, 141, 7, 21, 36, 238, 41, 96, + 21, 32, 76, 140, 128, 21, 47, 33, 21, 246, 201, 17, 14, 61, 15, 21, 253, 227, 128, 76, 1, 129, 169, 169, 17, 24, 61, 209, 21, 125, 17, 2, + 180, 17, 10, 130, 5, 22, 201, 128, 17, 4, 172, 30, 141, 1, 32, 76, 46, 129, 22, 65, 96, 173, 0, 96, 174, 1, 96, 32, 112, 130, 173, 2, + 96, 174, 3, 21, 65, 160, 4, 76, 105, 128, 17, 3, 228, 188, 162, 130, 17, 2, 228, 169, 188, 133, 10, 169, 130, 133, 11, 169, 0, 133, 12, 169, + 96, 133, 13, 162, 214, 169, 255, 133, 18, 160, 0, 232, 240, 13, 177, 10, 145, 12, 200, 208, 246, 230, 11, 230, 13, 208, 240, 230, 18, 208, 239, 96, + 133, 10, 134, 11, 162, 0, 177, 10, 96, 208, 42, 162, 0, 138, 96, 240, 36, 22, 163, 30, 48, 28, 22, 227, 2, 16, 20, 22, 227, 14, 144, 12, + 21, 200, 176, 4, 22, 226, 162, 0, 169, 1, 96, 165, 115, 208, 252, 96, 169, 255, 197, 115, 240, 252, 96, 133, 118, 132, 116, 134, 117, 32, 193, 129, + 164, 113, 165, 116, 153, 0, 2, 165, 117, 153, 0, 3, 165, 118, 153, 0, 4, 200, 132, 113, 230, 115, 96, 164, 115, 208, 1, 96, 166, 114, 169, 14, + 141, 42, 96, 189, 0, 2, 141, 6, 32, 189, 0, 3, 22, 163, 4, 141, 7, 32, 232, 136, 240, 93, 17, 19, 14, 71, 17, 19, 14, 49, 17, 19, + 14, 27, 17, 19, 14, 5, 206, 42, 96, 208, 141, 134, 114, 132, 115, 96, 169, 0, 162, 0, 72, 165, 2, 56, 233, 2, 133, 2, 176, 2, 198, 3, + 160, 1, 138, 145, 2, 104, 136, 145, 2, 96, 169, 41, 133, 10, 169, 96, 17, 34, 41, 168, 162, 0, 240, 10, 145, 10, 200, 208, 251, 230, 11, 202, + 208, 246, 192, 2, 240, 5, 21, 70, 247, 96, 78, 111, 32, 99, 97, 114, 116, 32, 108, 111, 97, 100, 101, 100, 0, 1, 0, 16, 32, 17, 66, 184, + 141, 18, 96, 142, 19, 96, 141, 25, 96, 142, 26, 96, 136, 185, 255, 255, 141, 35, 22, 196, 34, 96, 140, 37, 96, 32, 255, 255, 160, 255, 208, 232, + 96, 17, 71, 230, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 10, 53, 128, 0, 128, 92, 128, + 17, 14, 14, 204, 204, 51, 51, 22, 106, 0, 24, 60, 126, 24, 22, 1, 22, 231, 16, 48, 127, 127, 48, 16, 0, 22, 230, 12, 18, 48, 124, 48, + 98, 252, 22, 231, 0, 0, 3, 62, 118, 54, 54, 22, 231, 127, 127, 17, 4, 80, 22, 230, 224, 224, 96, 22, 3, 22, 230, 24, 24, 24, 248, 248, + 21, 16, 22, 230, 204, 153, 51, 102, 22, 106, 51, 153, 204, 22, 107, 21, 27, 255, 255, 17, 4, 67, 22, 227, 3, 22, 13, 17, 6, 188, 22, 230, + 17, 2, 172, 22, 13, 31, 31, 22, 236, 255, 255, 22, 236, 31, 31, 17, 4, 136, 22, 227, 22, 1, 248, 248, 21, 5, 22, 233, 17, 14, 123, 17, + 3, 64, 22, 230, 17, 3, 64, 21, 248, 17, 8, 29, 21, 216, 17, 6, 88, 17, 3, 64, 22, 230, 240, 22, 13, 21, 233, 21, 243, 22, 230, 17, + 6, 16, 22, 226, 192, 192, 48, 48, 22, 106, 15, 22, 1, 21, 84, 22, 230, 17, 10, 4, 22, 226, 17, 10, 52, 22, 230, 17, 6, 16, 17, 10, + 44, 22, 6, 17, 35, 220, 0, 24, 22, 231, 102, 102, 17, 34, 107, 0, 22, 233, 255, 22, 33, 102, 22, 231, 24, 62, 96, 60, 6, 124, 21, 40, + 22, 229, 0, 102, 12, 24, 48, 102, 70, 22, 231, 60, 102, 60, 56, 103, 102, 63, 22, 231, 6, 12, 17, 36, 59, 22, 230, 21, 30, 48, 48, 24, + 12, 22, 231, 22, 97, 12, 21, 4, 22, 231, 0, 102, 60, 255, 60, 17, 2, 115, 22, 230, 24, 24, 126, 17, 35, 70, 22, 230, 17, 4, 173, 21, + 33, 22, 231, 126, 21, 205, 22, 231, 21, 80, 22, 232, 3, 6, 12, 24, 48, 96, 22, 231, 60, 102, 110, 118, 102, 102, 60, 22, 231, 24, 24, 56, + 24, 24, 24, 126, 22, 231, 60, 102, 6, 12, 48, 96, 22, 235, 28, 6, 21, 168, 22, 228, 6, 14, 30, 102, 127, 6, 6, 22, 231, 126, 96, 124, + 6, 21, 80, 22, 230, 60, 102, 96, 124, 17, 4, 88, 22, 228, 126, 102, 12, 17, 35, 83, 22, 230, 60, 21, 13, 21, 216, 22, 231, 62, 21, 240, + 22, 228, 17, 34, 124, 22, 66, 22, 236, 17, 2, 224, 22, 228, 14, 24, 48, 96, 48, 24, 14, 0, 22, 230, 17, 2, 239, 17, 4, 241, 22, 228, + 112, 24, 12, 6, 12, 24, 112, 22, 231, 17, 2, 192, 24, 21, 52, 22, 232, 110, 110, 96, 98, 17, 3, 248, 22, 227, 24, 60, 102, 126, 17, 34, + 228, 22, 230, 124, 102, 102, 22, 66, 22, 231, 60, 102, 96, 96, 96, 17, 4, 200, 22, 227, 120, 108, 21, 30, 108, 120, 22, 231, 126, 96, 96, 120, + 96, 96, 126, 22, 237, 96, 22, 231, 21, 48, 110, 17, 37, 8, 22, 227, 21, 46, 17, 3, 96, 22, 230, 60, 17, 99, 19, 21, 24, 22, 229, 30, + 12, 22, 1, 108, 56, 22, 231, 102, 108, 120, 112, 120, 108, 21, 40, 22, 229, 17, 132, 62, 126, 22, 231, 99, 119, 127, 107, 99, 99, 99, 22, 231, + 102, 118, 126, 126, 110, 17, 2, 88, 22, 229, 60, 102, 22, 2, 17, 35, 88, 22, 227, 17, 2, 205, 21, 49, 22, 231, 21, 144, 60, 14, 22, 231, + 21, 80, 17, 2, 96, 22, 230, 60, 102, 96, 60, 17, 37, 208, 22, 227, 17, 163, 13, 17, 34, 200, 22, 229, 21, 111, 17, 5, 208, 22, 232, 60, + 17, 5, 16, 22, 225, 99, 99, 99, 107, 127, 119, 99, 22, 231, 21, 77, 60, 17, 3, 248, 22, 230, 21, 1, 17, 4, 64, 22, 227, 126, 17, 67, + 159, 126, 22, 231, 60, 48, 22, 2, 60, 22, 231, 96, 48, 24, 12, 6, 3, 0, 22, 231, 60, 17, 34, 32, 12, 21, 24, 22, 229, 17, 34, 193, + 17, 68, 244, 22, 229, 22, 3, 17, 165, 133, 22, 225, 17, 134, 203, 22, 230, 21, 58, 6, 62, 102, 62, 22, 232, 96, 17, 66, 176, 124, 22, 232, + 0, 60, 96, 96, 96, 17, 66, 144, 22, 229, 6, 21, 31, 21, 96, 22, 230, 0, 60, 102, 126, 21, 216, 22, 228, 14, 24, 62, 17, 3, 84, 22, + 230, 0, 21, 95, 6, 124, 22, 231, 17, 3, 80, 102, 17, 5, 88, 22, 225, 24, 0, 56, 17, 34, 240, 22, 231, 6, 0, 6, 22, 1, 60, 22, + 231, 96, 96, 108, 17, 34, 128, 22, 231, 21, 30, 21, 160, 22, 230, 0, 102, 127, 127, 107, 99, 22, 233, 17, 2, 79, 21, 32, 22, 231, 17, 34, + 210, 17, 4, 152, 22, 228, 17, 36, 242, 22, 232, 17, 3, 144, 6, 22, 232, 124, 17, 66, 226, 21, 160, 22, 228, 17, 131, 225, 22, 232, 17, 130, + 127, 17, 98, 112, 22, 230, 17, 35, 226, 17, 34, 0, 22, 233, 60, 17, 2, 240, 22, 230, 99, 107, 127, 62, 17, 226, 24, 22, 230, 17, 35, 241, + 22, 234, 21, 47, 12, 120, 22, 232, 126, 12, 24, 48, 17, 98, 194, 22, 228, 28, 48, 24, 112, 24, 48, 28, 22, 231, 17, 164, 159, 22, 3, 22, + 227, 56, 12, 24, 14, 24, 12, 56, 0, 22, 230, 51, 255, 204, 17, 35, 206, 22, 230, 22, 14, 17, 194, 92, 22, 10, 17, 236, 246, 204, 204, 255, + 231, 195, 129, 231, 22, 1, 22, 231, 239, 207, 128, 128, 207, 239, 255, 22, 230, 243, 237, 207, 131, 207, 157, 3, 22, 231, 255, 255, 252, 193, 137, 201, + 201, 22, 231, 128, 128, 17, 4, 80, 22, 230, 31, 31, 159, 22, 3, 22, 230, 231, 231, 231, 7, 7, 21, 16, 22, 230, 17, 236, 246, 204, 17, 237, + 246, 51, 153, 17, 227, 11, 17, 4, 67, 22, 227, 252, 22, 13, 17, 6, 188, 22, 230, 17, 2, 172, 22, 13, 224, 224, 22, 236, 0, 0, 22, 236, + 224, 224, 17, 4, 136, 22, 227, 22, 1, 7, 7, 21, 5, 22, 233, 17, 14, 123, 17, 3, 64, 22, 230, 17, 3, 64, 21, 248, 17, 8, 29, 21, + 216, 17, 6, 88, 17, 3, 64, 22, 230, 17, 226, 124, 22, 10, 17, 238, 244, 22, 226, 17, 6, 16, 22, 226, 63, 63, 207, 207, 22, 106, 17, 226, + 192, 21, 84, 22, 230, 17, 10, 4, 17, 230, 220, 17, 14, 60, 17, 234, 252, 17, 6, 44, 22, 6, 17, 35, 220, 255, 231, 22, 231, 153, 153, 17, + 34, 107, 255, 22, 233, 0, 22, 33, 153, 22, 231, 231, 193, 159, 195, 249, 131, 21, 40, 22, 229, 255, 153, 243, 231, 207, 153, 185, 22, 231, 195, 153, + 195, 199, 152, 153, 192, 22, 231, 249, 243, 17, 36, 59, 22, 230, 21, 30, 207, 207, 231, 243, 22, 231, 22, 97, 243, 21, 4, 22, 231, 255, 153, 195, + 0, 195, 17, 2, 115, 22, 230, 231, 231, 129, 17, 35, 70, 22, 230, 17, 4, 173, 21, 33, 22, 231, 129, 21, 205, 22, 231, 21, 80, 22, 232, 252, + 249, 243, 231, 207, 159, 22, 231, 195, 153, 145, 137, 153, 153, 195, 22, 231, 231, 231, 199, 231, 231, 231, 129, 22, 231, 195, 153, 249, 243, 207, 159, 22, + 235, 227, 249, 21, 168, 22, 228, 249, 241, 225, 153, 128, 249, 249, 22, 231, 129, 159, 131, 249, 21, 80, 22, 230, 195, 153, 159, 131, 17, 4, 88, 22, + 228, 129, 153, 243, 17, 35, 83, 22, 230, 195, 21, 13, 21, 216, 22, 231, 193, 21, 240, 22, 228, 17, 34, 124, 22, 66, 22, 236, 17, 2, 224, 22, + 228, 241, 231, 207, 159, 207, 231, 241, 255, 22, 230, 17, 2, 239, 17, 4, 241, 22, 228, 143, 231, 243, 249, 243, 231, 143, 22, 231, 17, 2, 192, 231, + 21, 52, 22, 232, 145, 145, 159, 157, 17, 3, 248, 22, 227, 231, 195, 153, 129, 17, 34, 228, 22, 230, 131, 153, 153, 22, 66, 22, 231, 195, 153, 159, + 159, 159, 17, 4, 200, 22, 227, 135, 147, 21, 30, 147, 135, 22, 231, 129, 159, 159, 135, 159, 159, 129, 22, 237, 159, 22, 231, 21, 48, 145, 17, 37, + 8, 22, 227, 21, 46, 17, 3, 96, 22, 230, 195, 17, 99, 19, 21, 24, 22, 229, 225, 243, 22, 1, 147, 199, 22, 231, 153, 147, 135, 143, 135, 147, + 21, 40, 22, 229, 17, 132, 62, 129, 22, 231, 156, 136, 128, 148, 156, 156, 156, 22, 231, 153, 137, 129, 129, 145, 17, 2, 88, 22, 229, 195, 153, 22, + 2, 17, 35, 88, 22, 227, 17, 2, 205, 21, 49, 22, 231, 21, 144, 195, 241, 22, 231, 21, 80, 17, 2, 96, 22, 230, 195, 153, 159, 195, 17, 37, + 208, 22, 227, 17, 163, 13, 17, 34, 200, 22, 229, 21, 111, 17, 5, 208, 22, 232, 195, 17, 5, 16, 22, 225, 156, 156, 156, 148, 128, 136, 156, 22, + 231, 21, 77, 195, 17, 3, 248, 22, 230, 21, 1, 17, 4, 64, 22, 227, 129, 17, 67, 159, 129, 22, 231, 195, 207, 22, 2, 195, 22, 231, 159, 207, + 231, 243, 249, 252, 255, 22, 231, 195, 17, 34, 32, 243, 21, 24, 22, 229, 17, 34, 193, 17, 68, 244, 22, 229, 22, 3, 17, 165, 133, 22, 225, 17, + 134, 203, 22, 230, 21, 58, 249, 193, 153, 193, 22, 232, 159, 17, 66, 176, 131, 22, 232, 255, 195, 159, 159, 159, 17, 66, 144, 22, 229, 249, 21, 31, + 21, 96, 22, 230, 255, 195, 153, 129, 21, 216, 22, 228, 241, 231, 193, 17, 3, 84, 22, 230, 255, 21, 95, 249, 131, 22, 231, 17, 3, 80, 153, 17, + 5, 88, 22, 225, 231, 255, 199, 17, 34, 240, 22, 231, 249, 255, 249, 22, 1, 195, 22, 231, 159, 159, 147, 17, 34, 128, 22, 231, 21, 30, 21, 160, + 22, 230, 255, 153, 128, 128, 148, 156, 22, 233, 17, 2, 79, 21, 32, 22, 231, 17, 34, 210, 17, 4, 152, 22, 228, 17, 36, 242, 22, 232, 17, 3, + 144, 249, 22, 232, 131, 17, 66, 226, 21, 160, 22, 228, 17, 131, 225, 22, 232, 17, 130, 127, 17, 98, 112, 22, 230, 17, 35, 226, 17, 34, 0, 22, + 233, 195, 17, 2, 240, 22, 230, 156, 148, 128, 193, 17, 226, 24, 22, 230, 17, 35, 241, 22, 234, 21, 47, 243, 135, 22, 232, 129, 243, 231, 207, 17, + 98, 194, 22, 228, 227, 207, 231, 143, 231, 207, 227, 22, 231, 17, 164, 159, 22, 3, 22, 227, 199, 243, 231, 241, 231, 243, 199, 255, 22, 230, 204, 0, + 51, 17, 35, 206, 22, 230, 22, 14, 9, 19, 0, 13, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, + 22, 31, 22, 31, 22, 31, 22, 31, 22, 31, 22, 30, 22, 28 +]; + +describe('LZG', function () { + it('Should decode LZG', function () { + var rom = new Uint8Array(new lzgmini().decode(NES_CONIO_ROM_LZG)); + assert.equal(40976, rom.length); + }); +}); + +describe('string functions', function () { + it('Should detect binary', function () { + assert.ok(!isProbablyBinary(null, [32, 32, 10, 13, 9, 32, 32, 10, 13])); + assert.ok(isProbablyBinary(null, [32, 32, 0x80])); + assert.ok(!isProbablyBinary(null, [32, 32, 0xc1, 0x81, 32, 32, 10, 13])); + assert.ok(isProbablyBinary(null, NES_CONIO_ROM_LZG)); + assert.ok(isProbablyBinary('test.bin')); + assert.ok(isProbablyBinary('test.chr')); + assert.ok(!isProbablyBinary('test.txt')); + assert.ok(isProbablyBinary('test.dat')); + assert.ok(!isProbablyBinary(null, [0x20, 0xa9, 0x20, 0x31, 0x39, 0x38, 0x32])); + assert.ok(isProbablyBinary(null, [0x00, 0x00])); // 00 is binary + assert.ok(isProbablyBinary(null, [0x9f])); // 9f is binary + assert.ok(isProbablyBinary(null, [0xff])); // FF is binary + assert.ok(isProbablyBinary(null, [0xf0, 0x12])); // ran out of data + }); +}); + +describe('EmuHalt', function () { + it('Should detect emuhalt', function () { + var e = new EmuHalt("?"); + assert.ok(e instanceof EmuHalt); + assert.ok(e.hasOwnProperty('$loc')); + }); +}); + +describe('Tokenizer', function () { + it('Should tokenize', function () { + var t = new Tokenizer(); + t.setTokenRules([ + { type: 'ident', regex: /[$A-Za-z_][A-Za-z0-9_-]*/ }, + { type: 'delim', regex: /[\(\)\{\}\[\]]/ }, + { type: 'qstring', regex: /".*?"/ }, + { type: 'integer', regex: /[-]?\d+/ }, + { type: 'eol', regex: /\n+/ }, + { type: 'ignore', regex: /\s+/ }, + ]); + t.tokenizeFile("a\n{\"key\" value\n \"number\" 531\n\n \"f\" (fn [x] (+ x 2))}\n", "test.file"); + assert.strictEqual(t.tokens.map(t => t.type).join(' '), + 'ident delim qstring ident qstring integer qstring delim ident delim ident delim delim catch-all ident integer delim delim delim eof'); + assert.strictEqual(t.tokens.map(t => t.str).join(' '), + 'a { "key" value "number" 531 "f" ( fn [ x ] ( + x 2 ) ) } '); + assert.strictEqual(t.tokens.filter(t => t.eol).map(t => t.str).join(' '), + 'a value 531 } '); + assert.strictEqual(t.tokens.map(t => t.$loc.line).join(' '), + '1 2 2 2 3 3 5 5 5 5 5 5 5 5 5 5 5 5 5 6'); + assert.strictEqual(20, t.tokens.length); + assert.strictEqual('a', t.peekToken().str); + assert.strictEqual('a', t.expectToken('a').str); + t.expectTokens(['foo', '{']); + t.pushbackToken(t.consumeToken()); + assert.strictEqual('"key"', t.consumeToken().str); + assert.deepStrictEqual({ 'value': true }, t.parseModifiers(['foo', 'value', 'bar'])); + assert.deepStrictEqual([], t.errors); + t = new Tokenizer(); + t.setTokenRules([ + { type: 'ident', regex: /[$A-Za-z_][A-Za-z0-9_-]*/ }, + { type: 'delim', regex: /[\(\)\{\}\[\]]/ }, + { type: 'code-fragment', regex: /---[^-]*---/ }, + { type: 'ignore', regex: /\s+/ }, + ]); + t.tokenizeFile("key value ---\nthis is\na fragment\n--- foo", "test.file"); + assert.strictEqual(t.tokens.map(t => t.type).join(' '), + 'ident ident code-fragment ident eof'); + }); +}); diff --git a/src/worker/tools/cc65.ts b/src/worker/tools/cc65.ts index 5f1f2a71..7d29999b 100644 --- a/src/worker/tools/cc65.ts +++ b/src/worker/tools/cc65.ts @@ -1,7 +1,7 @@ -import { getRootBasePlatform } from "../../common/util"; +import { getFilenamePrefix, getRootBasePlatform } from "../../common/util"; import { CodeListingMap, WorkerError } from "../../common/workertypes"; -import { re_crlf, BuildStepResult, anyTargetChanged, execMain, gatherFiles, msvcErrorMatcher, populateEntry, populateExtraFiles, populateFiles, print_fn, putWorkFile, setupFS, staleFiles, BuildStep, emglobal, loadNative, moduleInstFn, fixParamsWithDefines, store, makeErrorMatcher } from "../workermain"; +import { re_crlf, BuildStepResult, anyTargetChanged, execMain, gatherFiles, msvcErrorMatcher, populateEntry, populateExtraFiles, populateFiles, print_fn, putWorkFile, setupFS, staleFiles, BuildStep, emglobal, loadNative, moduleInstFn, fixParamsWithDefines, store, makeErrorMatcher, getWorkFileAsString } from "../workermain"; import { EmscriptenModule } from "../workermain" @@ -18,21 +18,30 @@ import { EmscriptenModule } from "../workermain" 00B726 1 xx xx IBSECSZ: .res 2 00BA2F 1 2A 2B E8 2C HEX "2A2BE82C2D2E2F303132F0F133343536" */ -function parseCA65Listing(code: string, symbols, params, dbg: boolean) { +function parseCA65Listing(code: string, symbols, params, dbg: boolean, listings?: CodeListingMap) { var segofs = 0; var offset = 0; var dbgLineMatch = /^([0-9A-F]+)([r]?)\s+(\d+)\s+[.]dbg\s+(\w+), "([^"]+)", (.+)/; var funcLineMatch = /"(\w+)", (\w+), "(\w+)"/; var insnLineMatch = /^([0-9A-F]+)([r]?)\s{1,2}(\d+)\s{1,2}([0-9A-Frx ]{11})\s+(.*)/; var segMatch = /[.]segment\s+"(\w+)"/i; - var lines = []; + var origlines = []; + var lines = origlines; var linenum = 0; + let curpath = ''; // TODO: only does .c functions, not all .s files for (var line of code.split(re_crlf)) { var dbgm = dbgLineMatch.exec(line); if (dbgm && dbgm[1]) { var dbgtype = dbgm[4]; offset = parseInt(dbgm[1], 16); + curpath = dbgm[5]; + // new file? + if (curpath && listings) { + let l = listings[curpath]; + if (!l) l = listings[curpath] = {lines:[]}; + lines = l.lines; + } if (dbgtype == 'func') { var funcm = funcLineMatch.exec(dbgm[6]); if (funcm) { @@ -45,9 +54,9 @@ function parseCA65Listing(code: string, symbols, params, dbg: boolean) { } } if (dbg && dbgm && dbgtype == 'line') { - //console.log(dbgm[6], offset, segofs); + //console.log(dbgm[5], dbgm[6], offset, segofs); lines.push({ - // TODO: sourcefile + path: dbgm[5], line: parseInt(dbgm[6]), offset: offset + segofs, insns: null @@ -65,6 +74,7 @@ function parseCA65Listing(code: string, symbols, params, dbg: boolean) { linenum--; } else if (!dbg) { lines.push({ + path: curpath, line: linenum, offset: offset + segofs, insns: insns, @@ -90,7 +100,7 @@ function parseCA65Listing(code: string, symbols, params, dbg: boolean) { } } } - return lines; + return origlines; } export function assembleCA65(step: BuildStep): BuildStepResult { @@ -118,8 +128,12 @@ export function assembleCA65(step: BuildStep): BuildStepResult { args.unshift.apply(args, ["-D", "__MAIN__=1"]); } execMain(step, CA65, args); - if (errors.length) - return { errors: errors }; + if (errors.length) { + // TODO? + let listings : CodeListingMap = {}; + listings[step.path] = { lines:[], text:getWorkFileAsString(step.path) }; + return { errors, listings }; + } objout = FS.readFile(objpath, { encoding: 'binary' }); lstout = FS.readFile(lstpath, { encoding: 'utf8' }); putWorkFile(objpath, objout); @@ -216,15 +230,25 @@ export function linkLD65(step: BuildStep): BuildStepResult { if (fn.endsWith('.lst')) { var lstout = FS.readFile(fn, { encoding: 'utf8' }); lstout = lstout.split('\n\n')[1] || lstout; // remove header - var asmlines = parseCA65Listing(lstout, symbolmap, params, false); - var srclines = parseCA65Listing(lstout, symbolmap, params, true); putWorkFile(fn, lstout); - // TODO: you have to get rid of all source lines to get asm listing - listings[fn] = { - asmlines: srclines.length ? asmlines : null, - lines: srclines.length ? srclines : asmlines, - text: lstout - }; + console.log(step); + let isECS = step.debuginfo?.entities != null; // TODO + if (isECS) { + var asmlines = []; + var srclines = parseCA65Listing(lstout, symbolmap, params, true, listings); + listings[fn] = { + lines: [], + text: lstout + } + } else { + var asmlines = parseCA65Listing(lstout, symbolmap, params, false); + var srclines = parseCA65Listing(lstout, symbolmap, params, true); // TODO: listings param for ecs + listings[fn] = { + asmlines: srclines.length ? asmlines : null, + lines: srclines.length ? srclines : asmlines, + text: lstout + } + } } } return { diff --git a/src/worker/tools/ecs.ts b/src/worker/tools/ecs.ts new file mode 100644 index 00000000..6798ca5a --- /dev/null +++ b/src/worker/tools/ecs.ts @@ -0,0 +1,51 @@ +import { ECSCompiler } from "../../common/ecs/compiler"; +import { Dialect_CA65, ECSError, EntityManager } from "../../common/ecs/ecs"; +import { CompileError } from "../../common/tokenizer"; +import { CodeListingMap } from "../../common/workertypes"; +import { BuildStep, BuildStepResult, fixParamsWithDefines, gatherFiles, getWorkFileAsString, putWorkFile, staleFiles } from "../workermain"; + +export function assembleECS(step: BuildStep): BuildStepResult { + let em = new EntityManager(new Dialect_CA65()); // TODO + let compiler = new ECSCompiler(em, true); + compiler.getImportFile = (path: string) => { + return getWorkFileAsString(path); + } + gatherFiles(step, { mainFilePath: "main.ecs" }); + if (step.mainfile) em.mainPath = step.path; + var destpath = step.prefix + '.ca65'; + if (staleFiles(step, [destpath])) { + let code = getWorkFileAsString(step.path); + fixParamsWithDefines(step.path, step.params); + try { + compiler.includeDebugInfo = true; + compiler.parseFile(code, step.path); + let outtext = compiler.export().toString(); + putWorkFile(destpath, outtext); + var listings: CodeListingMap = {}; + listings[destpath] = {lines:[], text:outtext} // TODO + var debuginfo = compiler.em.getDebugTree(); + } catch (e) { + if (e instanceof ECSError) { + compiler.addError(e.message, e.$loc); + for (let obj of e.$sources) { + let name = (obj as any).event; + if (name == 'start') break; + compiler.addError(`... ${name}`, obj.$loc); // TODO? + } + return { errors: compiler.errors }; + } else if (e instanceof CompileError) { + return { errors: compiler.errors }; + } else { + throw e; + } + } + return { + nexttool: "ca65", + path: destpath, + args: [destpath], + files: [destpath].concat(step.files), + listings, + debuginfo + }; + } +} diff --git a/src/worker/workermain.ts b/src/worker/workermain.ts index 5def55d0..1639b165 100644 --- a/src/worker/workermain.ts +++ b/src/worker/workermain.ts @@ -431,6 +431,7 @@ export interface BuildStep extends WorkerBuildStep { code? prefix? maxts? + debuginfo? }; /// @@ -518,6 +519,11 @@ class Builder { } if (step.result) { (step.result as any).params = step.params; // TODO: type check + if (step.debuginfo) { + let r = step.result as any; // TODO + if (!r.debuginfo) r.debuginfo = {}; + Object.assign(r.debuginfo, step.debuginfo); + } // errors? return them if ('errors' in step.result && step.result.errors.length) { applyDefaultErrorPath(step.result.errors, step.path); @@ -529,6 +535,7 @@ class Builder { } // combine files with a link tool? if ('linktool' in step.result) { + // add to existing link step if (linkstep) { linkstep.files = linkstep.files.concat(step.result.files); linkstep.args = linkstep.args.concat(step.result.args); @@ -540,6 +547,7 @@ class Builder { args:step.result.args }; } + linkstep.debuginfo = step.debuginfo; // TODO: multiple debuginfos } // process with another tool? if ('nexttool' in step.result) { @@ -1102,6 +1110,7 @@ import * as z80 from './tools/z80' import * as x86 from './tools/x86' import * as arm from './tools/arm' import * as script from './tools/script' +import * as ecs from './tools/ecs' var TOOLS = { 'dasm': dasm.assembleDASM, @@ -1137,7 +1146,8 @@ var TOOLS = { 'wiz': misc.compileWiz, 'armips': arm.assembleARMIPS, 'vasmarm': arm.assembleVASMARM, - 'js': script.runJavascript, + //'js': script.runJavascript, + 'ecs': ecs.assembleECS, } var TOOL_PRELOADFS = { @@ -1166,6 +1176,8 @@ var TOOL_PRELOADFS = { 'fastbasic': '65-atari8', 'silice': 'Silice', 'wiz': 'wiz', + 'ecs-vcs': '65-none', // TODO: support multiple platforms + 'ecs-nes': '65-nes', // TODO: support multiple platforms } //const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay)); // for testing diff --git a/test/cli/testplatforms.js b/test/cli/testplatforms.js index 8889f072..64e336f9 100644 --- a/test/cli/testplatforms.js +++ b/test/cli/testplatforms.js @@ -196,9 +196,9 @@ async function testPlatform(platid, romname, maxframes, callback) { assert.equal(clks, proberec.countClocks()); } // debug tree - if (platform.getDebugTree) { + if (platform.getDebugTree != null) { var dbgtree = platform.getDebugTree(); - JSON.stringify(dbgtree); + if (dbgtree != null) JSON.stringify(dbgtree); } // misc assert.ok(platform.getDefaultExtension().startsWith('.')); diff --git a/test/cli/testutil.js b/test/cli/testutil.js deleted file mode 100644 index cfa912a1..00000000 --- a/test/cli/testutil.js +++ /dev/null @@ -1,138 +0,0 @@ - -var vm = require('vm'); -var fs = require('fs'); -var assert = require('assert'); - -var emu = require("gen/common/emu.js"); -var util = require("gen/common/util.js"); -var nes = require("gen/platform/nes.js"); - -var NES_CONIO_ROM_LZG = [ - 76,90,71,0,0,160,16,0,0,11,158,107,131,223,83,1,9,17,21,22,78,69,83,26,2,1,3,0,22,6,120,216, - 162,0,134,112,134,114,134,113,134,115,154,169,32,157,0,2,157,0,3,157,0,4,232,208,244,32,134,130,32,85,129,169, - 0,162,8,133,2,134,3,32,93,128,32,50,129,32,73,129,76,0,128,72,152,72,138,72,169,1,133,112,230,107,208,2, - 230,108,32,232,129,169,32,141,6,32,169,0,22,129,141,5,22,66,104,170,104,168,104,64,160,0,240,7,169,105,162,128, - 76,4,96,96,162,0,21,23,0,32,22,195,1,22,194,63,21,37,21,134,22,197,41,21,27,173,41,96,201,4,32,169, - 129,240,3,76,158,128,76,188,128,169,184,162,130,24,109,41,96,144,1,232,160,0,32,130,129,141,7,21,36,238,41,96, - 21,32,76,140,128,21,47,33,21,246,201,17,14,61,15,21,253,227,128,76,1,129,169,169,17,24,61,209,21,125,17,2, - 180,17,10,130,5,22,201,128,17,4,172,30,141,1,32,76,46,129,22,65,96,173,0,96,174,1,96,32,112,130,173,2, - 96,174,3,21,65,160,4,76,105,128,17,3,228,188,162,130,17,2,228,169,188,133,10,169,130,133,11,169,0,133,12,169, - 96,133,13,162,214,169,255,133,18,160,0,232,240,13,177,10,145,12,200,208,246,230,11,230,13,208,240,230,18,208,239,96, - 133,10,134,11,162,0,177,10,96,208,42,162,0,138,96,240,36,22,163,30,48,28,22,227,2,16,20,22,227,14,144,12, - 21,200,176,4,22,226,162,0,169,1,96,165,115,208,252,96,169,255,197,115,240,252,96,133,118,132,116,134,117,32,193,129, - 164,113,165,116,153,0,2,165,117,153,0,3,165,118,153,0,4,200,132,113,230,115,96,164,115,208,1,96,166,114,169,14, - 141,42,96,189,0,2,141,6,32,189,0,3,22,163,4,141,7,32,232,136,240,93,17,19,14,71,17,19,14,49,17,19, - 14,27,17,19,14,5,206,42,96,208,141,134,114,132,115,96,169,0,162,0,72,165,2,56,233,2,133,2,176,2,198,3, - 160,1,138,145,2,104,136,145,2,96,169,41,133,10,169,96,17,34,41,168,162,0,240,10,145,10,200,208,251,230,11,202, - 208,246,192,2,240,5,21,70,247,96,78,111,32,99,97,114,116,32,108,111,97,100,101,100,0,1,0,16,32,17,66,184, - 141,18,96,142,19,96,141,25,96,142,26,96,136,185,255,255,141,35,22,196,34,96,140,37,96,32,255,255,160,255,208,232, - 96,17,71,230,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,10,53,128,0,128,92,128, - 17,14,14,204,204,51,51,22,106,0,24,60,126,24,22,1,22,231,16,48,127,127,48,16,0,22,230,12,18,48,124,48, - 98,252,22,231,0,0,3,62,118,54,54,22,231,127,127,17,4,80,22,230,224,224,96,22,3,22,230,24,24,24,248,248, - 21,16,22,230,204,153,51,102,22,106,51,153,204,22,107,21,27,255,255,17,4,67,22,227,3,22,13,17,6,188,22,230, - 17,2,172,22,13,31,31,22,236,255,255,22,236,31,31,17,4,136,22,227,22,1,248,248,21,5,22,233,17,14,123,17, - 3,64,22,230,17,3,64,21,248,17,8,29,21,216,17,6,88,17,3,64,22,230,240,22,13,21,233,21,243,22,230,17, - 6,16,22,226,192,192,48,48,22,106,15,22,1,21,84,22,230,17,10,4,22,226,17,10,52,22,230,17,6,16,17,10, - 44,22,6,17,35,220,0,24,22,231,102,102,17,34,107,0,22,233,255,22,33,102,22,231,24,62,96,60,6,124,21,40, - 22,229,0,102,12,24,48,102,70,22,231,60,102,60,56,103,102,63,22,231,6,12,17,36,59,22,230,21,30,48,48,24, - 12,22,231,22,97,12,21,4,22,231,0,102,60,255,60,17,2,115,22,230,24,24,126,17,35,70,22,230,17,4,173,21, - 33,22,231,126,21,205,22,231,21,80,22,232,3,6,12,24,48,96,22,231,60,102,110,118,102,102,60,22,231,24,24,56, - 24,24,24,126,22,231,60,102,6,12,48,96,22,235,28,6,21,168,22,228,6,14,30,102,127,6,6,22,231,126,96,124, - 6,21,80,22,230,60,102,96,124,17,4,88,22,228,126,102,12,17,35,83,22,230,60,21,13,21,216,22,231,62,21,240, - 22,228,17,34,124,22,66,22,236,17,2,224,22,228,14,24,48,96,48,24,14,0,22,230,17,2,239,17,4,241,22,228, - 112,24,12,6,12,24,112,22,231,17,2,192,24,21,52,22,232,110,110,96,98,17,3,248,22,227,24,60,102,126,17,34, - 228,22,230,124,102,102,22,66,22,231,60,102,96,96,96,17,4,200,22,227,120,108,21,30,108,120,22,231,126,96,96,120, - 96,96,126,22,237,96,22,231,21,48,110,17,37,8,22,227,21,46,17,3,96,22,230,60,17,99,19,21,24,22,229,30, - 12,22,1,108,56,22,231,102,108,120,112,120,108,21,40,22,229,17,132,62,126,22,231,99,119,127,107,99,99,99,22,231, - 102,118,126,126,110,17,2,88,22,229,60,102,22,2,17,35,88,22,227,17,2,205,21,49,22,231,21,144,60,14,22,231, - 21,80,17,2,96,22,230,60,102,96,60,17,37,208,22,227,17,163,13,17,34,200,22,229,21,111,17,5,208,22,232,60, - 17,5,16,22,225,99,99,99,107,127,119,99,22,231,21,77,60,17,3,248,22,230,21,1,17,4,64,22,227,126,17,67, - 159,126,22,231,60,48,22,2,60,22,231,96,48,24,12,6,3,0,22,231,60,17,34,32,12,21,24,22,229,17,34,193, - 17,68,244,22,229,22,3,17,165,133,22,225,17,134,203,22,230,21,58,6,62,102,62,22,232,96,17,66,176,124,22,232, - 0,60,96,96,96,17,66,144,22,229,6,21,31,21,96,22,230,0,60,102,126,21,216,22,228,14,24,62,17,3,84,22, - 230,0,21,95,6,124,22,231,17,3,80,102,17,5,88,22,225,24,0,56,17,34,240,22,231,6,0,6,22,1,60,22, - 231,96,96,108,17,34,128,22,231,21,30,21,160,22,230,0,102,127,127,107,99,22,233,17,2,79,21,32,22,231,17,34, - 210,17,4,152,22,228,17,36,242,22,232,17,3,144,6,22,232,124,17,66,226,21,160,22,228,17,131,225,22,232,17,130, - 127,17,98,112,22,230,17,35,226,17,34,0,22,233,60,17,2,240,22,230,99,107,127,62,17,226,24,22,230,17,35,241, - 22,234,21,47,12,120,22,232,126,12,24,48,17,98,194,22,228,28,48,24,112,24,48,28,22,231,17,164,159,22,3,22, - 227,56,12,24,14,24,12,56,0,22,230,51,255,204,17,35,206,22,230,22,14,17,194,92,22,10,17,236,246,204,204,255, - 231,195,129,231,22,1,22,231,239,207,128,128,207,239,255,22,230,243,237,207,131,207,157,3,22,231,255,255,252,193,137,201, - 201,22,231,128,128,17,4,80,22,230,31,31,159,22,3,22,230,231,231,231,7,7,21,16,22,230,17,236,246,204,17,237, - 246,51,153,17,227,11,17,4,67,22,227,252,22,13,17,6,188,22,230,17,2,172,22,13,224,224,22,236,0,0,22,236, - 224,224,17,4,136,22,227,22,1,7,7,21,5,22,233,17,14,123,17,3,64,22,230,17,3,64,21,248,17,8,29,21, - 216,17,6,88,17,3,64,22,230,17,226,124,22,10,17,238,244,22,226,17,6,16,22,226,63,63,207,207,22,106,17,226, - 192,21,84,22,230,17,10,4,17,230,220,17,14,60,17,234,252,17,6,44,22,6,17,35,220,255,231,22,231,153,153,17, - 34,107,255,22,233,0,22,33,153,22,231,231,193,159,195,249,131,21,40,22,229,255,153,243,231,207,153,185,22,231,195,153, - 195,199,152,153,192,22,231,249,243,17,36,59,22,230,21,30,207,207,231,243,22,231,22,97,243,21,4,22,231,255,153,195, - 0,195,17,2,115,22,230,231,231,129,17,35,70,22,230,17,4,173,21,33,22,231,129,21,205,22,231,21,80,22,232,252, - 249,243,231,207,159,22,231,195,153,145,137,153,153,195,22,231,231,231,199,231,231,231,129,22,231,195,153,249,243,207,159,22, - 235,227,249,21,168,22,228,249,241,225,153,128,249,249,22,231,129,159,131,249,21,80,22,230,195,153,159,131,17,4,88,22, - 228,129,153,243,17,35,83,22,230,195,21,13,21,216,22,231,193,21,240,22,228,17,34,124,22,66,22,236,17,2,224,22, - 228,241,231,207,159,207,231,241,255,22,230,17,2,239,17,4,241,22,228,143,231,243,249,243,231,143,22,231,17,2,192,231, - 21,52,22,232,145,145,159,157,17,3,248,22,227,231,195,153,129,17,34,228,22,230,131,153,153,22,66,22,231,195,153,159, - 159,159,17,4,200,22,227,135,147,21,30,147,135,22,231,129,159,159,135,159,159,129,22,237,159,22,231,21,48,145,17,37, - 8,22,227,21,46,17,3,96,22,230,195,17,99,19,21,24,22,229,225,243,22,1,147,199,22,231,153,147,135,143,135,147, - 21,40,22,229,17,132,62,129,22,231,156,136,128,148,156,156,156,22,231,153,137,129,129,145,17,2,88,22,229,195,153,22, - 2,17,35,88,22,227,17,2,205,21,49,22,231,21,144,195,241,22,231,21,80,17,2,96,22,230,195,153,159,195,17,37, - 208,22,227,17,163,13,17,34,200,22,229,21,111,17,5,208,22,232,195,17,5,16,22,225,156,156,156,148,128,136,156,22, - 231,21,77,195,17,3,248,22,230,21,1,17,4,64,22,227,129,17,67,159,129,22,231,195,207,22,2,195,22,231,159,207, - 231,243,249,252,255,22,231,195,17,34,32,243,21,24,22,229,17,34,193,17,68,244,22,229,22,3,17,165,133,22,225,17, - 134,203,22,230,21,58,249,193,153,193,22,232,159,17,66,176,131,22,232,255,195,159,159,159,17,66,144,22,229,249,21,31, - 21,96,22,230,255,195,153,129,21,216,22,228,241,231,193,17,3,84,22,230,255,21,95,249,131,22,231,17,3,80,153,17, - 5,88,22,225,231,255,199,17,34,240,22,231,249,255,249,22,1,195,22,231,159,159,147,17,34,128,22,231,21,30,21,160, - 22,230,255,153,128,128,148,156,22,233,17,2,79,21,32,22,231,17,34,210,17,4,152,22,228,17,36,242,22,232,17,3, - 144,249,22,232,131,17,66,226,21,160,22,228,17,131,225,22,232,17,130,127,17,98,112,22,230,17,35,226,17,34,0,22, - 233,195,17,2,240,22,230,156,148,128,193,17,226,24,22,230,17,35,241,22,234,21,47,243,135,22,232,129,243,231,207,17, - 98,194,22,228,227,207,231,143,231,207,227,22,231,17,164,159,22,3,22,227,199,243,231,241,231,243,199,255,22,230,204,0, - 51,17,35,206,22,230,22,14,9,19,0,13,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31,22,31, - 22,31,22,31,22,31,22,31,22,31,22,30,22,28 -]; - -describe('LZG', function() { - it('Should decode LZG', function() { - var rom = new Uint8Array(new util.lzgmini().decode(NES_CONIO_ROM_LZG)); - assert.equal(40976, rom.length); - }); -}); - -describe('string functions', function() { - it('Should detect binary', function() { - assert.ok(!util.isProbablyBinary(null, [32,32,10,13,9,32,32,10,13])); - assert.ok(util.isProbablyBinary(null, [32,32,0x80])); - assert.ok(!util.isProbablyBinary(null, [32,32,0xc1,0x81,32,32,10,13])); - assert.ok(util.isProbablyBinary(null, NES_CONIO_ROM_LZG)); - assert.ok(util.isProbablyBinary('test.bin')); - assert.ok(util.isProbablyBinary('test.chr')); - assert.ok(!util.isProbablyBinary('test.txt')); - assert.ok(util.isProbablyBinary('test.dat')); - assert.ok(!util.isProbablyBinary(null, [0x20,0xa9,0x20,0x31,0x39,0x38,0x32])); - assert.ok(util.isProbablyBinary(null, [0x00,0x00])); // 00 is binary - assert.ok(util.isProbablyBinary(null, [0x9f])); // 9f is binary - assert.ok(util.isProbablyBinary(null, [0xff])); // FF is binary - assert.ok(util.isProbablyBinary(null, [0xf0,0x12])); // ran out of data - }); -}); - -describe('EmuHalt', function() { - it('Should detect emuhalt', function() { - var e = new emu.EmuHalt(); - assert.ok(e instanceof emu.EmuHalt); - assert.ok(e.hasOwnProperty('$loc')); - }); -}); diff --git a/test/ecs/basic1.ecs b/test/ecs/basic1.ecs new file mode 100644 index 00000000..2221f030 --- /dev/null +++ b/test/ecs/basic1.ecs @@ -0,0 +1,16 @@ + // comment + /* + mju,fjeqowfjqewiofjqe + */ +component Kernel + lines: 0..255 + bgcolor: 0..255 +end + +component Bitmap + data: array of 0..255 +end + +component HasBitmap + bitmap: [Bitmap] +end diff --git a/test/ecs/basic1.txt b/test/ecs/basic1.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/ecs/bigdemo.ecs b/test/ecs/bigdemo.ecs new file mode 100644 index 00000000..6ab9bbf1 --- /dev/null +++ b/test/ecs/bigdemo.ecs @@ -0,0 +1,148 @@ + +//#resource "vcs-ca65.h" + +import "vcslib.ecs" +import "sprites.ecs" +import "kernel2.ecs" +import "score.ecs" + +demo Main + + using FrameLoop, Kernel2Sprite + using Joystick, MoveJoyX, MoveJoyY + using SetXPos, SetHorizPos + using SpriteShuffler, SpriteHider + + using Kernel6Digit + using JoyButton, BCDMath + + entity Score [BCDScore6, PFColor, BGColor] + const pfcolor = $3c + const bgcolor = $02 + end + + entity Kernel [KernelSection, BGColor] + const lines = 168 + const bgcolor = 0xa0 + end + + entity Weird [Bitmap,Colormap] + decode vcs_sprite + --- + .x...... 12 + .xx..... 14 + .xxx.... 16 + .xxxx... 18 + .xxxxx.. 1a + .xxxxxx. 1c + .xxx.xxx 1e + .x.x.x.x 18 + --- + end + + entity Cool [Bitmap,Colormap] + decode vcs_sprite + --- + ...xx... 48 + ..xxxx.. 4a + .xxxxxx. 4c + xxxxxxxx 4e + x..xx..x 4e + x.xxx.xx 4e + xxxxxxxx 4e + xxxxxxxx 4e + xxxxxxxx 4e + xx....xx 4e + xxxxxxxx 4e + .xxxxxx. 4c + ..xxxx.. 4a + --- + end + + entity Cool2 [Bitmap,Colormap] + decode vcs_sprite + --- + ...xx... 48 + ..xxxx.. 4a + .xxxxxx. 4c + xxxxxxxx 8e + x..xx..x ce + x..xx..x ce + xxxxxxxx ce + xxxxxxxx 4e + xxxxxxxx 4e + xx.xx.xx 4e + xxx..xxx 4e + .xxxxxx. 4c + ..xxxx.. 4a + --- + end + + entity Bitmap1 [Bitmap] + const bitmapdata = [1, 1, 3, 7, 15, 31, 63, 255] + const height = 8 + end + + entity Bitmap2 [Bitmap] + const bitmapdata = [$18,$3e,$ff,$ff,$ff,$ff,$3e,$18] + const height = 8 + end + + entity Colormap1 [Colormap] + const colormapdata = [6, 3, 6, 9, 12, 14, 31, 63] + end + + entity Sprite0 [Sprite,HasBitmap,HasColormap,HasXpos,HasYpos,Player] + var xpos = 50 + var ypos = 150 + var bitmap = #Cool + var colormap = #Cool + const plyrflags = 0 + end + + entity Sprite1 [Sprite,HasBitmap,HasColormap,HasXpos,HasYpos,Player] + var xpos = 100 + var ypos = 60 + var bitmap = #Cool2 + var colormap = #Cool2 + const plyrflags = 3 + end + + entity Sprite2 [Sprite,HasBitmap,HasColormap,HasXpos,HasYpos] + var xpos = 80 + var ypos = 90 + var bitmap = #Weird + var colormap = #Weird + const plyrflags = 0 + end + + entity Sprite3 [Sprite,HasBitmap,HasColormap,HasXpos,HasYpos] + var xpos = 40 + var ypos = 150 + var bitmap = #Weird + var colormap = #Weird + const plyrflags = 0 + end + + entity Slot0 [SpriteSlot] + var sprite = #Sprite0 + end + entity Slot1 [SpriteSlot] + var sprite = #Sprite1 + end + entity Slot2 [SpriteSlot] + var sprite = #Sprite2 + end + entity Slot3 [SpriteSlot] + var sprite = #Sprite3 + end + + system Enemies + on postframe do foreach [Sprite,HasYpos,-Player] + --- + inc {{set ypos}} +; inc {{set ypos}} + --- + end + +end demo diff --git a/test/ecs/bigdemo.txt b/test/ecs/bigdemo.txt new file mode 100644 index 00000000..c10eff64 --- /dev/null +++ b/test/ecs/bigdemo.txt @@ -0,0 +1,885 @@ +EVENT__start = 1 +EVENT__postframe = 1 +EVENT__preframe = 1 +EVENT__kernel = 1 +EVENT__scanline = 1 +EVENT__joyleft = 1 +EVENT__joyright = 1 +EVENT__joyup = 1 +EVENT__joydown = 1 +EVENT__prekernel = 1 +EVENT__SetHorizPos = 1 +EVENT__AddBCD4 = 1 +EVENT__AddBCD2 = 1 +EVENT__SubBCD2 = 1 +.scope Main +.zeropage +Sprite_bitmap_b0: +.res 1 +.res 1 +.res 1 +.res 1 +HasColormap_colormap_b0: +.res 1 +.res 1 +.res 1 +.res 1 +HasXpos_xpos_b0: +.res 1 +.res 1 +.res 1 +.res 1 +HasYpos_ypos_b0: +.res 1 +.res 1 +.res 1 +.res 1 +SpriteSlot_sprite_b0: +.res 1 +.res 1 +.res 1 +.res 1 +BCDScore6_digits_b0: +.res 1 +BCDScore6_digits_b8: +.res 1 +BCDScore6_digits_b16: +.res 1 +TEMP: +Kernel2Sprite__2__tmp: +Joystick__3__tmp: +SpriteHider__9__tmp: +.res 1 +SpriteShuffler__8__tmp: +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +Kernel6Digit__10__tmp: +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.code +PFColor_pfcolor_b0: +.byte 60 +BGColor_bgcolor_b0: +.byte 2 +.byte 160 +KernelSection_lines_b0: +.byte 168 +Bitmap_bitmapdata_b0: +.byte <(Bitmap_bitmapdata_e2_b0+31) +.byte <(Bitmap_bitmapdata_e3_b0+31) +.byte <(Bitmap_bitmapdata_e4_b0+31) +.byte <(Bitmap_bitmapdata_e5_b0+31) +.byte <(Bitmap_bitmapdata_e6_b0+31) +Bitmap_bitmapdata_b8: +.byte >(Bitmap_bitmapdata_e2_b0+31) +.byte >(Bitmap_bitmapdata_e3_b0+31) +.byte >(Bitmap_bitmapdata_e4_b0+31) +.byte >(Bitmap_bitmapdata_e5_b0+31) +.byte >(Bitmap_bitmapdata_e6_b0+31) +Bitmap_bitmapdata_e2_b0: +.byte 85 +.byte 119 +.byte 126 +.byte 124 +.byte 120 +.byte 112 +.byte 96 +.byte 64 +Bitmap_height_b0: +.byte 7 +.byte 12 +.byte 12 +.byte 8 +.byte 8 +Colormap_colormapdata_b0: +.byte <(Colormap_colormapdata_e2_b0+31) +.byte <(Colormap_colormapdata_e3_b0+31) +.byte <(Colormap_colormapdata_e4_b0+31) +.res 1 +.res 1 +.byte <(Colormap_colormapdata_e7_b0+31) +Colormap_colormapdata_b8: +.byte >(Colormap_colormapdata_e2_b0+31) +.byte >(Colormap_colormapdata_e3_b0+31) +.byte >(Colormap_colormapdata_e4_b0+31) +.res 1 +.res 1 +.byte >(Colormap_colormapdata_e7_b0+31) +Colormap_colormapdata_e2_b0: +.byte 24 +.byte 30 +.byte 28 +.byte 26 +.byte 24 +.byte 22 +.byte 20 +.byte 18 +Bitmap_bitmapdata_e3_b0: +.byte 60 +.byte 126 +.byte 255 +.byte 195 +.byte 255 +.byte 255 +.byte 255 +.byte 187 +.byte 153 +.byte 255 +.byte 126 +.byte 60 +.byte 24 +Colormap_colormapdata_e3_b0: +.byte 74 +.byte 76 +.byte 78 +.byte 78 +.byte 78 +.byte 78 +.byte 78 +.byte 78 +.byte 78 +.byte 78 +.byte 76 +.byte 74 +.byte 72 +Bitmap_bitmapdata_e4_b0: +.byte 60 +.byte 126 +.byte 231 +.byte 219 +.byte 255 +.byte 255 +.byte 255 +.byte 153 +.byte 153 +.byte 255 +.byte 126 +.byte 60 +.byte 24 +Colormap_colormapdata_e4_b0: +.byte 74 +.byte 76 +.byte 78 +.byte 78 +.byte 78 +.byte 78 +.byte 206 +.byte 206 +.byte 206 +.byte 142 +.byte 76 +.byte 74 +.byte 72 +Bitmap_bitmapdata_e5_b0: +.byte 1 +.byte 1 +.byte 3 +.byte 7 +.byte 15 +.byte 31 +.byte 63 +.byte 255 +Bitmap_bitmapdata_e6_b0: +.byte 24 +.byte 62 +.byte 255 +.byte 255 +.byte 255 +.byte 255 +.byte 62 +.byte 24 +Colormap_colormapdata_e7_b0: +.byte 6 +.byte 3 +.byte 6 +.byte 9 +.byte 12 +.byte 14 +.byte 31 +.byte 63 +Sprite_plyrflags_b0: +.byte 0 +.byte 3 +.byte 0 +.byte 0 +Main__INITDATA: +.byte 1 +.byte 2 +.byte 0 +.byte 0 +.byte 1 +.byte 2 +.byte 0 +.byte 0 +.byte 50 +.byte 100 +.byte 80 +.byte 40 +.byte 150 +.byte 60 +.byte 90 +.byte 150 +.byte 0 +.byte 1 +.byte 2 +.byte 3 +.byte 0 +.byte 0 +.byte 0 +__Start: + +;;; start action Init__main_init__1 + +.include "vcs-ca65.h" +.macpack longbranch +.define PAL 0 +__NMI: +__Reset: +__BRK: + CLEAN_START + + ldy #23 +: lda Main__INITDATA-1,y + sta Sprite_bitmap_b0-1,y + dey + bne :- + +;;; start action FrameLoop__start__3 + + +FrameLoop__start__4__NextFrame: + FRAME_END + + FRAME_START + +;;; start action Kernel2Sprite__preframe__5 + +; TODOO: can store KLINES in memory? +.define KLINES #168 +.define KPAD 32 +; set height to zero in case no sprites + lda #0 + sta Kernel2Sprite__2__tmp+8 + sta Kernel2Sprite__2__tmp+9 + +;;; end action Kernel2Sprite__preframe__5 + +;;; start action Kernel2Sprite__preframe__8 + + ldy #0 +Kernel2Sprite__preframe__9____each: + ldx SpriteSlot_sprite_b0,y + +; set player object flags + lda Sprite_plyrflags_b0,x + sta NUSIZ0,y + sta REFP0,y +; calculate screen height - ypos + lda KLINES + clc + adc KPAD + sec + sbc HasYpos_ypos_b0,x + sta Kernel2Sprite__2__tmp+11 +; calculate bitmap pointer + stx Kernel2Sprite__2__tmp+12 ; save X (Sprite index) + lda Sprite_bitmap_b0,x ; deref bitmap + tax + lda Bitmap_bitmapdata_b0,x + sec + sbc Kernel2Sprite__2__tmp+11 + sta Kernel2Sprite__2__tmp+0,y ; Y = sprite slot index + lda Bitmap_bitmapdata_b8,x + sbc #0 + sta Kernel2Sprite__2__tmp+2,y +; get bitmap height + lda Bitmap_height_b0,x + sta Kernel2Sprite__2__tmp+8,y +; calculate colormap pointer + ldx Kernel2Sprite__2__tmp+12 ; restore X + lda HasColormap_colormap_b0,x ; deref colormap + tax + lda Colormap_colormapdata_b0,x + sec + sbc Kernel2Sprite__2__tmp+11 + sta Kernel2Sprite__2__tmp+4,y + lda Colormap_colormapdata_b8,x + sbc #0 + sta Kernel2Sprite__2__tmp+6,y +; save ypos + ldx Kernel2Sprite__2__tmp+12 ; restore X + lda HasYpos_ypos_b0,x + sta Kernel2Sprite__2__tmp+10,y + + iny + cpy #2 + jne Kernel2Sprite__preframe__9____each +Kernel2Sprite__preframe__9____exit: + +;;; end action Kernel2Sprite__preframe__8 + +;;; start action Kernel2Sprite__preframe__11 + +; shuffle pointers into (MSB, LSB) byte order +; L0 L1 H0 H1 -> L0 H0 L1 H1 + lda Kernel2Sprite__2__tmp+1 + ldy Kernel2Sprite__2__tmp+2 + sty Kernel2Sprite__2__tmp+1 + sta Kernel2Sprite__2__tmp+2 + lda Kernel2Sprite__2__tmp+5 + ldy Kernel2Sprite__2__tmp+6 + sty Kernel2Sprite__2__tmp+5 + sta Kernel2Sprite__2__tmp+6 + +;;; end action Kernel2Sprite__preframe__11 + +;;; start action Kernel2Sprite__preframe__13 + + lda #160 + sta COLUBK + +;;; end action Kernel2Sprite__preframe__13 + +;;; start action Kernel2Sprite__preframe__16 + +;;; end action Kernel2Sprite__preframe__16 + +;;; start action SetXPos__preframe__17 + + ldy #0 +SetXPos__preframe__18____each: + ldx SpriteSlot_sprite_b0,y + + lda HasXpos_xpos_b0,x + + jsr SetHorizPos__SetHorizPos__20 + + + iny + cpy #2 + jne SetXPos__preframe__18____each +SetXPos__preframe__18____exit: + +;;; end action SetXPos__preframe__17 + +;;; start action SetXPos__preframe__22 + +;;; end action SetXPos__preframe__22 + +;;; start action Kernel6Digit__preframe__23 + +Digit0 = Kernel6Digit__10__tmp+0 +Digit1 = Kernel6Digit__10__tmp+2 +Digit2 = Kernel6Digit__10__tmp+4 +Digit3 = Kernel6Digit__10__tmp+6 +Digit4 = Kernel6Digit__10__tmp+8 +Digit5 = Kernel6Digit__10__tmp+10 +Kernel6Digit__preframe__25__BCD0 = Kernel6Digit__10__tmp+12 +Kernel6Digit__preframe__25__BCD1 = Kernel6Digit__10__tmp+13 +Kernel6Digit__preframe__25__BCD2 = Kernel6Digit__10__tmp+14 + + lda BCDScore6_digits_b0 + sta Kernel6Digit__preframe__25__BCD0 + lda BCDScore6_digits_b8 + sta Kernel6Digit__preframe__25__BCD1 + lda BCDScore6_digits_b16 + sta Kernel6Digit__preframe__25__BCD2 + ldx #0 ; leftmost bitmap + ldy #2 ; start from most-sigificant BCD value +Kernel6Digit__preframe__25__Loop: + lda Kernel6Digit__preframe__25__BCD0,y ; get BCD value + and #$f0 ; isolate high nibble (* 16) + lsr ; shift right 1 bit (* 8) + clc + adc #FontTable + adc #0 + sta Digit0+1,x ; store pointer hi byte + inx + inx ; next bitmap pointer + lda Kernel6Digit__preframe__25__BCD0,y ; get BCD value (again) + and #$f ; isolate low nibble + asl + asl + asl ; * 8 + clc + adc #FontTable + adc #0 + sta Digit0+1,x ; store pointer hi byte + inx + inx ; next bitmap pointer + dey ; next BCD value + bpl Kernel6Digit__preframe__25__Loop ; repeat until < 0 + +;;; end action Kernel6Digit__preframe__23 + + +;;; start action SetXPos__prekernel__26 + + sta WSYNC + sta HMOVE + SLEEPR 24 + sta HMCLR + +;;; end action SetXPos__prekernel__26 + + KERNEL_START + +;;; start action Kernel2Sprite__kernel__28 + + ldy #0 + sty VDELP0 + iny + sty VDELP1 + +;;; end action Kernel2Sprite__kernel__28 + + jsr Kernel2Sprite__kernel__31 + +;;; start action Kernel2Sprite__kernel__40 + + lda #0 + sta GRP0 + sta GRP1 + sta GRP0 + sta GRP1 + +;;; end action Kernel2Sprite__kernel__40 + +;;; start action Kernel6Digit__kernel__42 + + lda #60 + sta COLUP0 + sta COLUP1 + lda #3 + sta NUSIZ0 + sta NUSIZ1 +; set horizontal position of player objects + sta WSYNC + sta HMCLR + SLEEPR 24 + sta RESP0 + sta RESP1 + lda #$10 + sta HMP1 + sta WSYNC + sta HMOVE + SLEEPR 24 ; wait 24 cycles between write to HMOVE and HMxxx + sta HMCLR + lda #1 + sta VDELP0 + sta VDELP1 + +;;; end action Kernel6Digit__kernel__42 + + jsr Kernel6Digit__kernel__45 + + KERNEL_END + + +;;; start action FrameLoop__postframe__48 + + lsr SWCHB ; test Game Reset switch + bcs FrameLoop__postframe__49__NoStart + +FrameLoop__postframe__49__NoStart: + +;;; end action FrameLoop__postframe__48 + +;;; start action Joystick__postframe__50 + +; 2 control inputs share a single byte, 4 bits each + lda SWCHA + sta Joystick__3__tmp+0 + +;;; end action Joystick__postframe__50 + +;;; start action Joystick__postframe__52 + + ldx #0 +Joystick__postframe__53____each: + + asl Joystick__3__tmp+0 +.ifdef EVENT__joyright + bcs Joystick__postframe__54__SkipMoveRight + +;;; start action MoveJoyX__joyright__55 + + lda HasXpos_xpos_b0,x + clc + adc #1 + cmp #150 + bcs MoveJoyX__joyright__57__nomove + sta HasXpos_xpos_b0,x +MoveJoyX__joyright__57__nomove: + +;;; end action MoveJoyX__joyright__55 + +Joystick__postframe__54__SkipMoveRight: +.endif + asl Joystick__3__tmp+0 +.ifdef EVENT__joyleft + bcs Joystick__postframe__54__SkipMoveLeft + +;;; start action MoveJoyX__joyleft__58 + + lda HasXpos_xpos_b0,x + sec + sbc #1 + bcc MoveJoyX__joyleft__60__nomove + sta HasXpos_xpos_b0,x +MoveJoyX__joyleft__60__nomove: + +;;; end action MoveJoyX__joyleft__58 + +Joystick__postframe__54__SkipMoveLeft: +.endif + asl Joystick__3__tmp+0 +.ifdef EVENT__joydown + bcs Joystick__postframe__54__SkipMoveDown + +;;; start action MoveJoyY__joydown__61 + + lda HasYpos_ypos_b0,x + clc + adc #1 + cmp #220 + bcs MoveJoyY__joydown__63__nomove + sta HasYpos_ypos_b0,x +MoveJoyY__joydown__63__nomove: + +;;; end action MoveJoyY__joydown__61 + +Joystick__postframe__54__SkipMoveDown: +.endif + asl Joystick__3__tmp+0 +.ifdef EVENT__joyup + bcs Joystick__postframe__54__SkipMoveUp + +;;; start action MoveJoyY__joyup__64 + + lda HasYpos_ypos_b0,x + sec + sbc #1 + bcc MoveJoyY__joyup__66__nomove + sta HasYpos_ypos_b0,x +MoveJoyY__joyup__66__nomove: + +;;; end action MoveJoyY__joyup__64 + +Joystick__postframe__54__SkipMoveUp: +.endif + + inx + cpx #2 + jne Joystick__postframe__53____each +Joystick__postframe__53____exit: + +;;; end action Joystick__postframe__52 + +;;; start action SpriteShuffler__postframe__67 + +; load two sprite slots at left side of array + lda SpriteSlot_sprite_b0 + sta SpriteShuffler__8__tmp+0 + lda SpriteSlot_sprite_b0+1 + sta SpriteShuffler__8__tmp+1 +; move two slots to the left + ldx #0 +SpriteShuffler__postframe__69__loop: + lda SpriteSlot_sprite_b0+2,x + sta SpriteSlot_sprite_b0,x + inx + cpx #4-2 + bne SpriteShuffler__postframe__69__loop +; store two sprite slots at right side of array + lda SpriteShuffler__8__tmp+0 + sta SpriteSlot_sprite_b0+4-2 + lda SpriteShuffler__8__tmp+1 + sta SpriteSlot_sprite_b0+4-1 + +;;; end action SpriteShuffler__postframe__67 + +;;; start action SpriteHider__postframe__70 + + lda #4-1 + sta SpriteHider__9__tmp+0 + +;;; end action SpriteHider__postframe__70 + +;;; start action SpriteHider__postframe__73 + + ldy #0 +SpriteHider__postframe__74____each: + ldx SpriteSlot_sprite_b0,y + + lda HasYpos_ypos_b0,x + cmp #192 + bcc SpriteHider__postframe__75__skip +; swap this sprite slot with slot at end of array + lda SpriteSlot_sprite_b0,y + pha + ldx SpriteHider__9__tmp+0 ; clobbers X, but no longer used + lda SpriteSlot_sprite_b0,x + sta SpriteSlot_sprite_b0,y + pla + sta SpriteSlot_sprite_b0,x + dec SpriteHider__9__tmp+0 +SpriteHider__postframe__75__skip: + + iny + cpy #2 + jne SpriteHider__postframe__74____each +SpriteHider__postframe__74____exit: + +;;; end action SpriteHider__postframe__73 + +;;; start action JoyButton__postframe__76 + + ldx #0 +JoyButton__postframe__77____each: + + lda INPT4,x ;read button input + bmi JoyButton__postframe__78__NotPressed + +JoyButton__postframe__78__NotPressed: + + inx + cpx #2 + jne JoyButton__postframe__77____each +JoyButton__postframe__77____exit: + +;;; end action JoyButton__postframe__76 + +;;; start action Enemies__postframe__79 + + ldx #0 +Enemies__postframe__80____each: + + inc HasYpos_ypos_b0+2,x +; inc HasYpos_ypos_b0+2,x + + inx + cpx #2 + jne Enemies__postframe__80____each +Enemies__postframe__80____exit: + +;;; end action Enemies__postframe__79 + + jmp FrameLoop__start__4__NextFrame ; loop to next frame + +;;; end action FrameLoop__start__3 + ; start main routine +.segment "VECTORS" +ZeroByte: .byte $00 +Return: .byte $60 +VecNMI: +VecReset: .word __Reset +VecBRK: .word __BRK +.code + +;;; end action Init__main_init__1 +.rodata +__ALIGNORIGIN: +.rodata +SetHorizPos__SetHorizPos__20: + +; SetHorizPos routine +; A = X coordinate +; Y = player number (0 or 1) + sec ; set carry flag + sta WSYNC ; start a new line +: + sbc #15 ; subtract 15 + bcs :- ; branch until negative + eor #7 ; calculate fine offset + asl + asl + asl + asl + sta HMP0,y ; set fine offset + sta RESP0,y ; fix coarse position + sta WSYNC ; won't overrun if X < 150 + + rts + +.assert >(SetHorizPos__SetHorizPos__20) = >(*), error, "SetHorizPos__SetHorizPos__20 crosses a page boundary!" + +.assert (* - SetHorizPos__SetHorizPos__20) <= 22, error, .sprintf("SetHorizPos__SetHorizPos__20 does not fit in 22 bytes, it took %d!", (* - SetHorizPos__SetHorizPos__20)) +.rodata +Kernel2Sprite__kernel__31: + + ldy #168 +Kernel2Sprite__kernel__33__LVScan: + +;;; start action Kernel2Sprite__scanline__34 + +; draw player 0 + lda Kernel2Sprite__2__tmp+8 ; height + dcp Kernel2Sprite__2__tmp+10 ; ypos + bcs Kernel2Sprite__scanline__35__DoDraw1 + lda #0 + .byte $2C +Kernel2Sprite__scanline__35__DoDraw1: + lda (Kernel2Sprite__2__tmp+0),y +; .if 0 = 0 ; TODO: configurable? + sta WSYNC +; .endif + sta GRP0 + lda (Kernel2Sprite__2__tmp+4),y + sta COLUP0 +; draw player 1 + lda Kernel2Sprite__2__tmp+9 ; height + dcp Kernel2Sprite__2__tmp+11 ; ypos + bcs Kernel2Sprite__scanline__35__DoDraw2 + lda #0 + .byte $2C +Kernel2Sprite__scanline__35__DoDraw2: + lda (Kernel2Sprite__2__tmp+2),y + sta GRP1 + lda (Kernel2Sprite__2__tmp+6),y + sta COLUP1 + +;;; end action Kernel2Sprite__scanline__34 + +;;; start action Kernel2Sprite__scanline__36 + +;;; end action Kernel2Sprite__scanline__36 + + dey ; next scanline + +;;; start action Kernel2Sprite__scanline__37 + +; draw player 0 + lda Kernel2Sprite__2__tmp+8 ; height + dcp Kernel2Sprite__2__tmp+10 ; ypos + bcs Kernel2Sprite__scanline__38__DoDraw1 + lda #0 + .byte $2C +Kernel2Sprite__scanline__38__DoDraw1: + lda (Kernel2Sprite__2__tmp+0),y +; .if 1 = 0 ; TODO: configurable? + sta WSYNC +; .endif + sta GRP0 + lda (Kernel2Sprite__2__tmp+4),y + sta COLUP0 +; draw player 1 + lda Kernel2Sprite__2__tmp+9 ; height + dcp Kernel2Sprite__2__tmp+11 ; ypos + bcs Kernel2Sprite__scanline__38__DoDraw2 + lda #0 + .byte $2C +Kernel2Sprite__scanline__38__DoDraw2: + lda (Kernel2Sprite__2__tmp+2),y + sta GRP1 + lda (Kernel2Sprite__2__tmp+6),y + sta COLUP1 + +;;; end action Kernel2Sprite__scanline__37 + +;;; start action Kernel2Sprite__scanline__39 + +;;; end action Kernel2Sprite__scanline__39 + + dey ; next scanline + bne Kernel2Sprite__kernel__33__LVScan ; repeat until out of lines + + rts + +.assert >(Kernel2Sprite__kernel__31) = >(*), error, "Kernel2Sprite__kernel__31 crosses a page boundary!" + +.if <(* - __ALIGNORIGIN) > 256-72 +.align $100 +.endif +.rodata +Kernel6Digit__kernel__45: + +; Display the resulting 48x8 bitmap +; using the Digit0-5 pointers. +Kernel6Digit__kernel__47__LoopCount = Kernel6Digit__10__tmp+12 +Kernel6Digit__kernel__47__Temp = Kernel6Digit__10__tmp+13 + + lda #2 + sta WSYNC + sta COLUBK + lda #7 + sta Kernel6Digit__kernel__47__LoopCount + SLEEPR 20 ; TODO? +: + ldy Kernel6Digit__kernel__47__LoopCount ; counts backwards + lda (Digit0),y ; load B0 (1st sprite byte) + sta GRP0 ; B0 -> [GRP0] + lda (Digit1),y ; load B1 -> A + sta GRP1 ; B1 -> [GRP1], B0 -> GRP0 + sta WSYNC ; sync to next scanline + lda (Digit2),y ; load B2 -> A + sta GRP0 ; B2 -> [GRP0], B1 -> GRP1 + lda (Digit5),y ; load B5 -> A + sta Kernel6Digit__kernel__47__Temp ; B5 -> temp + lda (Digit4),y ; load B4 + tax ; -> X + lda (Digit3),y ; load B3 -> A + ldy Kernel6Digit__kernel__47__Temp ; load B5 -> Y + sta GRP1 ; B3 -> [GRP1]; B2 -> GRP0 + stx GRP0 ; B4 -> [GRP0]; B3 -> GRP1 + sty GRP1 ; B5 -> [GRP1]; B4 -> GRP0 + sta GRP0 ; ?? -> [GRP0]; B5 -> GRP1 + dec Kernel6Digit__kernel__47__LoopCount ; go to next line + bpl :- ; repeat until < 0 + + lda #0 ; clear the sprite registers + sta WSYNC + sta GRP0 + sta GRP1 + sta GRP0 + sta GRP1 + sta COLUBK + + rts + +.assert >(Kernel6Digit__kernel__45) = >(*), error, "Kernel6Digit__kernel__45 crosses a page boundary!" + +.assert (* - Kernel6Digit__kernel__45) <= 72, error, .sprintf("Kernel6Digit__kernel__45 does not fit in 72 bytes, it took %d!", (* - Kernel6Digit__kernel__45)) +FontTable: + +;;; start action FontTable__FontTable__82 + +; Font table for digits 0-9 (8x8 pixels) +;;{w:8,h:8,count:10,brev:1,flip:1};; + .byte $00,$3c,$66,$66,$76,$6e,$66,$3c,$00,$7e,$18,$18,$18,$38,$18,$18 + .byte $00,$7e,$60,$30,$0c,$06,$66,$3c,$00,$3c,$66,$06,$1c,$06,$66,$3c + .byte $00,$06,$06,$7f,$66,$1e,$0e,$06,$00,$3c,$66,$06,$06,$7c,$60,$7e + .byte $00,$3c,$66,$66,$7c,$60,$66,$3c,$00,$18,$18,$18,$18,$0c,$66,$7e + .byte $00,$3c,$66,$66,$3c,$66,$66,$3c,$00,$3c,$66,$06,$3e,$66,$66,$3c + +;;; end action FontTable__FontTable__82 + +.endscope +Main__Start = Main::__Start \ No newline at end of file diff --git a/test/ecs/errors1.ecs b/test/ecs/errors1.ecs new file mode 100644 index 00000000..8e433df3 --- /dev/null +++ b/test/ecs/errors1.ecs @@ -0,0 +1,31 @@ + +component KernelSection + lines: 1..255 +end + +component BGColor + color: 0..255 +end + +component FGColor + color: 0..255 +end + +demo Main + entity [KernelSection,BGColor] + const lines = 2 + const color = $18 + end + entity [KernelSection,BGColor,FGColor] + const lines = 2 + const color = $16 + end + entity [KernelSection,FGColor] + const lines = 2 + const color = $16 + end + entity [KernelSection,FGColor] + const lines = 2 + const color = $16 + end +end demo diff --git a/test/ecs/errors1.txt b/test/ecs/errors1.txt new file mode 100644 index 00000000..29b87eac --- /dev/null +++ b/test/ecs/errors1.txt @@ -0,0 +1 @@ +21:I found more than one field named "color" for this entity. \ No newline at end of file diff --git a/test/ecs/jumper.ecs b/test/ecs/jumper.ecs new file mode 100644 index 00000000..16d8aefa --- /dev/null +++ b/test/ecs/jumper.ecs @@ -0,0 +1,685 @@ + +//#resource "vcs-ca65.h" + +import "vcslib.ecs" +import "sprites.ecs" +import "score.ecs" +import "sound.ecs" +import "velocity.ecs" +import "kernel2.ecs" +import "random.ecs" +import "versatile.ecs" +import "music.ecs" + +// TODO: not yet used +component Activity + activity: enum [Standing,Walking,Jumping] +end + +component Enemy +end + +component Gravity +end + +component Jumper +end + +system MoveJoyVel + // dampen velocity + on frame8 do with [HasXpos] +--- + lda {{Bitmap:bitmapdata}},x + sbc #0 + sta {{$1}} +; get bitmap height + lda {{Colormap:colormapdata}},x + sbc #0 + sta {{$3}} +; save ypos + ldx {{$6}} ; restore X + lda {{ 1 player? or missile? + on kernelsetup do with [Sprite,HasXpos] +--- + ldy #0 + lda {{ player 0? + stx {{$0}} ; save object index +@nocollide: + --- +// TODO: somehow avoid select? pass arg to explode? + on postframe do select [Sprite] + --- + ldx {{$0}} ; get object index + bmi @noexplode ; was there collision? + {{!explode}} +@noexplode: + --- +end diff --git a/test/ecs/kernel2.ecs b/test/ecs/kernel2.ecs new file mode 100644 index 00000000..c3f4d0e9 --- /dev/null +++ b/test/ecs/kernel2.ecs @@ -0,0 +1,231 @@ + +//#resource "vcs-ca65.h" + +import "vcslib.ecs" +import "sprites.ecs" + +system Kernel2Sprite + locals 13 + on preframe do with [KernelSection] +--- +; TODOO: can store KLINES in memory? +.define KLINES {{Bitmap:bitmapdata}},x + sbc #0 + sta {{$2}},y +; get bitmap height + lda {{Colormap:colormapdata}},x + sbc #0 + sta {{$6}},y +; save ypos + ldx {{$12}} ; restore X + lda {{ L0 H0 L1 H1 + lda {{$1}} + ldy {{$2}} + sty {{$1}} + sta {{$2}} + lda {{$5}} + ldy {{$6}} + sty {{$5}} + sta {{$6}} +--- + on preframe do if [KernelSection,BGColor] +--- + lda {{(Bitmap_bitmapdata_e1_b0+31) +.byte >(Bitmap_bitmapdata_e2_b0+31) +Bitmap_bitmapdata_e1_b0: +.byte 1 +.byte 1 +.byte 3 +.byte 7 +.byte 15 +.byte 31 +.byte 63 +.byte 127 +.byte 0 +Bitmap_height_b0: +.byte 8 +.byte 8 +Bitmap_bitmapdata_e2_b0: +.byte 24 +.byte 62 +.byte 255 +.byte 255 +.byte 255 +.byte 255 +.byte 62 +.byte 24 +.byte 0 +Colormap_colormapdata_b0: +.byte <(Colormap_colormapdata_e3_b0+31) +Colormap_colormapdata_b8: +.byte >(Colormap_colormapdata_e3_b0+31) +Colormap_colormapdata_e3_b0: +.byte 2 +.byte 4 +.byte 6 +.byte 8 +.byte 10 +.byte 12 +.byte 14 +.byte 14 +.byte 14 +Sprite_plyrflags_b0: +.byte 0 +.byte 3 +.byte 2 +.byte 0 +Main__INITDATA: +.byte 1 +.byte 0 +.byte 1 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 50 +.byte 100 +.byte 80 +.byte 40 +.byte 150 +.byte 60 +.byte 90 +.byte 150 +.byte 0 +.byte 1 +.byte 2 +.byte 3 +__Start: + +;;; start action Init__main_init__1 + +.include "vcs-ca65.h" +.macpack longbranch +.define PAL 0 +__NMI: +__Reset: +__BRK: + CLEAN_START + + ldy #20 +: lda Main__INITDATA-1,y + sta Sprite_bitmap_b0-1,y + dey + bne :- + +;;; start action FrameLoop__start__3 + + +FrameLoop__start__4__NextFrame: + FRAME_END + + FRAME_START + +;;; start action Kernel2Sprite__preframe__5 + +; TODOO: can store KLINES in memory? +.define KLINES #192 +.define KPAD 32 +; set height to zero in case no sprites + lda #0 + sta Kernel2Sprite__2__tmp+8 + sta Kernel2Sprite__2__tmp+9 + +;;; end action Kernel2Sprite__preframe__5 + +;;; start action Kernel2Sprite__preframe__8 + + ldy #0 +Kernel2Sprite__preframe__9____each: + ldx SpriteSlot_sprite_b0,y + +; set player object flags + lda Sprite_plyrflags_b0,x + sta NUSIZ0,y + sta REFP0,y +; calculate screen height - ypos + lda KLINES + clc + adc KPAD + sec + sbc HasYpos_ypos_b0,x + sta Kernel2Sprite__2__tmp+11 +; calculate bitmap pointer + stx Kernel2Sprite__2__tmp+12 ; save X (Sprite index) + lda Sprite_bitmap_b0,x ; deref bitmap + tax + lda Bitmap_bitmapdata_b0,x + sec + sbc Kernel2Sprite__2__tmp+11 + sta Kernel2Sprite__2__tmp+0,y ; Y = sprite slot index + lda Bitmap_bitmapdata_b8,x + sbc #0 + sta Kernel2Sprite__2__tmp+2,y +; get bitmap height + lda Bitmap_height_b0,x + sta Kernel2Sprite__2__tmp+8,y +; calculate colormap pointer + ldx Kernel2Sprite__2__tmp+12 ; restore X + lda HasColormap_colormap_b0,x ; deref colormap + tax + lda Colormap_colormapdata_b0,x + sec + sbc Kernel2Sprite__2__tmp+11 + sta Kernel2Sprite__2__tmp+4,y + lda Colormap_colormapdata_b8,x + sbc #0 + sta Kernel2Sprite__2__tmp+6,y +; save ypos + ldx Kernel2Sprite__2__tmp+12 ; restore X + lda HasYpos_ypos_b0,x + sta Kernel2Sprite__2__tmp+10,y + + iny + cpy #2 + jne Kernel2Sprite__preframe__9____each +Kernel2Sprite__preframe__9____exit: + +;;; end action Kernel2Sprite__preframe__8 + +;;; start action Kernel2Sprite__preframe__11 + +; shuffle pointers into (MSB, LSB) byte order +; L0 L1 H0 H1 -> L0 H0 L1 H1 + lda Kernel2Sprite__2__tmp+1 + ldy Kernel2Sprite__2__tmp+2 + sty Kernel2Sprite__2__tmp+1 + sta Kernel2Sprite__2__tmp+2 + lda Kernel2Sprite__2__tmp+5 + ldy Kernel2Sprite__2__tmp+6 + sty Kernel2Sprite__2__tmp+5 + sta Kernel2Sprite__2__tmp+6 + +;;; end action Kernel2Sprite__preframe__11 + +;;; start action Kernel2Sprite__preframe__13 + + lda #162 + sta COLUBK + +;;; end action Kernel2Sprite__preframe__13 + +;;; start action Kernel2Sprite__preframe__16 + +;;; end action Kernel2Sprite__preframe__16 + +;;; start action SetXPos__preframe__17 + + ldy #0 +SetXPos__preframe__18____each: + ldx SpriteSlot_sprite_b0,y + + lda HasXpos_xpos_b0,x + + jsr SetHorizPos__SetHorizPos__20 + + + iny + cpy #2 + jne SetXPos__preframe__18____each +SetXPos__preframe__18____exit: + +;;; end action SetXPos__preframe__17 + +;;; start action SetXPos__preframe__22 + +;;; end action SetXPos__preframe__22 + + +;;; start action SetXPos__prekernel__23 + + sta WSYNC + sta HMOVE + SLEEPR 24 + sta HMCLR + +;;; end action SetXPos__prekernel__23 + + KERNEL_START + +;;; start action Kernel2Sprite__kernel__25 + + ldy #0 + sty VDELP0 + iny + sty VDELP1 + +;;; end action Kernel2Sprite__kernel__25 + + jsr Kernel2Sprite__kernel__28 + +;;; start action Kernel2Sprite__kernel__37 + + lda #0 + sta GRP0 + sta GRP1 + sta GRP0 + sta GRP1 + +;;; end action Kernel2Sprite__kernel__37 + + KERNEL_END + + +;;; start action FrameLoop__postframe__39 + + lsr SWCHB ; test Game Reset switch + bcs FrameLoop__postframe__40__NoStart + +FrameLoop__postframe__40__NoStart: + +;;; end action FrameLoop__postframe__39 + +;;; start action Joystick__postframe__41 + +; 2 control inputs share a single byte, 4 bits each + lda SWCHA + sta Joystick__3__tmp+0 + +;;; end action Joystick__postframe__41 + +;;; start action Joystick__postframe__43 + + ldx #0 +Joystick__postframe__44____each: + + asl Joystick__3__tmp+0 +.ifdef EVENT__joyright + bcs Joystick__postframe__45__SkipMoveRight + +;;; start action MoveJoyX__joyright__46 + + lda HasXpos_xpos_b0,x + clc + adc #1 + cmp #150 + bcs MoveJoyX__joyright__48__nomove + sta HasXpos_xpos_b0,x +MoveJoyX__joyright__48__nomove: + +;;; end action MoveJoyX__joyright__46 + +Joystick__postframe__45__SkipMoveRight: +.endif + asl Joystick__3__tmp+0 +.ifdef EVENT__joyleft + bcs Joystick__postframe__45__SkipMoveLeft + +;;; start action MoveJoyX__joyleft__49 + + lda HasXpos_xpos_b0,x + sec + sbc #1 + bcc MoveJoyX__joyleft__51__nomove + sta HasXpos_xpos_b0,x +MoveJoyX__joyleft__51__nomove: + +;;; end action MoveJoyX__joyleft__49 + +Joystick__postframe__45__SkipMoveLeft: +.endif + asl Joystick__3__tmp+0 +.ifdef EVENT__joydown + bcs Joystick__postframe__45__SkipMoveDown + +;;; start action MoveJoyY__joydown__52 + + lda HasYpos_ypos_b0,x + clc + adc #1 + cmp #220 + bcs MoveJoyY__joydown__54__nomove + sta HasYpos_ypos_b0,x +MoveJoyY__joydown__54__nomove: + +;;; end action MoveJoyY__joydown__52 + +Joystick__postframe__45__SkipMoveDown: +.endif + asl Joystick__3__tmp+0 +.ifdef EVENT__joyup + bcs Joystick__postframe__45__SkipMoveUp + +;;; start action MoveJoyY__joyup__55 + + lda HasYpos_ypos_b0,x + sec + sbc #1 + bcc MoveJoyY__joyup__57__nomove + sta HasYpos_ypos_b0,x +MoveJoyY__joyup__57__nomove: + +;;; end action MoveJoyY__joyup__55 + +Joystick__postframe__45__SkipMoveUp: +.endif + + inx + cpx #2 + jne Joystick__postframe__44____each +Joystick__postframe__44____exit: + +;;; end action Joystick__postframe__43 + +;;; start action SpriteShuffler__postframe__58 + +; load two sprite slots at left side of array + lda SpriteSlot_sprite_b0 + sta SpriteShuffler__8__tmp+0 + lda SpriteSlot_sprite_b0+1 + sta SpriteShuffler__8__tmp+1 +; move two slots to the left + ldx #0 +SpriteShuffler__postframe__60__loop: + lda SpriteSlot_sprite_b0+2,x + sta SpriteSlot_sprite_b0,x + inx + cpx #4-2 + bne SpriteShuffler__postframe__60__loop +; store two sprite slots at right side of array + lda SpriteShuffler__8__tmp+0 + sta SpriteSlot_sprite_b0+4-2 + lda SpriteShuffler__8__tmp+1 + sta SpriteSlot_sprite_b0+4-1 + +;;; end action SpriteShuffler__postframe__58 + +;;; start action SpriteHider__postframe__61 + + lda #4-1 + sta SpriteHider__9__tmp+0 + +;;; end action SpriteHider__postframe__61 + +;;; start action SpriteHider__postframe__64 + + ldy #0 +SpriteHider__postframe__65____each: + ldx SpriteSlot_sprite_b0,y + + lda HasYpos_ypos_b0,x + cmp #192 + bcc SpriteHider__postframe__66__skip +; swap this sprite slot with slot at end of array + lda SpriteSlot_sprite_b0,y + pha + ldx SpriteHider__9__tmp+0 ; clobbers X, but no longer used + lda SpriteSlot_sprite_b0,x + sta SpriteSlot_sprite_b0,y + pla + sta SpriteSlot_sprite_b0,x + dec SpriteHider__9__tmp+0 +SpriteHider__postframe__66__skip: + + iny + cpy #2 + jne SpriteHider__postframe__65____each +SpriteHider__postframe__65____exit: + +;;; end action SpriteHider__postframe__64 + + jmp FrameLoop__start__4__NextFrame ; loop to next frame + +;;; end action FrameLoop__start__3 + ; start main routine +.segment "VECTORS" +ZeroByte: .byte $00 +Return: .byte $60 +VecNMI: +VecReset: .word __Reset +VecBRK: .word __BRK +.code + +;;; end action Init__main_init__1 +.rodata +__ALIGNORIGIN: +.rodata +SetHorizPos__SetHorizPos__20: + +; SetHorizPos routine +; A = X coordinate +; Y = player number (0 or 1) + sec ; set carry flag + sta WSYNC ; start a new line +: + sbc #15 ; subtract 15 + bcs :- ; branch until negative + eor #7 ; calculate fine offset + asl + asl + asl + asl + sta HMP0,y ; set fine offset + sta RESP0,y ; fix coarse position + sta WSYNC ; won't overrun if X < 150 + + rts + +.assert >(SetHorizPos__SetHorizPos__20) = >(*), error, "SetHorizPos__SetHorizPos__20 crosses a page boundary!" + +.assert (* - SetHorizPos__SetHorizPos__20) <= 22, error, .sprintf("SetHorizPos__SetHorizPos__20 does not fit in 22 bytes, it took %d!", (* - SetHorizPos__SetHorizPos__20)) +.rodata +Kernel2Sprite__kernel__28: + + ldy #192 +Kernel2Sprite__kernel__30__LVScan: + +;;; start action Kernel2Sprite__scanline__31 + +; draw player 0 + lda Kernel2Sprite__2__tmp+8 ; height + dcp Kernel2Sprite__2__tmp+10 ; ypos + bcs Kernel2Sprite__scanline__32__DoDraw1 + lda #0 + .byte $2C +Kernel2Sprite__scanline__32__DoDraw1: + lda (Kernel2Sprite__2__tmp+0),y +; .if 0 = 0 ; TODO: configurable? + sta WSYNC +; .endif + sta GRP0 + lda (Kernel2Sprite__2__tmp+4),y + sta COLUP0 +; draw player 1 + lda Kernel2Sprite__2__tmp+9 ; height + dcp Kernel2Sprite__2__tmp+11 ; ypos + bcs Kernel2Sprite__scanline__32__DoDraw2 + lda #0 + .byte $2C +Kernel2Sprite__scanline__32__DoDraw2: + lda (Kernel2Sprite__2__tmp+2),y + sta GRP1 + lda (Kernel2Sprite__2__tmp+6),y + sta COLUP1 + +;;; end action Kernel2Sprite__scanline__31 + +;;; start action Kernel2Sprite__scanline__33 + +;;; end action Kernel2Sprite__scanline__33 + + dey ; next scanline + +;;; start action Kernel2Sprite__scanline__34 + +; draw player 0 + lda Kernel2Sprite__2__tmp+8 ; height + dcp Kernel2Sprite__2__tmp+10 ; ypos + bcs Kernel2Sprite__scanline__35__DoDraw1 + lda #0 + .byte $2C +Kernel2Sprite__scanline__35__DoDraw1: + lda (Kernel2Sprite__2__tmp+0),y +; .if 1 = 0 ; TODO: configurable? + sta WSYNC +; .endif + sta GRP0 + lda (Kernel2Sprite__2__tmp+4),y + sta COLUP0 +; draw player 1 + lda Kernel2Sprite__2__tmp+9 ; height + dcp Kernel2Sprite__2__tmp+11 ; ypos + bcs Kernel2Sprite__scanline__35__DoDraw2 + lda #0 + .byte $2C +Kernel2Sprite__scanline__35__DoDraw2: + lda (Kernel2Sprite__2__tmp+2),y + sta GRP1 + lda (Kernel2Sprite__2__tmp+6),y + sta COLUP1 + +;;; end action Kernel2Sprite__scanline__34 + +;;; start action Kernel2Sprite__scanline__36 + +;;; end action Kernel2Sprite__scanline__36 + + dey ; next scanline + bne Kernel2Sprite__kernel__30__LVScan ; repeat until out of lines + + rts + +.assert >(Kernel2Sprite__kernel__28) = >(*), error, "Kernel2Sprite__kernel__28 crosses a page boundary!" +.endscope +Main__Start = Main::__Start \ No newline at end of file diff --git a/test/ecs/music.ecs b/test/ecs/music.ecs new file mode 100644 index 00000000..0351cd9a --- /dev/null +++ b/test/ecs/music.ecs @@ -0,0 +1,189 @@ + +//#resource "vcs-ca65.h" + +import "vcslib.ecs" + +component Song + songdata: array of 0..255 +end + +// TODO: merge with SoundChannel +component MusicChannel + duration: 0..255 + note: 0..255 + duty: 0..255 +end + +component MusicPlayer + timer: 0..255 default 255 + channel: [MusicChannel] + songptr: 0..65535 + volume: 0..15 default 15 + tempo: 0..255 default 7 +end + + +// Table of AUDF base values for each note +resource FREQZ --- + .byte 30, 30, 30, 30, 30, 28, 26, 25, 23, 22, 21, 19, 18, 17, 16, 15, 14, 13, 12, 12, 11, 10, 10, 9, 8, 8, 7, 7, 6, 6, 5, 5, 30, 29, 27, 25, 24, 22, 21, 20, 19, 18, 16, 15, 15, 14, 13, 12, 11, 11, 10, 31, 29, 27, 25, 24, 23, 21, 20, 19, 18, 16, 15, 15 +--- +// Table of duty-cycle bits for each note +resource DUTYZ --- + .byte 247, 247, 247, 247, 1, 73, 219, 1, 219, 73, 0, 219, 181, 85, 85, 85, 181, 219, 247, 1, 73, 181, 0, 73, 219, 17, 219, 17, 219, 73, 247, 85, 247, 1, 85, 247, 73, 247, 181, 17, 1, 0, 247, 247, 0, 1, 17, 73, 181, 0, 17, 0, 1, 85, 247, 73, 0, 181, 73, 1, 0, 247, 247, 0 +--- +// Table of AUDC values for each note +resource TONEZ --- + .byte 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 +--- + +system MusicPlayer + on preframe do once + --- + {{!musicpulse}} ; update song + {{!musicframe}} ; update registers + --- + on prekernel do once + --- + {{!musicframe}} ; update registers + --- + on postkernel do once + --- + {{!musicframe}} ; update registers + --- + on postframe do once + --- + {{!musicframe}} ; update registers + --- + // TODO: unroll? + on musicframe do foreach [MusicChannel] + --- +; Update channel pitch in AUDF0 +; 8-bit rotation of duty cycle bits + lda {{get duration}} + beq :++ + lda {{set duty}} + asl + bcc :+ + ora #1 +: sta {{set duty}} + lda {{get note}} + beq :+ +; If next bit is set, add 1 to AUDF0 + adc #0 + sta AUDF0,x +: + --- + on musicpulse do foreach [MusicChannel] + --- +; Decrement the volumes for each channel +; Also decrement next-note timer, fetch next note + lda {{get duration}} + beq :+ + lsr + sta AUDV0,x + dec {{set duration}} +: +--- + on musicpulse do with [MusicPlayer] + --- + lda {{get timer}} + bmi @Done + beq @NextData + dec {{set timer}} + jmp @Done +; Timer ran out, so fetch next note +@NextData: + ldx #0 + lda ({{get songptr}},x) + bmi @LoadDuration +; < $80, play next note + ldx {{get channel}} ; next channel + tay + {{!musicnote}} + inx + txa + and #1 + sta {{set channel}} ; inc next channel + jmp @IncDataPtr +; >= $80, load next duration +@LoadDuration: + cmp #$ff ; $ff = end of song + bne @NoResetTrack + sta {{set timer}} + {{!musicdone}} + jmp @Done +@NoResetTrack: + and #$7f +; asl + sta {{set timer}} ; store duration * 2 +@IncDataPtr: +; increment song pointer + inc {{set songptr 0}} + bne @Done + inc {{set songptr 8}} +@Done: + --- + // TODO: should use "with"? + on musicnote do select all [MusicChannel] + --- +; Play a note +; X = channel (0,1) +; Y = note index (0-63) + lda {{^FREQZ}},y + sta {{base note}},x + lda {{^DUTYZ}},y + sta {{base duty}},x + lda {{^TONEZ}},y + sta AUDC0,x +; TODO: consts? + lda {{get MusicPlayer:tempo}} + sta {{base duration}},x + lda {{get MusicPlayer:volume}} + sta AUDV0,x + --- + on playmusic do foreach [MusicPlayer] limit 1 + --- + lda #<{{arg 0}} + sta {{set songptr 0}} + lda #>{{arg 0}} + sta {{set songptr 8}} + lda #0 + sta {{set timer}} + --- + on stopmusic do foreach [MusicPlayer] limit 1 + --- + lda #$ff + sta {{set timer}} + --- +end + +/// + +demo music + using FrameLoop + using MusicPlayer + entity [MusicChannel] end + entity [MusicChannel] end + entity MusicPlayer [MusicPlayer] + const volume = 10 + const tempo = 31 + end + system music + on musicdone do with [MusicPlayer] + --- + ; TODO: nested exprs + ; {{!playmusic ^SampleMusic}} + ;{{^SampleMusic}} + {{!playmusic SampleMusic}} + --- + on preframeloop do once + --- + {{!musicdone}} + --- + end +end demo + +resource SampleMusic --- + .byte $35,$41,$8a,$37,$43,$8a,$33,$3f,$8a,$30,$3c,$94,$3e,$32,$8a,$3a,$2e,$94,$35,$29,$8a,$37,$2b,$8a,$33,$27,$8a,$30,$24,$94,$32,$26,$8a,$2e,$22,$94,$29,$1d,$8a,$2b,$1f,$8a,$27,$1b,$8a,$24,$18,$94,$1a,$26,$8a,$18,$24,$8a,$17,$23,$8a,$16,$22,$a8,$3a,$35,$ff +--- + diff --git a/test/ecs/music.txt b/test/ecs/music.txt new file mode 100644 index 00000000..8eb467f0 --- /dev/null +++ b/test/ecs/music.txt @@ -0,0 +1,333 @@ +EVENT__start = 1 +EVENT__postframe = 1 +EVENT__preframe = 1 +EVENT__prekernel = 1 +EVENT__postkernel = 1 +EVENT__musicframe = 1 +EVENT__musicpulse = 1 +EVENT__musicnote = 1 +EVENT__playmusic = 1 +EVENT__stopmusic = 1 +EVENT__musicdone = 1 +EVENT__preframeloop = 1 +.scope music +.zeropage +MusicPlayer_timer_b0: +.res 1 +MusicChannel_duration_b0: +.res 1 +.res 1 +MusicChannel_note_b0: +.res 1 +.res 1 +MusicChannel_duty_b0: +.res 1 +.res 1 +MusicPlayer_channel_b0: +.res 1 +MusicPlayer_songptr_b0: +.res 1 +MusicPlayer_songptr_b8: +.res 1 +.code +MusicPlayer_volume_b0: +.byte 10 +MusicPlayer_tempo_b0: +.byte 31 +music__INITDATA: +.byte 255 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +__Start: + +;;; start action Init__main_init__1 + +.include "vcs-ca65.h" +.macpack longbranch +.define PAL 0 +__NMI: +__Reset: +__BRK: + CLEAN_START + + ldy #10 +: lda music__INITDATA-1,y + sta MusicPlayer_timer_b0-1,y + dey + bne :- + +;;; start action FrameLoop__start__3 + + +;;; start action music__preframeloop__5 + + +;;; start action music__musicdone__7 + + ; TODO: nested exprs + ; + jsr MusicPlayer__playmusic__10 + + ;SampleMusic + + jsr MusicPlayer__playmusic__13 + + +;;; end action music__musicdone__7 + + +;;; end action music__preframeloop__5 + +FrameLoop__start__4__NextFrame: + FRAME_END + + FRAME_START + +;;; start action MusicPlayer__preframe__16 + + +;;; start action MusicPlayer__musicpulse__18 + + ldx #0 +MusicPlayer__musicpulse__19____each: + +; Decrement the volumes for each channel +; Also decrement next-note timer, fetch next note + lda MusicChannel_duration_b0,x + beq :+ + lsr + sta AUDV0,x + dec MusicChannel_duration_b0,x +: + + inx + cpx #2 + jne MusicPlayer__musicpulse__19____each +MusicPlayer__musicpulse__19____exit: + +;;; end action MusicPlayer__musicpulse__18 + +;;; start action MusicPlayer__musicpulse__21 + + lda MusicPlayer_timer_b0 + bmi MusicPlayer__musicpulse__23__Done + beq MusicPlayer__musicpulse__23__NextData + dec MusicPlayer_timer_b0 + jmp MusicPlayer__musicpulse__23__Done +; Timer ran out, so fetch next note +MusicPlayer__musicpulse__23__NextData: + ldx #0 + lda (MusicPlayer_songptr_b0,x) + bmi MusicPlayer__musicpulse__23__LoadDuration +; < $80, play next note + ldx MusicPlayer_channel_b0 ; next channel + tay + +;;; start action MusicPlayer__musicnote__24 + +; Play a note +; X = channel (0,1) +; Y = note index (0-63) + lda FREQZ,y + sta MusicChannel_note_b0,x + lda DUTYZ,y + sta MusicChannel_duty_b0,x + lda TONEZ,y + sta AUDC0,x +; TODO: consts? + lda MusicPlayer_tempo_b0 + sta MusicChannel_duration_b0,x + lda MusicPlayer_volume_b0 + sta AUDV0,x + +;;; end action MusicPlayer__musicnote__24 + + inx + txa + and #1 + sta MusicPlayer_channel_b0 ; inc next channel + jmp MusicPlayer__musicpulse__23__IncDataPtr +; >= $80, load next duration +MusicPlayer__musicpulse__23__LoadDuration: + cmp #$ff ; $ff = end of song + bne MusicPlayer__musicpulse__23__NoResetTrack + sta MusicPlayer_timer_b0 + +;;; start action music__musicdone__27 + + ; TODO: nested exprs + ; + jsr MusicPlayer__playmusic__10 + + ;SampleMusic + + jsr MusicPlayer__playmusic__13 + + +;;; end action music__musicdone__27 + + jmp MusicPlayer__musicpulse__23__Done +MusicPlayer__musicpulse__23__NoResetTrack: + and #$7f +; asl + sta MusicPlayer_timer_b0 ; store duration * 2 +MusicPlayer__musicpulse__23__IncDataPtr: +; increment song pointer + inc MusicPlayer_songptr_b0 + bne MusicPlayer__musicpulse__23__Done + inc MusicPlayer_songptr_b8 +MusicPlayer__musicpulse__23__Done: + +;;; end action MusicPlayer__musicpulse__21 + ; update song + + jsr MusicPlayer__musicframe__36 + ; update registers + +;;; end action MusicPlayer__preframe__16 + + +;;; start action MusicPlayer__prekernel__39 + + + jsr MusicPlayer__musicframe__36 + ; update registers + +;;; end action MusicPlayer__prekernel__39 + + KERNEL_START + + KERNEL_END + +;;; start action MusicPlayer__postkernel__44 + + + jsr MusicPlayer__musicframe__36 + ; update registers + +;;; end action MusicPlayer__postkernel__44 + + +;;; start action FrameLoop__postframe__49 + + lsr SWCHB ; test Game Reset switch + bcs FrameLoop__postframe__50__NoStart + +FrameLoop__postframe__50__NoStart: + +;;; end action FrameLoop__postframe__49 + +;;; start action MusicPlayer__postframe__51 + + + jsr MusicPlayer__musicframe__36 + ; update registers + +;;; end action MusicPlayer__postframe__51 + + jmp FrameLoop__start__4__NextFrame ; loop to next frame + +;;; end action FrameLoop__start__3 + ; start main routine +.segment "VECTORS" +ZeroByte: .byte $00 +Return: .byte $60 +VecNMI: +VecReset: .word __Reset +VecBRK: .word __BRK +.code + +;;; end action Init__main_init__1 +.rodata +__ALIGNORIGIN: +.rodata +MusicPlayer__playmusic__10: + + lda #<^SampleMusic + sta MusicPlayer_songptr_b0 + lda #>^SampleMusic + sta MusicPlayer_songptr_b8 + lda #0 + sta MusicPlayer_timer_b0 + + rts +.rodata +MusicPlayer__playmusic__13: + + lda #SampleMusic + sta MusicPlayer_songptr_b8 + lda #0 + sta MusicPlayer_timer_b0 + + rts +.rodata +MusicPlayer__musicframe__36: + + ldx #0 +MusicPlayer__musicframe__37____each: + +; Update channel pitch in AUDF0 +; 8-bit rotation of duty cycle bits + lda MusicChannel_duration_b0,x + beq :++ + lda MusicChannel_duty_b0,x + asl + bcc :+ + ora #1 +: sta MusicChannel_duty_b0,x + lda MusicChannel_note_b0,x + beq :+ +; If next bit is set, add 1 to AUDF0 + adc #0 + sta AUDF0,x +: + + inx + cpx #2 + jne MusicPlayer__musicframe__37____each +MusicPlayer__musicframe__37____exit: + + rts +SampleMusic: + +;;; start action SampleMusic__SampleMusic__56 + + .byte $35,$41,$8a,$37,$43,$8a,$33,$3f,$8a,$30,$3c,$94,$3e,$32,$8a,$3a,$2e,$94,$35,$29,$8a,$37,$2b,$8a,$33,$27,$8a,$30,$24,$94,$32,$26,$8a,$2e,$22,$94,$29,$1d,$8a,$2b,$1f,$8a,$27,$1b,$8a,$24,$18,$94,$1a,$26,$8a,$18,$24,$8a,$17,$23,$8a,$16,$22,$a8,$3a,$35,$ff + +;;; end action SampleMusic__SampleMusic__56 + +FREQZ: + +;;; start action FREQZ__FREQZ__58 + + .byte 30, 30, 30, 30, 30, 28, 26, 25, 23, 22, 21, 19, 18, 17, 16, 15, 14, 13, 12, 12, 11, 10, 10, 9, 8, 8, 7, 7, 6, 6, 5, 5, 30, 29, 27, 25, 24, 22, 21, 20, 19, 18, 16, 15, 15, 14, 13, 12, 11, 11, 10, 31, 29, 27, 25, 24, 23, 21, 20, 19, 18, 16, 15, 15 + +;;; end action FREQZ__FREQZ__58 + +DUTYZ: + +;;; start action DUTYZ__DUTYZ__60 + + .byte 247, 247, 247, 247, 1, 73, 219, 1, 219, 73, 0, 219, 181, 85, 85, 85, 181, 219, 247, 1, 73, 181, 0, 73, 219, 17, 219, 17, 219, 73, 247, 85, 247, 1, 85, 247, 73, 247, 181, 17, 1, 0, 247, 247, 0, 1, 17, 73, 181, 0, 17, 0, 1, 85, 247, 73, 0, 181, 73, 1, 0, 247, 247, 0 + +;;; end action DUTYZ__DUTYZ__60 + +TONEZ: + +;;; start action TONEZ__TONEZ__62 + + .byte 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 + +;;; end action TONEZ__TONEZ__62 + +.endscope +music__Start = music::__Start \ No newline at end of file diff --git a/test/ecs/narrow1.ecs b/test/ecs/narrow1.ecs new file mode 100644 index 00000000..164c9bde --- /dev/null +++ b/test/ecs/narrow1.ecs @@ -0,0 +1,28 @@ + +component Xpos + x: 0..255 +end +component Player +end +component Enemy +end + +scope Main + entity foo [Xpos] + end + entity p [Xpos,Player] + init x = 50 + end + entity e1 [Xpos,Enemy] + init x = 100 + end + entity e2 [Xpos,Enemy] + init x = 150 + end + system move + on start do foreach [Enemy] + --- + lda {{{{^FontTable}} + adc #0 + sta Digit0+1,x ; store pointer hi byte + inx + inx ; next bitmap pointer + lda @BCD0,y ; get BCD value (again) + and #$f ; isolate low nibble + asl + asl + asl ; * 8 + clc + adc #<{{^FontTable}} + sta Digit0,x ; store pointer lo byte + lda #>{{^FontTable}} + adc #0 + sta Digit0+1,x ; store pointer hi byte + inx + inx ; next bitmap pointer + dey ; next BCD value + bpl @Loop ; repeat until < 0 +--- + on kernel do with [BCDScore6,PFColor] +--- + lda {{ [GRP0] + lda (Digit1),y ; load B1 -> A + sta GRP1 ; B1 -> [GRP1], B0 -> GRP0 + sta WSYNC ; sync to next scanline + lda (Digit2),y ; load B2 -> A + sta GRP0 ; B2 -> [GRP0], B1 -> GRP1 + lda (Digit5),y ; load B5 -> A + sta @Temp ; B5 -> temp + lda (Digit4),y ; load B4 + tax ; -> X + lda (Digit3),y ; load B3 -> A + ldy @Temp ; load B5 -> Y + sta GRP1 ; B3 -> [GRP1]; B2 -> GRP0 + stx GRP0 ; B4 -> [GRP0]; B3 -> GRP1 + sty GRP1 ; B5 -> [GRP1]; B4 -> GRP0 + sta GRP0 ; ?? -> [GRP0]; B5 -> GRP1 + dec @LoopCount ; go to next line + bpl :- ; repeat until < 0 + + lda #0 ; clear the sprite registers + sta WSYNC + sta GRP0 + sta GRP1 + sta GRP0 + sta GRP1 + sta COLUBK +--- +end + + +resource FontTable --- +; Font table for digits 0-9 (8x8 pixels) +;;{w:8,h:8,count:10,brev:1,flip:1};; + .byte $00,$3c,$66,$66,$76,$6e,$66,$3c,$00,$7e,$18,$18,$18,$38,$18,$18 + .byte $00,$7e,$60,$30,$0c,$06,$66,$3c,$00,$3c,$66,$06,$1c,$06,$66,$3c + .byte $00,$06,$06,$7f,$66,$1e,$0e,$06,$00,$3c,$66,$06,$06,$7c,$60,$7e + .byte $00,$3c,$66,$66,$7c,$60,$66,$3c,$00,$18,$18,$18,$18,$0c,$66,$7e + .byte $00,$3c,$66,$66,$3c,$66,$66,$3c,$00,$3c,$66,$06,$3e,$66,$66,$3c +--- + +system Kernel2Digit + locals 3 + on preframe do once +--- + lda #0 + sta {{$1}} + sta {{$2}} +--- + on kernel do select [BCDScore2] +--- + lda #$02 + sta CTRLPF +; TODO: should be constants +; and it's wrong, too! + lda {{1 + inx + {{!compute2digit 1}} +.else + {{!compute2digit 0}} +.endif +; playfield + dec {{$0}} + jpl @Loop +; dex +; stx PF1 +--- + on compute2digit do once +--- + lda {{$1}} ; load 1st pf + sta PF1 ; store 1st pf +; first digit + lda {{{{arg 0}} +.endif +--- + on AddBCD4 do with [BCDScore6] +--- +; Adds value to 6-BCD-digit score. +; A = 1st BCD digit +; Y = 2nd BCD digit + sed ; enter BCD mode + clc ; clear carry + adc {{get digits}} + sta {{set digits}} + tya + adc {{get digits 8}} + sta {{set digits 8}} + lda {{get digits 16}} + adc #0 + sta {{set digits 16}} + cld ; exit BCD mode +--- + on AddBCD2 do once +--- +.ifnblank {{arg 0}} + lda #<{{arg 0}} +.endif +--- + on SubBCD2 do once +--- +.ifnblank {{arg 0}} + lda #<{{arg 0}} +.endif +--- + on AddBCD2 do with [BCDScore2] +--- + sed ; enter BCD mode + clc ; clear carry + adc {{get digits}} + sta {{set digits}} + cld ; exit BCD mode + bcc :+ + lda #$99 + sta {{set digits}} +: +--- + on SubBCD2 do with [BCDScore2] +--- +; TODO? + tay + lda {{get digits}} + sty {{set digits}} + sed ; enter BCD mode + sec ; set carry + sbc {{get digits}} + sta {{set digits}} + cld ; exit BCD mode + bcs :+ + lda #0 + sta {{set digits}} +: +--- +end + +demo Main + + using FrameLoop + using Kernel6Digit + using Kernel2Digit + using JoyButton, BCDMath + + entity [Player,BCDScore6,PFColor,BGColor] + init digits = 0x123456 + init pfcolor = $3c + init bgcolor = $02 + end + + entity [BCDScore2] + init digits = 0x24 + init scorecolor = $ce + end + entity [BCDScore2] + init digits = 0x56 + init scorecolor = $3e + end + + system IncScore + on joybutton do with [Player,BCDScore6] +--- + {{!AddBCD4 $0210}} +--- + end +end demo + diff --git a/test/ecs/score.txt b/test/ecs/score.txt new file mode 100644 index 00000000..1e50ac22 --- /dev/null +++ b/test/ecs/score.txt @@ -0,0 +1,510 @@ +EVENT__start = 1 +EVENT__postframe = 1 +EVENT__preframe = 1 +EVENT__kernel = 1 +EVENT__compute2digit = 1 +EVENT__fetchdigit = 1 +EVENT__AddBCD4 = 1 +EVENT__AddBCD2 = 1 +EVENT__SubBCD2 = 1 +EVENT__joybutton = 1 +.scope Main +.zeropage +BCDScore6_digits_b0: +.res 1 +BCDScore6_digits_b8: +.res 1 +BCDScore6_digits_b16: +.res 1 +PFColor_pfcolor_b0: +.res 1 +BGColor_bgcolor_b0: +.res 1 +BCDScore2_digits_b0: +.res 1 +.res 1 +BCDScore2_scorecolor_b0: +.res 1 +.res 1 +TEMP: +Kernel6Digit__2__tmp: +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +Kernel2Digit__3__tmp: +.res 1 +.res 1 +.res 1 +.code +Main__INITDATA: +.byte 86 +.byte 52 +.byte 18 +.byte 60 +.byte 2 +.byte 36 +.byte 86 +.byte 206 +.byte 62 +__Start: + +;;; start action Init__main_init__1 + +.include "vcs-ca65.h" +.macpack longbranch +.define PAL 0 +__NMI: +__Reset: +__BRK: + CLEAN_START + + ldy #9 +: lda Main__INITDATA-1,y + sta BCDScore6_digits_b0-1,y + dey + bne :- + +;;; start action FrameLoop__start__3 + + +FrameLoop__start__4__NextFrame: + FRAME_END + + FRAME_START + +;;; start action Kernel6Digit__preframe__5 + +Digit0 = Kernel6Digit__2__tmp+0 +Digit1 = Kernel6Digit__2__tmp+2 +Digit2 = Kernel6Digit__2__tmp+4 +Digit3 = Kernel6Digit__2__tmp+6 +Digit4 = Kernel6Digit__2__tmp+8 +Digit5 = Kernel6Digit__2__tmp+10 +Kernel6Digit__preframe__7__BCD0 = Kernel6Digit__2__tmp+12 +Kernel6Digit__preframe__7__BCD1 = Kernel6Digit__2__tmp+13 +Kernel6Digit__preframe__7__BCD2 = Kernel6Digit__2__tmp+14 + + lda BCDScore6_digits_b0 + sta Kernel6Digit__preframe__7__BCD0 + lda BCDScore6_digits_b8 + sta Kernel6Digit__preframe__7__BCD1 + lda BCDScore6_digits_b16 + sta Kernel6Digit__preframe__7__BCD2 + ldx #0 ; leftmost bitmap + ldy #2 ; start from most-sigificant BCD value +Kernel6Digit__preframe__7__Loop: + lda Kernel6Digit__preframe__7__BCD0,y ; get BCD value + and #$f0 ; isolate high nibble (* 16) + lsr ; shift right 1 bit (* 8) + clc + adc #FontTable + adc #0 + sta Digit0+1,x ; store pointer hi byte + inx + inx ; next bitmap pointer + lda Kernel6Digit__preframe__7__BCD0,y ; get BCD value (again) + and #$f ; isolate low nibble + asl + asl + asl ; * 8 + clc + adc #FontTable + adc #0 + sta Digit0+1,x ; store pointer hi byte + inx + inx ; next bitmap pointer + dey ; next BCD value + bpl Kernel6Digit__preframe__7__Loop ; repeat until < 0 + +;;; end action Kernel6Digit__preframe__5 + +;;; start action Kernel2Digit__preframe__8 + + lda #0 + sta Kernel2Digit__3__tmp+1 + sta Kernel2Digit__3__tmp+2 + +;;; end action Kernel2Digit__preframe__8 + + + KERNEL_START + +;;; start action Kernel6Digit__kernel__10 + + lda PFColor_pfcolor_b0 + sta COLUP0 + sta COLUP1 + lda #3 + sta NUSIZ0 + sta NUSIZ1 +; set horizontal position of player objects + sta WSYNC + sta HMCLR + SLEEPR 24 + sta RESP0 + sta RESP1 + lda #$10 + sta HMP1 + sta WSYNC + sta HMOVE + SLEEPR 24 ; wait 24 cycles between write to HMOVE and HMxxx + sta HMCLR + lda #1 + sta VDELP0 + sta VDELP1 + +;;; end action Kernel6Digit__kernel__10 + + jsr Kernel6Digit__kernel__13 + +;;; start action Kernel2Digit__kernel__16 + + lda #$02 + sta CTRLPF +; TODO: should be constants +; and it's wrong, too! + lda BCDScore2_scorecolor_b0+0 + sta COLUP0 + lda BCDScore2_scorecolor_b0+1 + sta COLUP1 + +;;; end action Kernel2Digit__kernel__16 + + jsr Kernel2Digit__kernel__19 + + KERNEL_END + + +;;; start action FrameLoop__postframe__40 + + lsr SWCHB ; test Game Reset switch + bcs FrameLoop__postframe__41__NoStart + +FrameLoop__postframe__41__NoStart: + +;;; end action FrameLoop__postframe__40 + +;;; start action JoyButton__postframe__42 + + lda INPT4 ;read button input + bmi JoyButton__postframe__44__NotPressed + +;;; start action IncScore__joybutton__45 + + +;;; start action BCDMath__AddBCD4__48 + +.ifnblank $0210 + lda #<$0210 + ldy #>$0210 +.endif + +;;; end action BCDMath__AddBCD4__48 + +;;; start action BCDMath__AddBCD4__50 + +; Adds value to 6-BCD-digit score. +; A = 1st BCD digit +; Y = 2nd BCD digit + sed ; enter BCD mode + clc ; clear carry + adc BCDScore6_digits_b0 + sta BCDScore6_digits_b0 + tya + adc BCDScore6_digits_b8 + sta BCDScore6_digits_b8 + lda BCDScore6_digits_b16 + adc #0 + sta BCDScore6_digits_b16 + cld ; exit BCD mode + +;;; end action BCDMath__AddBCD4__50 + + +;;; end action IncScore__joybutton__45 + +JoyButton__postframe__44__NotPressed: + +;;; end action JoyButton__postframe__42 + + jmp FrameLoop__start__4__NextFrame ; loop to next frame + +;;; end action FrameLoop__start__3 + ; start main routine +.segment "VECTORS" +ZeroByte: .byte $00 +Return: .byte $60 +VecNMI: +VecReset: .word __Reset +VecBRK: .word __BRK +.code + +;;; end action Init__main_init__1 +.rodata +__ALIGNORIGIN: +.rodata +Kernel6Digit__kernel__13: + +; Display the resulting 48x8 bitmap +; using the Digit0-5 pointers. +Kernel6Digit__kernel__15__LoopCount = Kernel6Digit__2__tmp+12 +Kernel6Digit__kernel__15__Temp = Kernel6Digit__2__tmp+13 + + lda BGColor_bgcolor_b0 + sta WSYNC + sta COLUBK + lda #7 + sta Kernel6Digit__kernel__15__LoopCount + SLEEPR 20 ; TODO? +: + ldy Kernel6Digit__kernel__15__LoopCount ; counts backwards + lda (Digit0),y ; load B0 (1st sprite byte) + sta GRP0 ; B0 -> [GRP0] + lda (Digit1),y ; load B1 -> A + sta GRP1 ; B1 -> [GRP1], B0 -> GRP0 + sta WSYNC ; sync to next scanline + lda (Digit2),y ; load B2 -> A + sta GRP0 ; B2 -> [GRP0], B1 -> GRP1 + lda (Digit5),y ; load B5 -> A + sta Kernel6Digit__kernel__15__Temp ; B5 -> temp + lda (Digit4),y ; load B4 + tax ; -> X + lda (Digit3),y ; load B3 -> A + ldy Kernel6Digit__kernel__15__Temp ; load B5 -> Y + sta GRP1 ; B3 -> [GRP1]; B2 -> GRP0 + stx GRP0 ; B4 -> [GRP0]; B3 -> GRP1 + sty GRP1 ; B5 -> [GRP1]; B4 -> GRP0 + sta GRP0 ; ?? -> [GRP0]; B5 -> GRP1 + dec Kernel6Digit__kernel__15__LoopCount ; go to next line + bpl :- ; repeat until < 0 + + lda #0 ; clear the sprite registers + sta WSYNC + sta GRP0 + sta GRP1 + sta GRP0 + sta GRP1 + sta COLUBK + + rts + +.assert >(Kernel6Digit__kernel__13) = >(*), error, "Kernel6Digit__kernel__13 crosses a page boundary!" + +.assert (* - Kernel6Digit__kernel__13) <= 72, error, .sprintf("Kernel6Digit__kernel__13 does not fit in 72 bytes, it took %d!", (* - Kernel6Digit__kernel__13)) + +.if <(* - __ALIGNORIGIN) > 256-98 +.align $100 +.endif +.rodata +Kernel2Digit__kernel__19: + + lda #7 + sta Kernel2Digit__3__tmp+0 +Kernel2Digit__kernel__21__Loop: + ldx #0 + sta WSYNC + +;;; start action Kernel2Digit__compute2digit__22 + + lda Kernel2Digit__3__tmp+1 ; load 1st pf + sta PF1 ; store 1st pf +; first digit + lda BCDScore2_digits_b0 + 0 + pha + and #$0f + asl + asl + asl + +;;; start action Kernel2Digit__fetchdigit__24 + + adc Kernel2Digit__3__tmp+0 + tay + ; TODO: select your own? + lda FontTablePF,y + +;;; end action Kernel2Digit__fetchdigit__24 + + and #$0f + ldy Kernel2Digit__3__tmp+2 ; load 2nd pf + sta Kernel2Digit__3__tmp+1 + 0 +; second digit + pla + and #$f0 + lsr + sty PF1 ; store 2nd pf + +;;; start action Kernel2Digit__fetchdigit__26 + + adc Kernel2Digit__3__tmp+0 + tay + ; TODO: select your own? + lda FontTablePF,y + +;;; end action Kernel2Digit__fetchdigit__26 + + and #$f0 + ora Kernel2Digit__3__tmp+1 + 0 + sta Kernel2Digit__3__tmp+1 + 0 + +;;; end action Kernel2Digit__compute2digit__22 + +.if 2>1 + inx + +;;; start action Kernel2Digit__compute2digit__28 + + lda Kernel2Digit__3__tmp+1 ; load 1st pf + sta PF1 ; store 1st pf +; first digit + lda BCDScore2_digits_b0 + 1 + pha + and #$0f + asl + asl + asl + +;;; start action Kernel2Digit__fetchdigit__30 + + adc Kernel2Digit__3__tmp+0 + tay + ; TODO: select your own? + lda FontTablePF,y + +;;; end action Kernel2Digit__fetchdigit__30 + + and #$0f + ldy Kernel2Digit__3__tmp+2 ; load 2nd pf + sta Kernel2Digit__3__tmp+1 + 1 +; second digit + pla + and #$f0 + lsr + sty PF1 ; store 2nd pf + +;;; start action Kernel2Digit__fetchdigit__32 + + adc Kernel2Digit__3__tmp+0 + tay + ; TODO: select your own? + lda FontTablePF,y + +;;; end action Kernel2Digit__fetchdigit__32 + + and #$f0 + ora Kernel2Digit__3__tmp+1 + 1 + sta Kernel2Digit__3__tmp+1 + 1 + +;;; end action Kernel2Digit__compute2digit__28 + +.else + +;;; start action Kernel2Digit__compute2digit__34 + + lda Kernel2Digit__3__tmp+1 ; load 1st pf + sta PF1 ; store 1st pf +; first digit + lda BCDScore2_digits_b0 + 0 + pha + and #$0f + asl + asl + asl + +;;; start action Kernel2Digit__fetchdigit__36 + + adc Kernel2Digit__3__tmp+0 + tay + ; TODO: select your own? + lda FontTablePF,y + +;;; end action Kernel2Digit__fetchdigit__36 + + and #$0f + ldy Kernel2Digit__3__tmp+2 ; load 2nd pf + sta Kernel2Digit__3__tmp+1 + 0 +; second digit + pla + and #$f0 + lsr + sty PF1 ; store 2nd pf + +;;; start action Kernel2Digit__fetchdigit__38 + + adc Kernel2Digit__3__tmp+0 + tay + ; TODO: select your own? + lda FontTablePF,y + +;;; end action Kernel2Digit__fetchdigit__38 + + and #$f0 + ora Kernel2Digit__3__tmp+1 + 0 + sta Kernel2Digit__3__tmp+1 + 0 + +;;; end action Kernel2Digit__compute2digit__34 + +.endif +; playfield + dec Kernel2Digit__3__tmp+0 + jpl Kernel2Digit__kernel__21__Loop +; dex +; stx PF1 + + rts + +.assert >(Kernel2Digit__kernel__19) = >(*), error, "Kernel2Digit__kernel__19 crosses a page boundary!" + +.assert (* - Kernel2Digit__kernel__19) <= 98, error, .sprintf("Kernel2Digit__kernel__19 does not fit in 98 bytes, it took %d!", (* - Kernel2Digit__kernel__19)) +FontTable: + +;;; start action FontTable__FontTable__53 + +; Font table for digits 0-9 (8x8 pixels) +;;{w:8,h:8,count:10,brev:1,flip:1};; + .byte $00,$3c,$66,$66,$76,$6e,$66,$3c,$00,$7e,$18,$18,$18,$38,$18,$18 + .byte $00,$7e,$60,$30,$0c,$06,$66,$3c,$00,$3c,$66,$06,$1c,$06,$66,$3c + .byte $00,$06,$06,$7f,$66,$1e,$0e,$06,$00,$3c,$66,$06,$06,$7c,$60,$7e + .byte $00,$3c,$66,$66,$7c,$60,$66,$3c,$00,$18,$18,$18,$18,$0c,$66,$7e + .byte $00,$3c,$66,$66,$3c,$66,$66,$3c,$00,$3c,$66,$06,$3e,$66,$66,$3c + +;;; end action FontTable__FontTable__53 + +FontTablePF: + +;;; start action FontTablePF__FontTablePF__55 + +; Font table for digits 0-9 (4x8 pixels) +;;{w:8,h:8,count:10,brev:1,flip:1};; + .byte $00,$00,$EE,$AA,$AA,$AA,$EE,$00 + .byte $00,$00,$22,$22,$22,$22,$22,$00 + .byte $00,$00,$EE,$88,$EE,$22,$EE,$00 + .byte $00,$00,$EE,$22,$66,$22,$EE,$00 + .byte $00,$00,$22,$22,$EE,$AA,$AA,$00 + .byte $00,$00,$EE,$22,$EE,$88,$EE,$00 + .byte $00,$00,$EE,$AA,$EE,$88,$EE,$00 + .byte $00,$00,$22,$22,$22,$22,$EE,$00 + .byte $00,$00,$EE,$AA,$EE,$AA,$EE,$00 + .byte $00,$00,$EE,$22,$EE,$AA,$EE,$00 +;; + +;;; end action FontTablePF__FontTablePF__55 + +.endscope +Main__Start = Main::__Start \ No newline at end of file diff --git a/test/ecs/sound.ecs b/test/ecs/sound.ecs new file mode 100644 index 00000000..283751a7 --- /dev/null +++ b/test/ecs/sound.ecs @@ -0,0 +1,105 @@ + +//#resource "vcs-ca65.h" + +import "vcslib.ecs" + +component SoundEffect + duration: 0..255 + sounddata: array of 0..255 +end + +component SoundChannel + sfx: [SoundEffect] + timer: 0..255 +end + +// TODO +component SoundPriority + priority: 0..15 +end + +system SoundEngine + locals 3 + on preframe do + join [SoundChannel] + with [SoundEffect] + --- + lda {{base timer}},y + jeq @nosound + sec + sbc #1 + sta {{base timer}},y + pha + lda {{sounddata}} + sta {{$1}} ; save pointer to sound data + sty {{$2}} ; save Y (sound channel #) + pla + tay + lda ({{$0}}),y ; get sound data + bpl @setfreq ; hi bit clear = just freq + ldy {{$2}} + lsr ; right shift (/ 2) + bcs @setvol ; lo bit set = volume + sta AUDC0,y ; lo bit clear = control + lsr ; also set freq (/ 2) +@setfreq: + ldy {{$2}} + sta AUDF0,y ; set frequency + jmp @done +@nosound: + lda #0 +@setvol: + sta AUDV0,y ; set volume +@done: + --- + // TODO: need to pass sound entity as arg + on playsound do select [SoundChannel] + --- +; arg 0 = sound channel + ldy #{{arg 0}} +; arg 1 = sound effect # + lda #{{arg 1}} + sta {{base sfx}},y + tax + --- + // TODO: shouldn't need to split up like this... + on playsound do select [SoundEffect] + --- + lda {{base duration}},x + --- + on playsound do select [SoundChannel] + --- + sta {{base timer}},y +; arg 2 = base volume + lda #{{arg 2}} + sta AUDV0,y + --- +end + +// TODO: default entities? + + +demo SoundDemo + using FrameLoop, SoundEngine + + entity SFXScore [SoundEffect] + const duration = 11 + const sounddata = [ + $02,$03,$04,$08,$10,$20,$10,$20,$10,$08, + $a8] + end + + // TODO: make sfx have priority? + entity SFX1 [SoundChannel] end + entity SFX2 [SoundChannel] end + + system Test + on preframeloop do once + --- + {{!playsound 0 0 15}} + --- + end +end demo + diff --git a/test/ecs/sound.txt b/test/ecs/sound.txt new file mode 100644 index 00000000..ec688807 --- /dev/null +++ b/test/ecs/sound.txt @@ -0,0 +1,174 @@ +EVENT__start = 1 +EVENT__postframe = 1 +EVENT__preframe = 1 +EVENT__playsound = 1 +EVENT__preframeloop = 1 +.scope SoundDemo +.zeropage +SoundChannel_sfx_b0: +.res 1 +.res 1 +SoundChannel_timer_b0: +.res 1 +.res 1 +TEMP: +SoundEngine__2__tmp: +.res 1 +.res 1 +.res 1 +.code +SoundEffect_duration_b0: +.byte 11 +SoundEffect_sounddata_b0: +.byte SoundEffect_sounddata_e0_b0 +SoundEffect_sounddata_e0_b0: +.byte 2 +.byte 3 +.byte 4 +.byte 8 +.byte 16 +.byte 32 +.byte 16 +.byte 32 +.byte 16 +.byte 8 +.byte 168 +SoundDemo__INITDATA: +.byte 0 +.byte 0 +.byte 0 +.byte 0 +__Start: + +;;; start action Init__main_init__1 + +.include "vcs-ca65.h" +.macpack longbranch +.define PAL 0 +__NMI: +__Reset: +__BRK: + CLEAN_START + + ldy #4 +: lda SoundDemo__INITDATA-1,y + sta SoundChannel_sfx_b0-1,y + dey + bne :- + +;;; start action FrameLoop__start__3 + + +;;; start action Test__preframeloop__5 + + +;;; start action SoundEngine__playsound__7 + +; arg 0 = sound channel + ldy #0 +; arg 1 = sound effect # + lda #0 + sta SoundChannel_sfx_b0,y + tax + +;;; end action SoundEngine__playsound__7 + +;;; start action SoundEngine__playsound__10 + + lda SoundEffect_duration_b0,x + +;;; end action SoundEngine__playsound__10 + +;;; start action SoundEngine__playsound__13 + + sta SoundChannel_timer_b0,y +; arg 2 = base volume + lda #15 + sta AUDV0,y + +;;; end action SoundEngine__playsound__13 + + +;;; end action Test__preframeloop__5 + +FrameLoop__start__4__NextFrame: + FRAME_END + + FRAME_START + +;;; start action SoundEngine__preframe__16 + + ldy #0 +SoundEngine__preframe__17____each: + ldx SoundChannel_sfx_b0,y + + lda SoundChannel_timer_b0,y + jeq SoundEngine__preframe__18__nosound + sec + sbc #1 + sta SoundChannel_timer_b0,y + pha + lda SoundEffect_sounddata_b0,x + sta SoundEngine__2__tmp+0 + lda SoundEffect_sounddata_b8,x + sta SoundEngine__2__tmp+1 ; save pointer to sound data + sty SoundEngine__2__tmp+2 ; save Y (sound channel #) + pla + tay + lda (SoundEngine__2__tmp+0),y ; get sound data + bpl SoundEngine__preframe__18__setfreq ; hi bit clear = just freq + ldy SoundEngine__2__tmp+2 + lsr ; right shift (/ 2) + bcs SoundEngine__preframe__18__setvol ; lo bit set = volume + sta AUDC0,y ; lo bit clear = control + lsr ; also set freq (/ 2) +SoundEngine__preframe__18__setfreq: + ldy SoundEngine__2__tmp+2 + sta AUDF0,y ; set frequency + jmp SoundEngine__preframe__18__done +SoundEngine__preframe__18__nosound: + lda #0 +SoundEngine__preframe__18__setvol: + sta AUDV0,y ; set volume +SoundEngine__preframe__18__done: + + iny + cpy #2 + jne SoundEngine__preframe__17____each +SoundEngine__preframe__17____exit: + +;;; end action SoundEngine__preframe__16 + + + KERNEL_START + + KERNEL_END + + +;;; start action FrameLoop__postframe__19 + + lsr SWCHB ; test Game Reset switch + bcs FrameLoop__postframe__20__NoStart + +FrameLoop__postframe__20__NoStart: + +;;; end action FrameLoop__postframe__19 + + jmp FrameLoop__start__4__NextFrame ; loop to next frame + +;;; end action FrameLoop__start__3 + ; start main routine +.segment "VECTORS" +ZeroByte: .byte $00 +Return: .byte $60 +VecNMI: +VecReset: .word __Reset +VecBRK: .word __BRK +.code + +;;; end action Init__main_init__1 + +.endscope +SoundDemo__Start = SoundDemo::__Start \ No newline at end of file diff --git a/test/ecs/sprites.ecs b/test/ecs/sprites.ecs new file mode 100644 index 00000000..40adb7a9 --- /dev/null +++ b/test/ecs/sprites.ecs @@ -0,0 +1,177 @@ + +//#resource "vcs-ca65.h" + +import "vcslib.ecs" + +component Bitmap + bitmapdata: array of 0..255 baseoffset 31 + height: 0..255 +end + +// TODO: remove? +component HasBitmap +end + +component Colormap + colormapdata: array of 0..255 baseoffset 31 +end + +component HasColormap + colormap: [Colormap] +end + +component Sprite + bitmap: [Bitmap] + plyrflags: 0..63 +end + +component HasXpos + xpos: 0..255 +end + +component HasYpos + ypos: 0..255 +end + +component Missile + index: 2..3 +end + +component SpriteSlot + sprite: [Sprite] // TODO: HasBitmap? +end + +system JoyFaceDirection + on joyleft do with [Sprite] +--- + lda {{Bitmap:bitmapdata}},x + sbc #0 + sta {{$2}},y +; get bitmap height + lda {{Colormap:colormapdata}},x + sbc #0 + sta {{$6}},y +; save ypos + ldx {{$12}} ; restore X + lda {{ L0 H0 L1 H1 + lda {{$1}} + ldy {{$2}} + sty {{$1}} + sta {{$2}} + lda {{$5}} + ldy {{$6}} + sty {{$5}} + sta {{$6}} +--- + on preframe do if [BGColor] +--- + lda {{data}} + sta {{$1}} +--- + on scanline1 do with [VersatilePlayfield] +--- + lda ({{local 0}}),y + tax +--- + on scanline2 do with [VersatilePlayfield] +--- + lda ({{local 0}}),y + sta $00,x +--- +end + +system SetXPos + on preframe do once +--- + sta HMCLR +--- + on preframe do join [SpriteSlot] with [HasXpos] + limit 2 +--- + lda {{(Bitmap_bitmapdata_e1_b0+31) +.byte >(Bitmap_bitmapdata_e2_b0+31) +.byte >(Bitmap_bitmapdata_e3_b0+31) +Bitmap_bitmapdata_e1_b0: +Colormap_colormapdata_e1_b0: +VersatilePlayfield_data_e4_b0: +.byte 0 +Bitmap_height_b0: +.byte 0 +.byte 17 +.byte 27 +Colormap_colormapdata_b0: +.byte <(Colormap_colormapdata_e1_b0+31) +.byte <(Colormap_colormapdata_e2_b0+31) +.byte <(Colormap_colormapdata_e3_b0+31) +Colormap_colormapdata_b8: +.byte >(Colormap_colormapdata_e1_b0+31) +.byte >(Colormap_colormapdata_e2_b0+31) +.byte >(Colormap_colormapdata_e3_b0+31) +Bitmap_bitmapdata_e2_b0: +.byte 128 +.byte 192 +.byte 192 +.byte 224 +.byte 224 +.byte 112 +.byte 116 +.byte 62 +.byte 61 +.byte 28 +.byte 28 +.byte 12 +.byte 22 +.byte 39 +.byte 70 +.byte 134 +.byte 6 +.byte 3 +Colormap_colormapdata_e2_b0: +.byte 70 +.byte 70 +.byte 134 +.byte 134 +.byte 134 +.byte 136 +.byte 134 +.byte 134 +.byte 134 +.byte 134 +.byte 136 +.byte 70 +.byte 72 +.byte 72 +.byte 72 +.byte 72 +.byte 84 +.byte 82 +Bitmap_bitmapdata_e3_b0: +.byte 14 +.byte 14 +.byte 12 +.byte 12 +.byte 12 +.byte 12 +.byte 12 +.byte 12 +.byte 14 +.byte 30 +.byte 31 +.byte 63 +.byte 63 +.byte 63 +.byte 62 +.byte 62 +.byte 44 +.byte 46 +.byte 46 +.byte 47 +.byte 46 +.byte 46 +.byte 38 +.byte 32 +.byte 40 +.byte 48 +.byte 96 +.byte 128 +Colormap_colormapdata_e3_b0: +.byte 4 +.byte 4 +.byte 198 +.byte 198 +.byte 198 +.byte 200 +.byte 200 +.byte 200 +.byte 38 +.byte 40 +.byte 40 +.byte 40 +.byte 40 +.byte 40 +.byte 42 +.byte 42 +.byte 70 +.byte 72 +.byte 72 +.byte 72 +.byte 72 +.byte 72 +.byte 70 +.byte 4 +.byte 4 +.byte 4 +.byte 4 +.byte 4 +Room_fgcolor_b0: +.byte 0 +.byte 12 +.byte 12 +Room_bgcolor_b0: +.byte 0 +.byte 18 +.byte 18 +Room_north_b0: +.byte 0 +.byte 1 +.byte 1 +Room_east_b0: +.byte 0 +.byte 2 +.byte 1 +Room_south_b0: +.byte 0 +.byte 1 +.byte 1 +Room_west_b0: +.byte 0 +.byte 2 +.byte 1 +VersatilePlayfield_data_b0: +.byte <(VersatilePlayfield_data_e4_b0+-1) +.byte <(VersatilePlayfield_data_e5_b0+-1) +.byte <(VersatilePlayfield_data_e6_b0+-1) +VersatilePlayfield_data_b8: +.byte >(VersatilePlayfield_data_e4_b0+-1) +.byte >(VersatilePlayfield_data_e5_b0+-1) +.byte >(VersatilePlayfield_data_e6_b0+-1) +VersatilePlayfield_data_e5_b0: +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 255 +.byte 14 +.byte 85 +.byte 14 +.byte 24 +.byte 9 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 6 +.byte 8 +.byte 0 +.byte 63 +.byte 255 +.byte 15 +.byte 247 +.byte 15 +.byte 128 +.byte 15 +.byte 0 +.byte 63 +.byte 193 +.byte 15 +.byte 227 +.byte 15 +.byte 0 +.byte 63 +.byte 247 +.byte 15 +.byte 200 +.byte 9 +.byte 0 +.byte 10 +.byte 255 +.byte 15 +.byte 255 +.byte 14 +.byte 240 +.byte 13 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 56 +.byte 9 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 247 +.byte 15 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 246 +.byte 15 +.byte 0 +.byte 63 +.byte 70 +.byte 9 +.byte 240 +.byte 15 +.byte 0 +.byte 63 +.byte 176 +.byte 15 +.byte 127 +.byte 14 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 107 +.byte 14 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 68 +.byte 9 +.byte 240 +.byte 15 +.byte 0 +.byte 63 +.byte 176 +.byte 15 +.byte 127 +.byte 14 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 107 +.byte 14 +.byte 240 +.byte 15 +.byte 80 +.byte 9 +.byte 176 +.byte 15 +.byte 127 +.byte 14 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 107 +.byte 14 +.byte 240 +.byte 15 +.byte 112 +.byte 15 +.byte 48 +.byte 15 +.byte 16 +.byte 15 +.byte 144 +.byte 15 +.byte 96 +.byte 13 +.byte 127 +.byte 14 +.byte 96 +.byte 9 +.byte 0 +.byte 63 +.byte 208 +.byte 15 +.byte 144 +.byte 15 +.byte 16 +.byte 15 +.byte 48 +.byte 15 +.byte 112 +.byte 15 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 240 +.byte 15 +.byte 224 +.byte 15 +.byte 120 +.byte 14 +.byte 192 +.byte 15 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 8 +.byte 8 +.byte 1 +.byte 10 +.byte 192 +.byte 9 +VersatilePlayfield_data_e6_b0: +.byte 63 +.byte 63 +.byte 63 +.byte 63 +.byte 63 +.byte 63 +.byte 63 +.byte 63 +.byte 63 +.byte 63 +.byte 63 +.byte 63 +.byte 63 +.byte 63 +.byte 63 +.byte 63 +.byte 6 +.byte 9 +.byte 24 +.byte 9 +.byte 0 +.byte 63 +.byte 85 +.byte 15 +.byte 170 +.byte 14 +.byte 80 +.byte 13 +.byte 6 +.byte 9 +.byte 6 +.byte 8 +.byte 8 +.byte 8 +.byte 255 +.byte 14 +.byte 247 +.byte 14 +.byte 128 +.byte 14 +.byte 0 +.byte 63 +.byte 193 +.byte 14 +.byte 227 +.byte 14 +.byte 0 +.byte 63 +.byte 247 +.byte 14 +.byte 200 +.byte 9 +.byte 0 +.byte 10 +.byte 255 +.byte 15 +.byte 255 +.byte 14 +.byte 240 +.byte 13 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 56 +.byte 9 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 247 +.byte 15 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 246 +.byte 15 +.byte 0 +.byte 63 +.byte 70 +.byte 9 +.byte 240 +.byte 15 +.byte 0 +.byte 63 +.byte 176 +.byte 15 +.byte 127 +.byte 14 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 107 +.byte 14 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 68 +.byte 9 +.byte 240 +.byte 15 +.byte 0 +.byte 63 +.byte 176 +.byte 15 +.byte 127 +.byte 14 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 107 +.byte 14 +.byte 240 +.byte 15 +.byte 80 +.byte 9 +.byte 176 +.byte 15 +.byte 127 +.byte 14 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 107 +.byte 14 +.byte 240 +.byte 15 +.byte 112 +.byte 15 +.byte 48 +.byte 15 +.byte 16 +.byte 15 +.byte 144 +.byte 15 +.byte 96 +.byte 13 +.byte 127 +.byte 14 +.byte 96 +.byte 9 +.byte 0 +.byte 63 +.byte 208 +.byte 15 +.byte 144 +.byte 15 +.byte 16 +.byte 15 +.byte 48 +.byte 15 +.byte 112 +.byte 15 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 240 +.byte 15 +.byte 224 +.byte 15 +.byte 120 +.byte 14 +.byte 192 +.byte 15 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 0 +.byte 63 +.byte 168 +.byte 8 +.byte 1 +.byte 10 +.byte 112 +.byte 9 +Sprite_bitmap_b0: +.byte 0 +.byte 1 +.byte 2 +.byte 2 +HasColormap_colormap_b0: +.byte 1 +.byte 2 +.byte 2 +Moving_speed_b0: +.byte 2 +.byte 1 +.byte 1 +Main__INITDATA: +.byte 0 +.byte 5 +.byte 0 +.byte 0 +.byte 2 +.byte 2 +.byte 2 +.byte 40 +.byte 70 +.byte 100 +.byte 30 +.byte 70 +.byte 60 +.byte 1 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +__Start: + +;;; start action Init__main_init__1 + +.include "vcs-ca65.h" +.macpack longbranch +.define PAL 0 +__NMI: +__Reset: +__BRK: + CLEAN_START + + ldy #18 +: lda Main__INITDATA-1,y + sta Sprite_plyrflags_b0-1,y + dey + bne :- + +;;; start action FrameLoop__start__3 + + +FrameLoop__start__4__NextFrame: + FRAME_END + + FRAME_START + +;;; start action Kernel2Sprite__preframe__5 + +; TODOO: can store KLINES in memory? +.define KLINES #190 +.define KPAD 32 +; set height to zero in case no sprites + lda #0 + sta Kernel2Sprite__2__tmp+8 + sta Kernel2Sprite__2__tmp+9 + +;;; end action Kernel2Sprite__preframe__5 + +;;; start action Kernel2Sprite__preframe__8 + + ldy #0 +Kernel2Sprite__preframe__9____each: + ldx SpriteSlot_sprite_b0,y + +; set player object flags + lda Sprite_plyrflags_b0,x + sta NUSIZ0,y + sta REFP0,y +; calculate screen height - ypos + lda KLINES + clc + adc KPAD + sec + sbc HasYpos_ypos_b0-1,x + sta Kernel2Sprite__2__tmp+11 +; calculate bitmap pointer + stx Kernel2Sprite__2__tmp+12 ; save X (Sprite index) + lda Sprite_bitmap_b0,x ; deref bitmap + tax + lda Bitmap_bitmapdata_b0,x + sec + sbc Kernel2Sprite__2__tmp+11 + sta Kernel2Sprite__2__tmp+0,y ; Y = sprite slot index + lda Bitmap_bitmapdata_b8,x + sbc #0 + sta Kernel2Sprite__2__tmp+2,y +; get bitmap height + lda Bitmap_height_b0,x + sta Kernel2Sprite__2__tmp+8,y +; calculate colormap pointer + ldx Kernel2Sprite__2__tmp+12 ; restore X + lda HasColormap_colormap_b0-1,x ; deref colormap + tax + lda Colormap_colormapdata_b0,x + sec + sbc Kernel2Sprite__2__tmp+11 + sta Kernel2Sprite__2__tmp+4,y + lda Colormap_colormapdata_b8,x + sbc #0 + sta Kernel2Sprite__2__tmp+6,y +; save ypos + ldx Kernel2Sprite__2__tmp+12 ; restore X + lda HasYpos_ypos_b0-1,x + sta Kernel2Sprite__2__tmp+10,y + + iny + cpy #2 + jne Kernel2Sprite__preframe__9____each +Kernel2Sprite__preframe__9____exit: + +;;; end action Kernel2Sprite__preframe__8 + +;;; start action Kernel2Sprite__preframe__11 + +; shuffle pointers into (MSB, LSB) byte order +; L0 L1 H0 H1 -> L0 H0 L1 H1 + lda Kernel2Sprite__2__tmp+1 + ldy Kernel2Sprite__2__tmp+2 + sty Kernel2Sprite__2__tmp+1 + sta Kernel2Sprite__2__tmp+2 + lda Kernel2Sprite__2__tmp+5 + ldy Kernel2Sprite__2__tmp+6 + sty Kernel2Sprite__2__tmp+5 + sta Kernel2Sprite__2__tmp+6 + +;;; end action Kernel2Sprite__preframe__11 + +;;; start action Kernel2Sprite__preframe__13 + + lda #162 + sta COLUBK + +;;; end action Kernel2Sprite__preframe__13 + +;;; start action Kernel2Sprite__preframe__16 + +;;; end action Kernel2Sprite__preframe__16 + +;;; start action SetXPos__preframe__17 + + ldy #0 +SetXPos__preframe__18____each: + ldx SpriteSlot_sprite_b0,y + + lda HasXpos_xpos_b0-1,x + + jsr SetHorizPos__SetHorizPos__20 + + + iny + cpy #2 + jne SetXPos__preframe__18____each +SetXPos__preframe__18____exit: + +;;; end action SetXPos__preframe__17 + +;;; start action SetXPos__preframe__22 + +;;; end action SetXPos__preframe__22 + +;;; start action VersatilePlayfield__preframe__23 + + ldx Location_room_b0+0 + + lda VersatilePlayfield_data_b0,x + sta VersatilePlayfield__10__tmp+0 + lda VersatilePlayfield_data_b8,x + sta VersatilePlayfield__10__tmp+1 + + +;;; end action VersatilePlayfield__preframe__23 + + +;;; start action SetXPos__prekernel__26 + + sta WSYNC + sta HMOVE + SLEEPR 24 + sta HMCLR + +;;; end action SetXPos__prekernel__26 + + KERNEL_START + +;;; start action Kernel2Sprite__kernel__28 + + ldy #0 + sty VDELP0 + iny + sty VDELP1 + +;;; end action Kernel2Sprite__kernel__28 + + jsr Kernel2Sprite__kernel__31 + +;;; start action Kernel2Sprite__kernel__48 + + lda #0 + sta GRP0 + sta GRP1 + sta GRP0 + sta GRP1 + +;;; end action Kernel2Sprite__kernel__48 + + KERNEL_END + + +;;; start action FrameLoop__postframe__50 + + lsr SWCHB ; test Game Reset switch + bcs FrameLoop__postframe__51__NoStart + +FrameLoop__postframe__51__NoStart: + +;;; end action FrameLoop__postframe__50 + +;;; start action Joystick__postframe__52 + +; 2 control inputs share a single byte, 4 bits each + lda SWCHA + sta Joystick__3__tmp+0 + +;;; end action Joystick__postframe__52 + +;;; start action Joystick__postframe__54 + + asl Joystick__3__tmp+0 +.ifdef EVENT__joyright + bcs Joystick__postframe__56__SkipMoveRight + +;;; start action JoyFaceDirection__joyright__57 + + lda Sprite_plyrflags_b0+1 + and #$f7 + sta Sprite_plyrflags_b0+1 + +;;; end action JoyFaceDirection__joyright__57 + +;;; start action SuperFly__joyright__60 + + lda HasXpos_xpos_b0 + clc + adc #2 + cmp #142 + jcc SuperFly__joyright__62__nomove + +;;; start action SuperFly__goeast__63 + + ldy Location_room_b0 + lda Room_east_b0,y + sta Location_room_b0 + +;;; end action SuperFly__goeast__63 + + lda #2 +SuperFly__joyright__62__nomove: + sta HasXpos_xpos_b0 + +;;; end action SuperFly__joyright__60 + +Joystick__postframe__56__SkipMoveRight: +.endif + asl Joystick__3__tmp+0 +.ifdef EVENT__joyleft + bcs Joystick__postframe__56__SkipMoveLeft + +;;; start action JoyFaceDirection__joyleft__66 + + lda Sprite_plyrflags_b0+1 + ora #$08 + sta Sprite_plyrflags_b0+1 + +;;; end action JoyFaceDirection__joyleft__66 + +;;; start action SuperFly__joyleft__69 + + lda HasXpos_xpos_b0 + sec + sbc #2 + jcs SuperFly__joyleft__71__nomove + +;;; start action SuperFly__gowest__72 + + ldy Location_room_b0 + lda Room_west_b0,y + sta Location_room_b0 + +;;; end action SuperFly__gowest__72 + + lda #142 +SuperFly__joyleft__71__nomove: + sta HasXpos_xpos_b0 + +;;; end action SuperFly__joyleft__69 + +Joystick__postframe__56__SkipMoveLeft: +.endif + asl Joystick__3__tmp+0 +.ifdef EVENT__joydown + bcs Joystick__postframe__56__SkipMoveDown + +;;; start action SuperFly__joydown__75 + + lda HasYpos_ypos_b0 + clc + adc #2 + cmp #220 + jcc SuperFly__joydown__77__nomove + +;;; start action SuperFly__gosouth__78 + + ldy Location_room_b0 + lda Room_south_b0,y + sta Location_room_b0 + +;;; end action SuperFly__gosouth__78 + + lda #2 +SuperFly__joydown__77__nomove: + sta HasYpos_ypos_b0 + +;;; end action SuperFly__joydown__75 + +Joystick__postframe__56__SkipMoveDown: +.endif + asl Joystick__3__tmp+0 +.ifdef EVENT__joyup + bcs Joystick__postframe__56__SkipMoveUp + +;;; start action SuperFly__joyup__81 + + lda HasYpos_ypos_b0 + sec + sbc #2 + jcs SuperFly__joyup__83__nomove + +;;; start action SuperFly__gonorth__84 + + ldy Location_room_b0 + lda Room_north_b0,y + sta Location_room_b0 + +;;; end action SuperFly__gonorth__84 + + lda #200 +SuperFly__joyup__83__nomove: + sta HasYpos_ypos_b0 + +;;; end action SuperFly__joyup__81 + +Joystick__postframe__56__SkipMoveUp: +.endif + +;;; end action Joystick__postframe__54 + +;;; start action BadMove__postframe__87 + + ldx #0 +BadMove__postframe__88____each: + + +;;; start action JoyFaceDirection__joyright__90 + + lda Sprite_plyrflags_b0+2,x + and #$f7 + sta Sprite_plyrflags_b0+2,x + +;;; end action JoyFaceDirection__joyright__90 + +;;; start action SuperFly__joyright__93 + + lda HasXpos_xpos_b0+1,x + clc + adc #1 + cmp #142 + jcc SuperFly__joyright__95__nomove + +;;; start action SuperFly__goeast__96 + + ldy Location_room_b0+1,x + lda Room_east_b0,y + sta Location_room_b0+1,x + +;;; end action SuperFly__goeast__96 + + lda #2 +SuperFly__joyright__95__nomove: + sta HasXpos_xpos_b0+1,x + +;;; end action SuperFly__joyright__93 + + + inx + cpx #2 + jne BadMove__postframe__88____each +BadMove__postframe__88____exit: + +;;; end action BadMove__postframe__87 + +;;; start action RoomShuffle__postframe__99 + + ldy #3-1 + ldx SpriteSlot_sprite_b0+1 + beq RoomShuffle__postframe__101__empty ; empty slot, load 1st entry +RoomShuffle__postframe__101__loop: + inx + cpx #3+1 + bcc RoomShuffle__postframe__101__norecycle +; TODO: need to get index of specific entity +RoomShuffle__postframe__101__empty: + ldx #2 ; skip null sprite and player +RoomShuffle__postframe__101__norecycle: +; TODO: should automatically index + lda Location_room_b0-1,x + cmp Location_room_b0 + beq RoomShuffle__postframe__101__exit + dey + bne RoomShuffle__postframe__101__loop + ldx #0 ; null sprite +RoomShuffle__postframe__101__exit: + stx SpriteSlot_sprite_b0+1 + +;;; end action RoomShuffle__postframe__99 + +;;; start action VersatilePlayfield__postframe__102 + + lda #0 + sta PF0 + sta PF1 + sta PF2 + +;;; end action VersatilePlayfield__postframe__102 + + jmp FrameLoop__start__4__NextFrame ; loop to next frame + +;;; end action FrameLoop__start__3 + ; start main routine +.segment "VECTORS" +ZeroByte: .byte $00 +Return: .byte $60 +VecNMI: +VecReset: .word __Reset +VecBRK: .word __BRK +.code + +;;; end action Init__main_init__1 +.rodata +__ALIGNORIGIN: +.rodata +SetHorizPos__SetHorizPos__20: + +; SetHorizPos routine +; A = X coordinate +; Y = player number (0 or 1) + sec ; set carry flag + sta WSYNC ; start a new line +: + sbc #15 ; subtract 15 + bcs :- ; branch until negative + eor #7 ; calculate fine offset + asl + asl + asl + asl + sta HMP0,y ; set fine offset + sta RESP0,y ; fix coarse position + sta WSYNC ; won't overrun if X < 150 + + rts + +.assert >(SetHorizPos__SetHorizPos__20) = >(*), error, "SetHorizPos__SetHorizPos__20 crosses a page boundary!" + +.assert (* - SetHorizPos__SetHorizPos__20) <= 22, error, .sprintf("SetHorizPos__SetHorizPos__20 does not fit in 22 bytes, it took %d!", (* - SetHorizPos__SetHorizPos__20)) +.rodata +Kernel2Sprite__kernel__31: + + ldy #190 +Kernel2Sprite__kernel__33__LVScan: + +;;; start action Kernel2Sprite__scanline__34 + +; draw player 0 + lda Kernel2Sprite__2__tmp+8 ; height + dcp Kernel2Sprite__2__tmp+10 ; ypos + bcs Kernel2Sprite__scanline__35__DoDraw1 + lda #0 + .byte $2C +Kernel2Sprite__scanline__35__DoDraw1: + lda (Kernel2Sprite__2__tmp+0),y +; .if 0 = 0 ; TODO: configurable? + sta WSYNC +; .endif + sta GRP0 + lda (Kernel2Sprite__2__tmp+4),y + sta COLUP0 +; draw player 1 + lda Kernel2Sprite__2__tmp+9 ; height + dcp Kernel2Sprite__2__tmp+11 ; ypos + bcs Kernel2Sprite__scanline__35__DoDraw2 + lda #0 + .byte $2C +Kernel2Sprite__scanline__35__DoDraw2: + lda (Kernel2Sprite__2__tmp+2),y + sta GRP1 + lda (Kernel2Sprite__2__tmp+6),y + sta COLUP1 + +;;; end action Kernel2Sprite__scanline__34 + +;;; start action Kernel2Sprite__scanline__36 + +;;; end action Kernel2Sprite__scanline__36 + +;;; start action VersatilePlayfield__scanline__37 + +.if 0 = 0 + lda (VersatilePlayfield__10__tmp+0),y + tax +.endif + +;;; end action VersatilePlayfield__scanline__37 + +;;; start action VersatilePlayfield__scanline__39 + +.if 0 = 1 + lda (VersatilePlayfield__10__tmp+0),y + sta $00,x +.endif + +;;; end action VersatilePlayfield__scanline__39 + + dey ; next scanline + +;;; start action Kernel2Sprite__scanline__41 + +; draw player 0 + lda Kernel2Sprite__2__tmp+8 ; height + dcp Kernel2Sprite__2__tmp+10 ; ypos + bcs Kernel2Sprite__scanline__42__DoDraw1 + lda #0 + .byte $2C +Kernel2Sprite__scanline__42__DoDraw1: + lda (Kernel2Sprite__2__tmp+0),y +; .if 1 = 0 ; TODO: configurable? + sta WSYNC +; .endif + sta GRP0 + lda (Kernel2Sprite__2__tmp+4),y + sta COLUP0 +; draw player 1 + lda Kernel2Sprite__2__tmp+9 ; height + dcp Kernel2Sprite__2__tmp+11 ; ypos + bcs Kernel2Sprite__scanline__42__DoDraw2 + lda #0 + .byte $2C +Kernel2Sprite__scanline__42__DoDraw2: + lda (Kernel2Sprite__2__tmp+2),y + sta GRP1 + lda (Kernel2Sprite__2__tmp+6),y + sta COLUP1 + +;;; end action Kernel2Sprite__scanline__41 + +;;; start action Kernel2Sprite__scanline__43 + +;;; end action Kernel2Sprite__scanline__43 + +;;; start action VersatilePlayfield__scanline__44 + +.if 1 = 0 + lda (VersatilePlayfield__10__tmp+0),y + tax +.endif + +;;; end action VersatilePlayfield__scanline__44 + +;;; start action VersatilePlayfield__scanline__46 + +.if 1 = 1 + lda (VersatilePlayfield__10__tmp+0),y + sta $00,x +.endif + +;;; end action VersatilePlayfield__scanline__46 + + dey ; next scanline + bne Kernel2Sprite__kernel__33__LVScan ; repeat until out of lines + + rts + +.assert >(Kernel2Sprite__kernel__31) = >(*), error, "Kernel2Sprite__kernel__31 crosses a page boundary!" +.endscope +Main__Start = Main::__Start \ No newline at end of file diff --git a/test/ecs/titles.ecs b/test/ecs/titles.ecs new file mode 100644 index 00000000..bc982e7f --- /dev/null +++ b/test/ecs/titles.ecs @@ -0,0 +1,305 @@ + +//#resource "vcs-ca65.h" + +import "vcslib.ecs" + +component Bitmap48 + bitmap0: array of 0..0xff + bitmap1: array of 0..0xff + bitmap2: array of 0..0xff + bitmap3: array of 0..0xff + bitmap4: array of 0..0xff + bitmap5: array of 0..0xff + height: 0..255 +end + +system Kernel48Pixel + locals 2 + on kernelsetup do if [Bitmap48] --- + lda {{ [GRP0] + lda {{data bitmap1}},y ; load B1 -> A + sta GRP1 ; B1 -> [GRP1], B0 -> GRP0 + lda {{data bitmap2}},y ; load B2 -> A + sta GRP0 ; B2 -> [GRP0], B1 -> GRP1 + lda {{data bitmap5}},y ; load B5 -> A + sta {{$1}} ; B5 -> temp + ldx {{data bitmap4}},y ; load B4 -> X + lda {{data bitmap3}},y ; load B3 -> A + ldy {{$1}} ; load B5 -> Y + sta GRP1 ; B3 -> [GRP1]; B2 -> GRP0 + stx GRP0 ; B4 -> [GRP0]; B3 -> GRP1 + sty GRP1 ; B5 -> [GRP1]; B4 -> GRP0 + sta GRP0 ; ?? -> [GRP0]; B5 -> GRP1 + dec {{$0}} ; go to next line + bpl :- ; repeat until < 0 + pla + tax +--- +end + +demo TitleDemo + + scope Title + + using FrameLoop, StandardKernel + using Kernel48Pixel with [#Name] + using Kernel48Pixel with [#Copyright] + using JoyButton + + entity [Player] + end + + system Advance + on joybutton do once + --- + jmp Title2__Start + --- + end + + entity [KernelSection, BGColor] + const lines = 30 + const bgcolor = $a0 + end + + entity [KernelSection, BGColor] + const lines = 10 + const bgcolor = $a2 + end + +// convert -size 48x -gravity center label:"6502\nECS" pbm: | pnmtopnm -plain + entity Name [KernelSection, BGColor, PFColor, Bitmap48] + const lines = 2 + const bgcolor = 0xa4 + const pfcolor = 0xfc + decode vcs_bitmap48 --- +000000111100000111111110000011110000001111100000 +000011111110000111111110001111111000011111110000 +000011000011000110000000001100011000111000011000 +000110000011000110000000011000001100110000011000 +000110000000000110000000011000001100000000011000 +000110111100000111111000011000001100000000011000 +000111111110000111111100011000001100000000110000 +000111000111000000001110011000001100000001110000 +000110000011000000000110011000001100000011100000 +000110000011000000000110011000001100000111000000 +000110000011000000000110011000001100001110000000 +000110000011001100000110011000001100011100000000 +000011000011001100001110001100011000111000000000 +000011111110000111111100001111111000111111111000 +000000111100000011111000000111110000111111111000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000111111111100000011111000000001111100000000 +000000111111111100001111111110000111111111000000 +000000110000000000011100000111000110000011100000 +000000110000000000011000000011001100000001100000 +000000110000000000111000000001001100000000000000 +000000110000000000110000000000000111000000000000 +000000111111111000110000000000000011111000000000 +000000111111111000110000000000000001111111000000 +000000110000000000110000000000000000000111100000 +000000110000000000110000000000000000000001100000 +000000110000000000111000000011101100000001100000 +000000110000000000011000000011001100000001100000 +000000110000000000011100000111000110000011100000 +000000111111111100001111111110000111111111000000 +000000111111111100000011111000000001111110000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 + +--- + end + + entity Copyright [KernelSection, BGColor, PFColor, Bitmap48] + const lines = 2 + const bgcolor = 0xa4 + const pfcolor = 0x8c + decode vcs_bitmap48 --- +000000000000000000000000000000100000000100000100 +000000000000000000000000000000000000000100000100 +000111000111001111010001101110100011010111101110 +001001001001001101111001001100100100110110100100 +001001001000101000101001001000100100110100010100 +011000011000101000101001001000101100010100010100 +011000011000101000101010001000101100010100010100 +011001001000101000100110001000100100110100010100 +001001001001001001000110001000100100110100010110 +000110000111001111000110001000100011110100010000 +000000000000001000000100000000000000010000000000 +000000000000001000000100000000000100100000000000 +000000000000001000001000000000000111100000000000 +000000000000000000000000000000000000000000000000 +000000000000001110001110001110001110000000000000 +000000000000010011011010010011010011000000000000 +000000000000010001010011010011010001000000000000 +000000000000000001110001000001000001000000000000 +000000000000000011110001000011000011000000000000 +000000000000000110110001000110000110000000000000 +000000000000001100110001001100001000000000000000 +000000000000010000010001010000010000000000000000 +000000000000010000010010010000010000000000000000 +000000000000111111001110111111111111000000000000 +000000000000000000000000000000000000000000000000 +--- + end + + entity [KernelSection, BGColor] + const lines = 10 + const bgcolor = $a2 + end + entity [KernelSection, BGColor] + const lines = 10 + const bgcolor = $a0 + end + + end + + scope Title2 + + using FrameLoop, Kernel48Pixel, StandardKernel + + entity [KernelSection, BGColor] + var lines = 60 + var bgcolor = 0x10 + end + + entity [KernelSection, BGColor] + var lines = 30 + var bgcolor = 0x30 + end + +// convert -size 48x -gravity center label:"6502\nECS" pbm: | pnmtopnm -plain + entity [KernelSection, BGColor, PFColor, Bitmap48] + var lines = 2 + var pfcolor = 0xec + var bgcolor = 0x30 + decode vcs_bitmap48 --- +000000000000000000000000000000000000000000011000 +000000000000000000000000000000000000000000011000 +000000000000000000000000000000000000000000011000 +000000000000000000000000000000000000000000011000 +000001111011000011111000000111110000001111011000 +000011111111000111111100001111111000011111111000 +000110000111001100001110011000001100110000111000 +000110000011001100000110011000001100110000011000 +000110000011001100000110011000001100110000011000 +000110000011001100000110011000001100110000011000 +000110000011001100000110011000001100110000011000 +000110000011001100000110011000001100110000011000 +000110000111001100000110011000001100110000111000 +000011111111000111111100001111111000011111111000 +000001111011000011111000000111110000001111011000 +000000000011000000000000000000000000000000000000 +000110000110000000000000000000000000000000000000 +000111111110000000000000000000000000000000000000 +000001111100000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000110000000000000110000000000110000000000 +000000000110000000000000110000000000110000000000 +000000000000000000000000110000000000110000000000 +000000000000000000000000110000000000110000000000 +000000000110000111110000110111100000110000000000 +000000000110001111111000111111110000110000000000 +000000000110011100001100111000011000110000000000 +000000000110011000001100110000011000110000000000 +000000000110011000001100110000011000110000000000 +000000000110011000001100110000011000110000000000 +000000000110011000001100110000011000110000000000 +000000000110011000001100110000011000000000000000 +000000000110011000001100111000011000000000000000 +000000000110001111111000111100110000110000000000 +000000000110000111110000110111100000110000000000 +000000000110000000000000000000000000000000000000 +000000000110000000000000000000000000000000000000 +000000011110000000000000000000000000000000000000 +000000011100000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +--- + end + + entity [KernelSection, BGColor] + var lines = 20 + var bgcolor = 0x30 + end + + entity [KernelSection, BGColor] + var lines = 20 + var bgcolor = 0x50 + end + +system Colors + on postframe do foreach [PFColor] +--- + inc {{set pfcolor}} + bne :+ + inc {{set bgcolor}} +: +--- + on postframe do foreach [KernelSection] +--- + dec {{set lines}} + bne :+ + lda #1 + sta {{set lines}} +: +--- +end + + end + + system Demo + on start do once + --- + {{start Title}} + --- + end + +end demo + diff --git a/test/ecs/titles.txt b/test/ecs/titles.txt new file mode 100644 index 00000000..252d0cd7 --- /dev/null +++ b/test/ecs/titles.txt @@ -0,0 +1,1529 @@ +EVENT__start = 1 +EVENT__postframe = 1 +EVENT__wsync = 1 +EVENT__preframe = 1 +EVENT__kernel = 1 +EVENT__kerneldraw = 1 +EVENT__kernelsetup = 1 +EVENT__joybutton = 1 +.scope TitleDemo +.zeropage +.code +__Start: + +;;; start action Init__main_init__1 + +.include "vcs-ca65.h" +.macpack longbranch +.define PAL 0 +__NMI: +__Reset: +__BRK: + CLEAN_START + +;;; start action Demo__start__3 + + jmp Title__Start + +;;; end action Demo__start__3 + ; start main routine +.segment "VECTORS" +ZeroByte: .byte $00 +Return: .byte $60 +VecNMI: +VecReset: .word __Reset +VecBRK: .word __BRK +.code + +;;; end action Init__main_init__1 + +.scope Title +.zeropage +TEMP: +Kernel48Pixel__3__tmp: +.res 1 +.res 1 +Kernel48Pixel__4__tmp: +.res 1 +.res 1 +.code +KernelSection_lines_b0: +.byte 30 +.byte 10 +.byte 2 +.byte 2 +.byte 10 +.byte 10 +BGColor_bgcolor_b0: +.byte 160 +.byte 162 +.byte 164 +.byte 164 +.byte 162 +.byte 160 +PFColor_pfcolor_b0: +.byte 252 +.byte 140 +Bitmap48_bitmap0_b0: +.byte Bitmap48_bitmap0_e3_b0 +.byte >Bitmap48_bitmap0_e4_b0 +Bitmap48_bitmap0_e3_b0: +.byte 0 +.byte 0 +.byte 3 +.byte 3 +.byte 3 +.byte 3 +.byte 3 +.byte 3 +.byte 3 +.byte 3 +.byte 3 +.byte 3 +.byte 3 +.byte 3 +.byte 3 +.byte 3 +.byte 3 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 3 +.byte 15 +.byte 12 +.byte 24 +.byte 24 +.byte 24 +.byte 24 +.byte 28 +.byte 31 +.byte 27 +.byte 24 +.byte 24 +.byte 12 +.byte 15 +.byte 3 +Bitmap48_bitmap1_b0: +.byte Bitmap48_bitmap1_e3_b0 +.byte >Bitmap48_bitmap1_e4_b0 +Bitmap48_bitmap1_e3_b0: +.byte 0 +.byte 0 +.byte 255 +.byte 255 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 254 +.byte 254 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 255 +.byte 255 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 192 +.byte 225 +.byte 51 +.byte 51 +.byte 48 +.byte 48 +.byte 48 +.byte 112 +.byte 225 +.byte 193 +.byte 1 +.byte 49 +.byte 49 +.byte 225 +.byte 193 +Bitmap48_bitmap2_b0: +.byte Bitmap48_bitmap2_e3_b0 +.byte >Bitmap48_bitmap2_e4_b0 +Bitmap48_bitmap2_e3_b0: +.byte 0 +.byte 0 +.byte 3 +.byte 15 +.byte 28 +.byte 24 +.byte 56 +.byte 48 +.byte 48 +.byte 48 +.byte 48 +.byte 48 +.byte 56 +.byte 24 +.byte 28 +.byte 15 +.byte 3 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 248 +.byte 252 +.byte 14 +.byte 6 +.byte 6 +.byte 6 +.byte 6 +.byte 14 +.byte 252 +.byte 248 +.byte 128 +.byte 128 +.byte 128 +.byte 254 +.byte 254 +Bitmap48_bitmap3_b0: +.byte Bitmap48_bitmap3_e3_b0 +.byte >Bitmap48_bitmap3_e4_b0 +Bitmap48_bitmap3_e3_b0: +.byte 0 +.byte 0 +.byte 224 +.byte 248 +.byte 28 +.byte 12 +.byte 14 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 4 +.byte 12 +.byte 28 +.byte 248 +.byte 224 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 31 +.byte 63 +.byte 49 +.byte 96 +.byte 96 +.byte 96 +.byte 96 +.byte 96 +.byte 96 +.byte 96 +.byte 96 +.byte 96 +.byte 49 +.byte 63 +.byte 15 +Bitmap48_bitmap4_b0: +.byte Bitmap48_bitmap4_e3_b0 +.byte >Bitmap48_bitmap4_e4_b0 +Bitmap48_bitmap4_e3_b0: +.byte 0 +.byte 0 +.byte 31 +.byte 127 +.byte 96 +.byte 192 +.byte 192 +.byte 0 +.byte 1 +.byte 31 +.byte 62 +.byte 112 +.byte 192 +.byte 192 +.byte 96 +.byte 127 +.byte 31 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 15 +.byte 143 +.byte 142 +.byte 199 +.byte 195 +.byte 193 +.byte 192 +.byte 192 +.byte 192 +.byte 192 +.byte 192 +.byte 204 +.byte 142 +.byte 135 +.byte 3 +Bitmap48_bitmap5_b0: +.byte Bitmap48_bitmap5_e3_b0 +.byte >Bitmap48_bitmap5_e4_b0 +Bitmap48_bitmap5_e3_b0: +.byte 0 +.byte 0 +.byte 128 +.byte 192 +.byte 224 +.byte 96 +.byte 96 +.byte 96 +.byte 224 +.byte 192 +.byte 0 +.byte 0 +.byte 0 +.byte 96 +.byte 224 +.byte 192 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 248 +.byte 248 +.byte 0 +.byte 0 +.byte 128 +.byte 192 +.byte 224 +.byte 112 +.byte 48 +.byte 24 +.byte 24 +.byte 24 +.byte 24 +.byte 240 +.byte 224 +Bitmap48_height_b0: +.byte 36 +.byte 24 +Bitmap48_bitmap0_e4_b0: +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 24 +.byte 36 +.byte 100 +.byte 97 +.byte 97 +.byte 36 +.byte 36 +.byte 28 +.byte 0 +.byte 0 +Bitmap48_bitmap1_e4_b0: +.byte 0 +.byte 15 +.byte 4 +.byte 4 +.byte 3 +.byte 1 +.byte 0 +.byte 0 +.byte 4 +.byte 4 +.byte 3 +.byte 0 +.byte 2 +.byte 2 +.byte 2 +.byte 115 +.byte 146 +.byte 138 +.byte 138 +.byte 138 +.byte 138 +.byte 147 +.byte 115 +.byte 0 +.byte 0 +Bitmap48_bitmap2_e4_b0: +.byte 0 +.byte 206 +.byte 18 +.byte 17 +.byte 49 +.byte 177 +.byte 241 +.byte 113 +.byte 83 +.byte 218 +.byte 142 +.byte 0 +.byte 8 +.byte 4 +.byte 4 +.byte 198 +.byte 70 +.byte 38 +.byte 42 +.byte 41 +.byte 41 +.byte 121 +.byte 209 +.byte 0 +.byte 0 +Bitmap48_bitmap3_e4_b0: +.byte 0 +.byte 255 +.byte 65 +.byte 65 +.byte 48 +.byte 24 +.byte 12 +.byte 4 +.byte 77 +.byte 77 +.byte 56 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 34 +.byte 34 +.byte 34 +.byte 34 +.byte 34 +.byte 34 +.byte 50 +.byte 186 +.byte 0 +.byte 2 +Bitmap48_bitmap4_e4_b0: +.byte 0 +.byte 240 +.byte 0 +.byte 0 +.byte 128 +.byte 96 +.byte 48 +.byte 16 +.byte 16 +.byte 48 +.byte 224 +.byte 0 +.byte 120 +.byte 72 +.byte 4 +.byte 61 +.byte 77 +.byte 77 +.byte 197 +.byte 197 +.byte 77 +.byte 77 +.byte 53 +.byte 1 +.byte 1 +Bitmap48_bitmap5_e4_b0: +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 16 +.byte 22 +.byte 20 +.byte 20 +.byte 20 +.byte 20 +.byte 164 +.byte 238 +.byte 4 +.byte 4 +__Start: + +;;; start action FrameLoop__start__5 + + +FrameLoop__start__6__NextFrame: + FRAME_END + + FRAME_START + +;;; start action StandardKernel__preframe__7 + + +;;; start action StandardKernel__wsync__10 + + sta WSYNC + +;;; end action StandardKernel__wsync__10 + + +;;; start action StandardKernel__kernelsetup__12 + + lda #160 + sta COLUBK + +;;; end action StandardKernel__kernelsetup__12 + +;;; start action StandardKernel__kernelsetup__15 + +;;; end action StandardKernel__kernelsetup__15 + +;;; start action StandardKernel__kernelsetup__16 + +;;; end action StandardKernel__kernelsetup__16 + +;;; start action Kernel48Pixel__kernelsetup__17 + +;;; end action Kernel48Pixel__kernelsetup__17 + +;;; start action Kernel48Pixel__kernelsetup__18 + +;;; end action Kernel48Pixel__kernelsetup__18 + +;;; start action Kernel48Pixel__kernelsetup__19 + +;;; end action Kernel48Pixel__kernelsetup__19 + +;;; start action Kernel48Pixel__kernelsetup__20 + +;;; end action Kernel48Pixel__kernelsetup__20 + +;;; start action Kernel48Pixel__kernelsetup__21 + +;;; end action Kernel48Pixel__kernelsetup__21 + +;;; start action Kernel48Pixel__kernelsetup__22 + +;;; end action Kernel48Pixel__kernelsetup__22 + + +;;; end action StandardKernel__preframe__7 + + + KERNEL_START + +;;; start action StandardKernel__kernel__23 + + ldx #0 +StandardKernel__kernel__24____each: + + +;;; start action StandardKernel__wsync__26 + + sta WSYNC + +;;; end action StandardKernel__wsync__26 + + +;;; start action StandardKernel__kernelsetup__28 + + lda BGColor_bgcolor_b0,x + sta COLUBK + +;;; end action StandardKernel__kernelsetup__28 + +;;; start action StandardKernel__kernelsetup__31 + + cpx #2+2 + jcs StandardKernel__kernelsetup__32____skipxhi + + cpx #2 + jcc StandardKernel__kernelsetup__32____skipxlo + + lda PFColor_pfcolor_b0-2,x + sta COLUPF + +StandardKernel__kernelsetup__32____skipxlo: + +StandardKernel__kernelsetup__32____skipxhi: + +;;; end action StandardKernel__kernelsetup__31 + +;;; start action StandardKernel__kernelsetup__34 + +;;; end action StandardKernel__kernelsetup__34 + +;;; start action Kernel48Pixel__kernelsetup__35 + + cpx #2+1 + jcs Kernel48Pixel__kernelsetup__36____skipxhi + + cpx #2 + jcc Kernel48Pixel__kernelsetup__36____skipxlo + + lda #36 + sta Kernel48Pixel__3__tmp+0 ; scanline counter + +Kernel48Pixel__kernelsetup__36____skipxlo: + +Kernel48Pixel__kernelsetup__36____skipxhi: + +;;; end action Kernel48Pixel__kernelsetup__35 + +;;; start action Kernel48Pixel__kernelsetup__38 + + cpx #2+1 + jcs Kernel48Pixel__kernelsetup__39____skipxhi + + cpx #2 + jcc Kernel48Pixel__kernelsetup__39____skipxlo + + lda #$22 + sta COLUP0 ; show how players alternate + lda #$12 + sta COLUP1 ; by having different colors + lda #3 + sta NUSIZ0 + sta NUSIZ1 ; both players have 3 copies + sta WSYNC + SLEEPH 35 + sta RESP0 ; position 1st player + sta RESP1 ; ...and 2nd player + lda #$10 + sta HMP1 ; 1 pixel to the left + sta WSYNC + sta HMOVE ; apply HMOVE + lda #1 + sta VDELP0 ; we need the VDEL registers + sta VDELP1 ; so we can do our 4-store trick + SLEEPH 24-8 ; sleep 24 cycles + sta HMCLR ; clear HMOVE registers + +Kernel48Pixel__kernelsetup__39____skipxlo: + +Kernel48Pixel__kernelsetup__39____skipxhi: + +;;; end action Kernel48Pixel__kernelsetup__38 + +;;; start action Kernel48Pixel__kernelsetup__41 + + cpx #2+1 + jcs Kernel48Pixel__kernelsetup__42____skipxhi + + cpx #2 + jcc Kernel48Pixel__kernelsetup__42____skipxlo + + lda #252 + sta COLUP0 + sta COLUP1 + +Kernel48Pixel__kernelsetup__42____skipxlo: + +Kernel48Pixel__kernelsetup__42____skipxhi: + +;;; end action Kernel48Pixel__kernelsetup__41 + +;;; start action Kernel48Pixel__kernelsetup__44 + + cpx #3+1 + jcs Kernel48Pixel__kernelsetup__45____skipxhi + + cpx #3 + jcc Kernel48Pixel__kernelsetup__45____skipxlo + + lda #24 + sta Kernel48Pixel__4__tmp+0 ; scanline counter + +Kernel48Pixel__kernelsetup__45____skipxlo: + +Kernel48Pixel__kernelsetup__45____skipxhi: + +;;; end action Kernel48Pixel__kernelsetup__44 + +;;; start action Kernel48Pixel__kernelsetup__47 + + cpx #3+1 + jcs Kernel48Pixel__kernelsetup__48____skipxhi + + cpx #3 + jcc Kernel48Pixel__kernelsetup__48____skipxlo + + lda #$22 + sta COLUP0 ; show how players alternate + lda #$12 + sta COLUP1 ; by having different colors + lda #3 + sta NUSIZ0 + sta NUSIZ1 ; both players have 3 copies + sta WSYNC + SLEEPH 35 + sta RESP0 ; position 1st player + sta RESP1 ; ...and 2nd player + lda #$10 + sta HMP1 ; 1 pixel to the left + sta WSYNC + sta HMOVE ; apply HMOVE + lda #1 + sta VDELP0 ; we need the VDEL registers + sta VDELP1 ; so we can do our 4-store trick + SLEEPH 24-8 ; sleep 24 cycles + sta HMCLR ; clear HMOVE registers + +Kernel48Pixel__kernelsetup__48____skipxlo: + +Kernel48Pixel__kernelsetup__48____skipxhi: + +;;; end action Kernel48Pixel__kernelsetup__47 + +;;; start action Kernel48Pixel__kernelsetup__50 + + cpx #3+1 + jcs Kernel48Pixel__kernelsetup__51____skipxhi + + cpx #3 + jcc Kernel48Pixel__kernelsetup__51____skipxlo + + lda #140 + sta COLUP0 + sta COLUP1 + +Kernel48Pixel__kernelsetup__51____skipxlo: + +Kernel48Pixel__kernelsetup__51____skipxhi: + +;;; end action Kernel48Pixel__kernelsetup__50 + + +;;; start action StandardKernel__kerneldraw__53 + + ldy KernelSection_lines_b0,x +StandardKernel__kerneldraw__55__loop: + + +;;; start action StandardKernel__wsync__56 + + sta WSYNC + +;;; end action StandardKernel__wsync__56 + + + dey + bne StandardKernel__kerneldraw__55__loop + +;;; end action StandardKernel__kerneldraw__53 + + jsr Kernel48Pixel__kerneldraw__58 + + jsr Kernel48Pixel__kerneldraw__63 + + + + inx + cpx #6 + jne StandardKernel__kernel__24____each +StandardKernel__kernel__24____exit: + +;;; end action StandardKernel__kernel__23 + + KERNEL_END + + +;;; start action FrameLoop__postframe__68 + + lsr SWCHB ; test Game Reset switch + bcs FrameLoop__postframe__69__NoStart + +FrameLoop__postframe__69__NoStart: + +;;; end action FrameLoop__postframe__68 + +;;; start action JoyButton__postframe__70 + + lda INPT4 ;read button input + bmi JoyButton__postframe__72__NotPressed + +;;; start action Advance__joybutton__73 + + jmp Title2__Start + +;;; end action Advance__joybutton__73 + +JoyButton__postframe__72__NotPressed: + +;;; end action JoyButton__postframe__70 + + jmp FrameLoop__start__6__NextFrame ; loop to next frame + +;;; end action FrameLoop__start__5 +.rodata +__ALIGNORIGIN: +.rodata +Kernel48Pixel__kerneldraw__58: + + cpx #2+1 + jcs Kernel48Pixel__kerneldraw__59____skipxhi + + cpx #2 + jcc Kernel48Pixel__kerneldraw__59____skipxlo + + txa + pha +: + ldy Kernel48Pixel__3__tmp+0 ; counts backwards + +;;; start action StandardKernel__wsync__61 + + sta WSYNC + +;;; end action StandardKernel__wsync__61 + ; sync to next scanline + lda Bitmap48_bitmap0_e3_b0,y ; load B0 (1st sprite byte) + sta GRP0 ; B0 -> [GRP0] + lda Bitmap48_bitmap1_e3_b0,y ; load B1 -> A + sta GRP1 ; B1 -> [GRP1], B0 -> GRP0 + lda Bitmap48_bitmap2_e3_b0,y ; load B2 -> A + sta GRP0 ; B2 -> [GRP0], B1 -> GRP1 + lda Bitmap48_bitmap5_e3_b0,y ; load B5 -> A + sta Kernel48Pixel__3__tmp+1 ; B5 -> temp + ldx Bitmap48_bitmap4_e3_b0,y ; load B4 -> X + lda Bitmap48_bitmap3_e3_b0,y ; load B3 -> A + ldy Kernel48Pixel__3__tmp+1 ; load B5 -> Y + sta GRP1 ; B3 -> [GRP1]; B2 -> GRP0 + stx GRP0 ; B4 -> [GRP0]; B3 -> GRP1 + sty GRP1 ; B5 -> [GRP1]; B4 -> GRP0 + sta GRP0 ; ?? -> [GRP0]; B5 -> GRP1 + dec Kernel48Pixel__3__tmp+0 ; go to next line + bpl :- ; repeat until < 0 + pla + tax + +Kernel48Pixel__kerneldraw__59____skipxlo: + +Kernel48Pixel__kerneldraw__59____skipxhi: + + rts + +.assert >(Kernel48Pixel__kerneldraw__58) = >(*), error, "Kernel48Pixel__kerneldraw__58 crosses a page boundary!" + +.assert (* - Kernel48Pixel__kerneldraw__58) <= 63, error, .sprintf("Kernel48Pixel__kerneldraw__58 does not fit in 63 bytes, it took %d!", (* - Kernel48Pixel__kerneldraw__58)) + +.if <(* - __ALIGNORIGIN) > 256-63 +.align $100 +.endif +.rodata +Kernel48Pixel__kerneldraw__63: + + cpx #3+1 + jcs Kernel48Pixel__kerneldraw__64____skipxhi + + cpx #3 + jcc Kernel48Pixel__kerneldraw__64____skipxlo + + txa + pha +: + ldy Kernel48Pixel__4__tmp+0 ; counts backwards + +;;; start action StandardKernel__wsync__66 + + sta WSYNC + +;;; end action StandardKernel__wsync__66 + ; sync to next scanline + lda Bitmap48_bitmap0_e4_b0,y ; load B0 (1st sprite byte) + sta GRP0 ; B0 -> [GRP0] + lda Bitmap48_bitmap1_e4_b0,y ; load B1 -> A + sta GRP1 ; B1 -> [GRP1], B0 -> GRP0 + lda Bitmap48_bitmap2_e4_b0,y ; load B2 -> A + sta GRP0 ; B2 -> [GRP0], B1 -> GRP1 + lda Bitmap48_bitmap5_e4_b0,y ; load B5 -> A + sta Kernel48Pixel__4__tmp+1 ; B5 -> temp + ldx Bitmap48_bitmap4_e4_b0,y ; load B4 -> X + lda Bitmap48_bitmap3_e4_b0,y ; load B3 -> A + ldy Kernel48Pixel__4__tmp+1 ; load B5 -> Y + sta GRP1 ; B3 -> [GRP1]; B2 -> GRP0 + stx GRP0 ; B4 -> [GRP0]; B3 -> GRP1 + sty GRP1 ; B5 -> [GRP1]; B4 -> GRP0 + sta GRP0 ; ?? -> [GRP0]; B5 -> GRP1 + dec Kernel48Pixel__4__tmp+0 ; go to next line + bpl :- ; repeat until < 0 + pla + tax + +Kernel48Pixel__kerneldraw__64____skipxlo: + +Kernel48Pixel__kerneldraw__64____skipxhi: + + rts + +.assert >(Kernel48Pixel__kerneldraw__63) = >(*), error, "Kernel48Pixel__kerneldraw__63 crosses a page boundary!" + +.assert (* - Kernel48Pixel__kerneldraw__63) <= 63, error, .sprintf("Kernel48Pixel__kerneldraw__63 does not fit in 63 bytes, it took %d!", (* - Kernel48Pixel__kerneldraw__63)) +.endscope +Title__Start = Title::__Start +.scope Title2 +.zeropage +KernelSection_lines_b0: +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +BGColor_bgcolor_b0: +.res 1 +.res 1 +.res 1 +.res 1 +.res 1 +PFColor_pfcolor_b0: +.res 1 +TEMP: +Kernel48Pixel__2__tmp: +.res 1 +.res 1 +.code +Bitmap48_bitmap0_b0: +.byte Bitmap48_bitmap0_e2_b0 +Bitmap48_bitmap0_e2_b0: +.byte 0 +.byte 1 +.byte 1 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 7 +.byte 31 +.byte 24 +.byte 0 +.byte 7 +.byte 15 +.byte 24 +.byte 24 +.byte 24 +.byte 24 +.byte 24 +.byte 24 +.byte 24 +.byte 15 +.byte 7 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +Bitmap48_bitmap1_b0: +.byte Bitmap48_bitmap1_e2_b0 +Bitmap48_bitmap1_e2_b0: +.byte 0 +.byte 192 +.byte 224 +.byte 96 +.byte 96 +.byte 97 +.byte 99 +.byte 102 +.byte 102 +.byte 102 +.byte 102 +.byte 102 +.byte 102 +.byte 103 +.byte 99 +.byte 97 +.byte 0 +.byte 0 +.byte 96 +.byte 96 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 192 +.byte 224 +.byte 96 +.byte 48 +.byte 176 +.byte 241 +.byte 115 +.byte 51 +.byte 51 +.byte 51 +.byte 51 +.byte 51 +.byte 115 +.byte 241 +.byte 176 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +Bitmap48_bitmap2_b0: +.byte Bitmap48_bitmap2_e2_b0 +Bitmap48_bitmap2_e2_b0: +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 240 +.byte 248 +.byte 12 +.byte 12 +.byte 12 +.byte 12 +.byte 12 +.byte 12 +.byte 12 +.byte 248 +.byte 240 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 248 +.byte 252 +.byte 6 +.byte 6 +.byte 6 +.byte 6 +.byte 6 +.byte 6 +.byte 14 +.byte 252 +.byte 248 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +Bitmap48_bitmap3_b0: +.byte Bitmap48_bitmap3_e2_b0 +Bitmap48_bitmap3_e2_b0: +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 222 +.byte 243 +.byte 225 +.byte 193 +.byte 193 +.byte 193 +.byte 193 +.byte 193 +.byte 225 +.byte 255 +.byte 222 +.byte 192 +.byte 192 +.byte 192 +.byte 192 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 31 +.byte 63 +.byte 96 +.byte 96 +.byte 96 +.byte 96 +.byte 96 +.byte 96 +.byte 96 +.byte 63 +.byte 31 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +Bitmap48_bitmap4_b0: +.byte Bitmap48_bitmap4_e2_b0 +Bitmap48_bitmap4_e2_b0: +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 12 +.byte 12 +.byte 128 +.byte 128 +.byte 140 +.byte 140 +.byte 140 +.byte 140 +.byte 140 +.byte 12 +.byte 12 +.byte 12 +.byte 12 +.byte 12 +.byte 12 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 3 +.byte 135 +.byte 204 +.byte 204 +.byte 204 +.byte 204 +.byte 204 +.byte 204 +.byte 204 +.byte 135 +.byte 3 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +Bitmap48_bitmap5_b0: +.byte Bitmap48_bitmap5_e2_b0 +Bitmap48_bitmap5_e2_b0: +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 0 +.byte 216 +.byte 248 +.byte 56 +.byte 24 +.byte 24 +.byte 24 +.byte 24 +.byte 24 +.byte 56 +.byte 248 +.byte 216 +.byte 24 +.byte 24 +.byte 24 +.byte 24 +Bitmap48_height_b0: +.byte 43 +Title2__INITDATA: +.byte 60 +.byte 30 +.byte 2 +.byte 20 +.byte 20 +.byte 16 +.byte 48 +.byte 48 +.byte 48 +.byte 80 +.byte 236 +__Start: + + ldy #11 +: lda Title2__INITDATA-1,y + sta KernelSection_lines_b0-1,y + dey + bne :- + +;;; start action FrameLoop__start__75 + + +FrameLoop__start__76__NextFrame: + FRAME_END + + FRAME_START + +;;; start action StandardKernel__preframe__77 + + +;;; start action StandardKernel__wsync__80 + + sta WSYNC + +;;; end action StandardKernel__wsync__80 + + +;;; start action Kernel48Pixel__kernelsetup__82 + +;;; end action Kernel48Pixel__kernelsetup__82 + +;;; start action Kernel48Pixel__kernelsetup__83 + +;;; end action Kernel48Pixel__kernelsetup__83 + +;;; start action Kernel48Pixel__kernelsetup__84 + +;;; end action Kernel48Pixel__kernelsetup__84 + +;;; start action StandardKernel__kernelsetup__85 + + lda BGColor_bgcolor_b0 + sta COLUBK + +;;; end action StandardKernel__kernelsetup__85 + +;;; start action StandardKernel__kernelsetup__88 + +;;; end action StandardKernel__kernelsetup__88 + +;;; start action StandardKernel__kernelsetup__89 + +;;; end action StandardKernel__kernelsetup__89 + + +;;; end action StandardKernel__preframe__77 + + + KERNEL_START + +;;; start action StandardKernel__kernel__90 + + ldx #0 +StandardKernel__kernel__91____each: + + +;;; start action StandardKernel__wsync__93 + + sta WSYNC + +;;; end action StandardKernel__wsync__93 + + +;;; start action Kernel48Pixel__kernelsetup__95 + + cpx #2+1 + jcs Kernel48Pixel__kernelsetup__96____skipxhi + + cpx #2 + jcc Kernel48Pixel__kernelsetup__96____skipxlo + + lda #43 + sta Kernel48Pixel__2__tmp+0 ; scanline counter + +Kernel48Pixel__kernelsetup__96____skipxlo: + +Kernel48Pixel__kernelsetup__96____skipxhi: + +;;; end action Kernel48Pixel__kernelsetup__95 + +;;; start action Kernel48Pixel__kernelsetup__98 + + cpx #2+1 + jcs Kernel48Pixel__kernelsetup__99____skipxhi + + cpx #2 + jcc Kernel48Pixel__kernelsetup__99____skipxlo + + lda #$22 + sta COLUP0 ; show how players alternate + lda #$12 + sta COLUP1 ; by having different colors + lda #3 + sta NUSIZ0 + sta NUSIZ1 ; both players have 3 copies + sta WSYNC + SLEEPH 35 + sta RESP0 ; position 1st player + sta RESP1 ; ...and 2nd player + lda #$10 + sta HMP1 ; 1 pixel to the left + sta WSYNC + sta HMOVE ; apply HMOVE + lda #1 + sta VDELP0 ; we need the VDEL registers + sta VDELP1 ; so we can do our 4-store trick + SLEEPH 24-8 ; sleep 24 cycles + sta HMCLR ; clear HMOVE registers + +Kernel48Pixel__kernelsetup__99____skipxlo: + +Kernel48Pixel__kernelsetup__99____skipxhi: + +;;; end action Kernel48Pixel__kernelsetup__98 + +;;; start action Kernel48Pixel__kernelsetup__101 + + cpx #2+1 + jcs Kernel48Pixel__kernelsetup__102____skipxhi + + cpx #2 + jcc Kernel48Pixel__kernelsetup__102____skipxlo + + lda PFColor_pfcolor_b0 + sta COLUP0 + sta COLUP1 + +Kernel48Pixel__kernelsetup__102____skipxlo: + +Kernel48Pixel__kernelsetup__102____skipxhi: + +;;; end action Kernel48Pixel__kernelsetup__101 + +;;; start action StandardKernel__kernelsetup__104 + + lda BGColor_bgcolor_b0,x + sta COLUBK + +;;; end action StandardKernel__kernelsetup__104 + +;;; start action StandardKernel__kernelsetup__107 + + cpx #2+1 + jcs StandardKernel__kernelsetup__108____skipxhi + + cpx #2 + jcc StandardKernel__kernelsetup__108____skipxlo + + lda PFColor_pfcolor_b0 + sta COLUPF + +StandardKernel__kernelsetup__108____skipxlo: + +StandardKernel__kernelsetup__108____skipxhi: + +;;; end action StandardKernel__kernelsetup__107 + +;;; start action StandardKernel__kernelsetup__110 + +;;; end action StandardKernel__kernelsetup__110 + + + jsr Kernel48Pixel__kerneldraw__111 + +;;; start action StandardKernel__kerneldraw__116 + + ldy KernelSection_lines_b0,x +StandardKernel__kerneldraw__118__loop: + + +;;; start action StandardKernel__wsync__119 + + sta WSYNC + +;;; end action StandardKernel__wsync__119 + + + dey + bne StandardKernel__kerneldraw__118__loop + +;;; end action StandardKernel__kerneldraw__116 + + + + inx + cpx #5 + jne StandardKernel__kernel__91____each +StandardKernel__kernel__91____exit: + +;;; end action StandardKernel__kernel__90 + + KERNEL_END + + +;;; start action FrameLoop__postframe__121 + + lsr SWCHB ; test Game Reset switch + bcs FrameLoop__postframe__122__NoStart + +FrameLoop__postframe__122__NoStart: + +;;; end action FrameLoop__postframe__121 + +;;; start action Colors__postframe__123 + + inc PFColor_pfcolor_b0 + bne :+ + inc BGColor_bgcolor_b0+2 +: + +;;; end action Colors__postframe__123 + +;;; start action Colors__postframe__126 + + ldx #0 +Colors__postframe__127____each: + + dec KernelSection_lines_b0,x + bne :+ + lda #1 + sta KernelSection_lines_b0,x +: + + inx + cpx #5 + jne Colors__postframe__127____each +Colors__postframe__127____exit: + +;;; end action Colors__postframe__126 + + jmp FrameLoop__start__76__NextFrame ; loop to next frame + +;;; end action FrameLoop__start__75 +.rodata +__ALIGNORIGIN: +.rodata +Kernel48Pixel__kerneldraw__111: + + cpx #2+1 + jcs Kernel48Pixel__kerneldraw__112____skipxhi + + cpx #2 + jcc Kernel48Pixel__kerneldraw__112____skipxlo + + txa + pha +: + ldy Kernel48Pixel__2__tmp+0 ; counts backwards + +;;; start action StandardKernel__wsync__114 + + sta WSYNC + +;;; end action StandardKernel__wsync__114 + ; sync to next scanline + lda Bitmap48_bitmap0_e2_b0,y ; load B0 (1st sprite byte) + sta GRP0 ; B0 -> [GRP0] + lda Bitmap48_bitmap1_e2_b0,y ; load B1 -> A + sta GRP1 ; B1 -> [GRP1], B0 -> GRP0 + lda Bitmap48_bitmap2_e2_b0,y ; load B2 -> A + sta GRP0 ; B2 -> [GRP0], B1 -> GRP1 + lda Bitmap48_bitmap5_e2_b0,y ; load B5 -> A + sta Kernel48Pixel__2__tmp+1 ; B5 -> temp + ldx Bitmap48_bitmap4_e2_b0,y ; load B4 -> X + lda Bitmap48_bitmap3_e2_b0,y ; load B3 -> A + ldy Kernel48Pixel__2__tmp+1 ; load B5 -> Y + sta GRP1 ; B3 -> [GRP1]; B2 -> GRP0 + stx GRP0 ; B4 -> [GRP0]; B3 -> GRP1 + sty GRP1 ; B5 -> [GRP1]; B4 -> GRP0 + sta GRP0 ; ?? -> [GRP0]; B5 -> GRP1 + dec Kernel48Pixel__2__tmp+0 ; go to next line + bpl :- ; repeat until < 0 + pla + tax + +Kernel48Pixel__kerneldraw__112____skipxlo: + +Kernel48Pixel__kerneldraw__112____skipxhi: + + rts + +.assert >(Kernel48Pixel__kerneldraw__111) = >(*), error, "Kernel48Pixel__kerneldraw__111 crosses a page boundary!" + +.assert (* - Kernel48Pixel__kerneldraw__111) <= 63, error, .sprintf("Kernel48Pixel__kerneldraw__111 does not fit in 63 bytes, it took %d!", (* - Kernel48Pixel__kerneldraw__111)) +.endscope +Title2__Start = Title2::__Start +.endscope +TitleDemo__Start = TitleDemo::__Start \ No newline at end of file diff --git a/test/ecs/vcs1.ecs b/test/ecs/vcs1.ecs new file mode 100644 index 00000000..5301a65c --- /dev/null +++ b/test/ecs/vcs1.ecs @@ -0,0 +1,235 @@ + +//#resource "vcs-ca65.h" + +system Init + on main_init do once +--- +.include "vcs-ca65.h" +.define PAL 0 +__NMI: +__Reset: +__BRK: + CLEAN_START +{{!start}} ; start main routine +.segment "VECTORS" +Return: .word $6060 +VecNMI: +VecReset: .word Main::__Reset +VecBRK: .word Main::__BRK +--- +end + +component Player +end + +component KernelSection + lines: 1..255 +end + +component BGColor + bgcolor: 0..255 +end + +component PFColor + pfcolor: 0..255 +end + +component Playfield + pf: 0..0xffffff +end + +component AsymPlayfield + pfleft: 0..0xffffff + pfright: 0..0xffffff +end + +component VersatilePlayfield + data: array of 0..255 baseoffset -1 +end + +system FrameLoop + on start do once +--- +@NextFrame: + FRAME_START + {{emit preframe}} + KERNEL_START + {{emit kernel}} + KERNEL_END + {{emit postframe}} + FRAME_END + {{emit nextframe}} + jmp @NextFrame ; loop to next frame +--- +end + +system ResetSwitch + on nextframe do once +--- + lsr SWCHB ; test Game Reset switch + bcs @NoStart + {{!resetswitch}} +@NoStart: +--- +end + +system ResetConsole + on resetswitch do once +--- + jmp Main::__Reset ; jump to Reset handler +--- +end + +system JoyButton + on postframe do foreach [Player] +--- + lda {{index INPT4}} ;read button input + bmi @NotPressed + {{emit joybutton}} +@NotPressed: +--- +end + +system Joystick + locals 1 + on postframe do once +--- +; 2 control inputs share a single byte, 4 bits each + lda SWCHA + sta {{$0}} +--- + on postframe do foreach [Player] +--- + asl {{$0}} + bcs @SkipMoveRight + {{!joyright}} +@SkipMoveRight: + asl {{$0}} + bcs @SkipMoveLeft + {{!joyleft}} +@SkipMoveLeft: + asl {{$0}} + bcs @SkipMoveDown + {{!joydown}} +@SkipMoveDown: + asl {{$0}} + bcs @SkipMoveUp + {{!joyup}} +@SkipMoveUp: +--- +end + +system SetHorizPos + on SetHorizPos do once +--- +; SetHorizPos routine +; A = X coordinate +; Y = player number (0 or 1) + sta WSYNC ; start a new line + sec ; set carry flag + nop +@DivideLoop: + sbc #15 ; subtract 15 + bcs @DivideLoop ; branch until negative + eor #7 ; calculate fine offset + asl + asl + asl + asl + sta RESP0,y ; fix coarse position + sta HMP0,y ; set fine offset +--- +end + + +system StaticKernel + on preframe do foreach [KernelSection] limit 1 +--- + {{!kernelsetup}} +--- + on kernel do foreach [KernelSection] +--- + sta WSYNC + {{!kernelsetup}} + ldy {{data}} + sta {{$1}} +--- + on scanline do once +--- +.if {{arg 0}} = 0 + lda ({{local 0}}),y + tax +.endif +--- + on scanline do once +--- +.if {{arg 0}} = 1 + lda ({{local 0}}),y + sta $00,x +.endif +--- + on postframe do once +--- + lda #0 + sta PF0 + sta PF1 + sta PF2 +--- + on checkplatform do with [HasXpos,HasYpos] + --- + lda {{